| // SPDX-License-Identifier: GPL-2.0-only | 
 | /* | 
 |  * MCP2200 - Microchip USB to GPIO bridge | 
 |  * | 
 |  * Copyright (c) 2023, Johannes Roith <johannes@gnu-linux.rocks> | 
 |  * | 
 |  * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22228A.pdf | 
 |  * App Note for HID: https://ww1.microchip.com/downloads/en/DeviceDoc/93066A.pdf | 
 |  */ | 
 | #include <linux/completion.h> | 
 | #include <linux/delay.h> | 
 | #include <linux/err.h> | 
 | #include <linux/gpio/driver.h> | 
 | #include <linux/hid.h> | 
 | #include <linux/hidraw.h> | 
 | #include <linux/module.h> | 
 | #include <linux/mutex.h> | 
 | #include "hid-ids.h" | 
 |  | 
 | /* Commands codes in a raw output report */ | 
 | #define SET_CLEAR_OUTPUTS	0x08 | 
 | #define CONFIGURE		0x10 | 
 | #define READ_EE			0x20 | 
 | #define WRITE_EE		0x40 | 
 | #define READ_ALL		0x80 | 
 |  | 
 | /* MCP GPIO direction encoding */ | 
 | enum MCP_IO_DIR { | 
 | 	MCP2200_DIR_OUT = 0x00, | 
 | 	MCP2200_DIR_IN  = 0x01, | 
 | }; | 
 |  | 
 | /* Altternative pin assignments */ | 
 | #define TXLED		2 | 
 | #define RXLED		3 | 
 | #define USBCFG		6 | 
 | #define SSPND		7 | 
 | #define MCP_NGPIO	8 | 
 |  | 
 | /* CMD to set or clear a GPIO output */ | 
 | struct mcp_set_clear_outputs { | 
 | 	u8 cmd; | 
 | 	u8 dummys1[10]; | 
 | 	u8 set_bmap; | 
 | 	u8 clear_bmap; | 
 | 	u8 dummys2[3]; | 
 | } __packed; | 
 |  | 
 | /* CMD to configure the IOs */ | 
 | struct mcp_configure { | 
 | 	u8 cmd; | 
 | 	u8 dummys1[3]; | 
 | 	u8 io_bmap; | 
 | 	u8 config_alt_pins; | 
 | 	u8 io_default_val_bmap; | 
 | 	u8 config_alt_options; | 
 | 	u8 baud_h; | 
 | 	u8 baud_l; | 
 | 	u8 dummys2[6]; | 
 | } __packed; | 
 |  | 
 | /* CMD to read all parameters */ | 
 | struct mcp_read_all { | 
 | 	u8 cmd; | 
 | 	u8 dummys[15]; | 
 | } __packed; | 
 |  | 
 | /* Response to the read all cmd */ | 
 | struct mcp_read_all_resp { | 
 | 	u8 cmd; | 
 | 	u8 eep_addr; | 
 | 	u8 dummy; | 
 | 	u8 eep_val; | 
 | 	u8 io_bmap; | 
 | 	u8 config_alt_pins; | 
 | 	u8 io_default_val_bmap; | 
 | 	u8 config_alt_options; | 
 | 	u8 baud_h; | 
 | 	u8 baud_l; | 
 | 	u8 io_port_val_bmap; | 
 | 	u8 dummys[5]; | 
 | } __packed; | 
 |  | 
 | struct mcp2200 { | 
 | 	struct hid_device *hdev; | 
 | 	struct mutex lock; | 
 | 	struct completion wait_in_report; | 
 | 	u8 gpio_dir; | 
 | 	u8 gpio_val; | 
 | 	u8 gpio_inval; | 
 | 	u8 baud_h; | 
 | 	u8 baud_l; | 
 | 	u8 config_alt_pins; | 
 | 	u8 gpio_reset_val; | 
 | 	u8 config_alt_options; | 
 | 	int status; | 
 | 	struct gpio_chip gc; | 
 | 	u8 hid_report[16]; | 
 | }; | 
 |  | 
 | /* this executes the READ_ALL cmd */ | 
 | static int mcp_cmd_read_all(struct mcp2200 *mcp) | 
 | { | 
 | 	struct mcp_read_all *read_all; | 
 | 	int len, t; | 
 |  | 
 | 	reinit_completion(&mcp->wait_in_report); | 
 |  | 
 | 	mutex_lock(&mcp->lock); | 
 |  | 
 | 	read_all = (struct mcp_read_all *) mcp->hid_report; | 
 | 	read_all->cmd = READ_ALL; | 
 | 	len = hid_hw_output_report(mcp->hdev, (u8 *) read_all, | 
 | 				   sizeof(struct mcp_read_all)); | 
 |  | 
 | 	mutex_unlock(&mcp->lock); | 
 |  | 
 | 	if (len != sizeof(struct mcp_read_all)) | 
 | 		return -EINVAL; | 
 |  | 
 | 	t = wait_for_completion_timeout(&mcp->wait_in_report, | 
 | 					msecs_to_jiffies(4000)); | 
 | 	if (!t) | 
 | 		return -ETIMEDOUT; | 
 |  | 
 | 	/* return status, negative value if wrong response was received */ | 
 | 	return mcp->status; | 
 | } | 
 |  | 
 | static void mcp_set_multiple(struct gpio_chip *gc, unsigned long *mask, | 
 | 			     unsigned long *bits) | 
 | { | 
 | 	struct mcp2200 *mcp = gpiochip_get_data(gc); | 
 | 	u8 value; | 
 | 	int status; | 
 | 	struct mcp_set_clear_outputs *cmd; | 
 |  | 
 | 	mutex_lock(&mcp->lock); | 
 | 	cmd = (struct mcp_set_clear_outputs *) mcp->hid_report; | 
 |  | 
 | 	value = mcp->gpio_val & ~*mask; | 
 | 	value |= (*mask & *bits); | 
 |  | 
 | 	cmd->cmd = SET_CLEAR_OUTPUTS; | 
 | 	cmd->set_bmap = value; | 
 | 	cmd->clear_bmap = ~(value); | 
 |  | 
 | 	status = hid_hw_output_report(mcp->hdev, (u8 *) cmd, | 
 | 		       sizeof(struct mcp_set_clear_outputs)); | 
 |  | 
 | 	if (status == sizeof(struct mcp_set_clear_outputs)) | 
 | 		mcp->gpio_val = value; | 
 |  | 
 | 	mutex_unlock(&mcp->lock); | 
 | } | 
 |  | 
 | static void mcp_set(struct gpio_chip *gc, unsigned int gpio_nr, int value) | 
 | { | 
 | 	unsigned long mask = 1 << gpio_nr; | 
 | 	unsigned long bmap_value = value << gpio_nr; | 
 |  | 
 | 	mcp_set_multiple(gc, &mask, &bmap_value); | 
 | } | 
 |  | 
 | static int mcp_get_multiple(struct gpio_chip *gc, unsigned long *mask, | 
 | 		unsigned long *bits) | 
 | { | 
 | 	u32 val; | 
 | 	struct mcp2200 *mcp = gpiochip_get_data(gc); | 
 | 	int status; | 
 |  | 
 | 	status = mcp_cmd_read_all(mcp); | 
 | 	if (status) | 
 | 		return status; | 
 |  | 
 | 	val = mcp->gpio_inval; | 
 | 	*bits = (val & *mask); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int mcp_get(struct gpio_chip *gc, unsigned int gpio_nr) | 
 | { | 
 | 	unsigned long mask = 0, bits = 0; | 
 |  | 
 | 	mask = (1 << gpio_nr); | 
 | 	mcp_get_multiple(gc, &mask, &bits); | 
 | 	return bits > 0; | 
 | } | 
 |  | 
 | static int mcp_get_direction(struct gpio_chip *gc, unsigned int gpio_nr) | 
 | { | 
 | 	struct mcp2200 *mcp = gpiochip_get_data(gc); | 
 |  | 
 | 	return (mcp->gpio_dir & (MCP2200_DIR_IN << gpio_nr)) | 
 | 		? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT; | 
 | } | 
 |  | 
 | static int mcp_set_direction(struct gpio_chip *gc, unsigned int gpio_nr, | 
 | 			     enum MCP_IO_DIR io_direction) | 
 | { | 
 | 	struct mcp2200 *mcp = gpiochip_get_data(gc); | 
 | 	struct mcp_configure *conf; | 
 | 	int status; | 
 | 	/* after the configure cmd we will need to set the outputs again */ | 
 | 	unsigned long mask = ~(mcp->gpio_dir); /* only set outputs */ | 
 | 	unsigned long bits = mcp->gpio_val; | 
 | 	/* Offsets of alternative pins in config_alt_pins, 0 is not used */ | 
 | 	u8 alt_pin_conf[8] = {SSPND, USBCFG, 0, 0, 0, 0, RXLED, TXLED}; | 
 | 	u8 config_alt_pins = mcp->config_alt_pins; | 
 |  | 
 | 	/* Read in the reset baudrate first, we need it later */ | 
 | 	status = mcp_cmd_read_all(mcp); | 
 | 	if (status != 0) | 
 | 		return status; | 
 |  | 
 | 	mutex_lock(&mcp->lock); | 
 | 	conf = (struct mcp_configure  *) mcp->hid_report; | 
 |  | 
 | 	/* configure will reset the chip! */ | 
 | 	conf->cmd = CONFIGURE; | 
 | 	conf->io_bmap = (mcp->gpio_dir & ~(1 << gpio_nr)) | 
 | 		| (io_direction << gpio_nr); | 
 | 	/* Don't overwrite the reset parameters */ | 
 | 	conf->baud_h = mcp->baud_h; | 
 | 	conf->baud_l = mcp->baud_l; | 
 | 	conf->config_alt_options = mcp->config_alt_options; | 
 | 	conf->io_default_val_bmap = mcp->gpio_reset_val; | 
 | 	/* Adjust alt. func if necessary */ | 
 | 	if (alt_pin_conf[gpio_nr]) | 
 | 		config_alt_pins &= ~(1 << alt_pin_conf[gpio_nr]); | 
 | 	conf->config_alt_pins = config_alt_pins; | 
 |  | 
 | 	status = hid_hw_output_report(mcp->hdev, (u8 *) conf, | 
 | 				      sizeof(struct mcp_set_clear_outputs)); | 
 |  | 
 | 	if (status == sizeof(struct mcp_set_clear_outputs)) { | 
 | 		mcp->gpio_dir = conf->io_bmap; | 
 | 		mcp->config_alt_pins = config_alt_pins; | 
 | 	} else { | 
 | 		mutex_unlock(&mcp->lock); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	mutex_unlock(&mcp->lock); | 
 |  | 
 | 	/* Configure CMD will clear all IOs -> rewrite them */ | 
 | 	mcp_set_multiple(gc, &mask, &bits); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int mcp_direction_input(struct gpio_chip *gc, unsigned int gpio_nr) | 
 | { | 
 | 	return mcp_set_direction(gc, gpio_nr, MCP2200_DIR_IN); | 
 | } | 
 |  | 
 | static int mcp_direction_output(struct gpio_chip *gc, unsigned int gpio_nr, | 
 | 				int value) | 
 | { | 
 | 	int ret; | 
 | 	unsigned long mask, bmap_value; | 
 |  | 
 | 	mask = 1 << gpio_nr; | 
 | 	bmap_value = value << gpio_nr; | 
 |  | 
 | 	ret = mcp_set_direction(gc, gpio_nr, MCP2200_DIR_OUT); | 
 | 	if (!ret) | 
 | 		mcp_set_multiple(gc, &mask, &bmap_value); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static const struct gpio_chip template_chip = { | 
 | 	.label			= "mcp2200", | 
 | 	.owner			= THIS_MODULE, | 
 | 	.get_direction		= mcp_get_direction, | 
 | 	.direction_input	= mcp_direction_input, | 
 | 	.direction_output	= mcp_direction_output, | 
 | 	.set			= mcp_set, | 
 | 	.set_multiple		= mcp_set_multiple, | 
 | 	.get			= mcp_get, | 
 | 	.get_multiple		= mcp_get_multiple, | 
 | 	.base			= -1, | 
 | 	.ngpio			= MCP_NGPIO, | 
 | 	.can_sleep		= true, | 
 | }; | 
 |  | 
 | /* | 
 |  * MCP2200 uses interrupt endpoint for input reports. This function | 
 |  * is called by HID layer when it receives i/p report from mcp2200, | 
 |  * which is actually a response to the previously sent command. | 
 |  */ | 
 | static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report, | 
 | 		u8 *data, int size) | 
 | { | 
 | 	struct mcp2200 *mcp = hid_get_drvdata(hdev); | 
 | 	struct mcp_read_all_resp *all_resp; | 
 |  | 
 | 	switch (data[0]) { | 
 | 	case READ_ALL: | 
 | 		all_resp = (struct mcp_read_all_resp *) data; | 
 | 		mcp->status = 0; | 
 | 		mcp->gpio_inval = all_resp->io_port_val_bmap; | 
 | 		mcp->baud_h = all_resp->baud_h; | 
 | 		mcp->baud_l = all_resp->baud_l; | 
 | 		mcp->gpio_reset_val = all_resp->io_default_val_bmap; | 
 | 		mcp->config_alt_pins = all_resp->config_alt_pins; | 
 | 		mcp->config_alt_options = all_resp->config_alt_options; | 
 | 		break; | 
 | 	default: | 
 | 		mcp->status = -EIO; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	complete(&mcp->wait_in_report); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int mcp2200_probe(struct hid_device *hdev, const struct hid_device_id *id) | 
 | { | 
 | 	int ret; | 
 | 	struct mcp2200 *mcp; | 
 |  | 
 | 	mcp = devm_kzalloc(&hdev->dev, sizeof(*mcp), GFP_KERNEL); | 
 | 	if (!mcp) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	ret = hid_parse(hdev); | 
 | 	if (ret) { | 
 | 		hid_err(hdev, "can't parse reports\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	ret = hid_hw_start(hdev, 0); | 
 | 	if (ret) { | 
 | 		hid_err(hdev, "can't start hardware\n"); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8, | 
 | 			hdev->version & 0xff, hdev->name, hdev->phys); | 
 |  | 
 | 	ret = hid_hw_open(hdev); | 
 | 	if (ret) { | 
 | 		hid_err(hdev, "can't open device\n"); | 
 | 		hid_hw_stop(hdev); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	mutex_init(&mcp->lock); | 
 | 	init_completion(&mcp->wait_in_report); | 
 | 	hid_set_drvdata(hdev, mcp); | 
 | 	mcp->hdev = hdev; | 
 |  | 
 | 	mcp->gc = template_chip; | 
 | 	mcp->gc.parent = &hdev->dev; | 
 |  | 
 | 	ret = devm_gpiochip_add_data(&hdev->dev, &mcp->gc, mcp); | 
 | 	if (ret < 0) { | 
 | 		hid_err(hdev, "Unable to register gpiochip\n"); | 
 | 		hid_hw_close(hdev); | 
 | 		hid_hw_stop(hdev); | 
 | 		return ret; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void mcp2200_remove(struct hid_device *hdev) | 
 | { | 
 | 	hid_hw_close(hdev); | 
 | 	hid_hw_stop(hdev); | 
 | } | 
 |  | 
 | static const struct hid_device_id mcp2200_devices[] = { | 
 | 	{ HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2200) }, | 
 | 	{ } | 
 | }; | 
 | MODULE_DEVICE_TABLE(hid, mcp2200_devices); | 
 |  | 
 | static struct hid_driver mcp2200_driver = { | 
 | 	.name		= "mcp2200", | 
 | 	.id_table	= mcp2200_devices, | 
 | 	.probe		= mcp2200_probe, | 
 | 	.remove		= mcp2200_remove, | 
 | 	.raw_event	= mcp2200_raw_event, | 
 | }; | 
 |  | 
 | /* Register with HID core */ | 
 | module_hid_driver(mcp2200_driver); | 
 |  | 
 | MODULE_AUTHOR("Johannes Roith <johannes@gnu-linux.rocks>"); | 
 | MODULE_DESCRIPTION("MCP2200 Microchip HID USB to GPIO bridge"); | 
 | MODULE_LICENSE("GPL"); |