1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * MCP2200 - Microchip USB to GPIO bridge
4 *
5 * Copyright (c) 2023, Johannes Roith <johannes@gnu-linux.rocks>
6 *
7 * Datasheet: https://ww1.microchip.com/downloads/en/DeviceDoc/22228A.pdf
8 * App Note for HID: https://ww1.microchip.com/downloads/en/DeviceDoc/93066A.pdf
9 */
10#include <linux/completion.h>
11#include <linux/delay.h>
12#include <linux/err.h>
13#include <linux/gpio/driver.h>
14#include <linux/hid.h>
15#include <linux/hidraw.h>
16#include <linux/module.h>
17#include <linux/mutex.h>
18#include "hid-ids.h"
19
20/* Commands codes in a raw output report */
21#define SET_CLEAR_OUTPUTS 0x08
22#define CONFIGURE 0x10
23#define READ_EE 0x20
24#define WRITE_EE 0x40
25#define READ_ALL 0x80
26
27/* MCP GPIO direction encoding */
28enum MCP_IO_DIR {
29 MCP2200_DIR_OUT = 0x00,
30 MCP2200_DIR_IN = 0x01,
31};
32
33/* Altternative pin assignments */
34#define TXLED 2
35#define RXLED 3
36#define USBCFG 6
37#define SSPND 7
38#define MCP_NGPIO 8
39
40/* CMD to set or clear a GPIO output */
41struct mcp_set_clear_outputs {
42 u8 cmd;
43 u8 dummys1[10];
44 u8 set_bmap;
45 u8 clear_bmap;
46 u8 dummys2[3];
47} __packed;
48
49/* CMD to configure the IOs */
50struct mcp_configure {
51 u8 cmd;
52 u8 dummys1[3];
53 u8 io_bmap;
54 u8 config_alt_pins;
55 u8 io_default_val_bmap;
56 u8 config_alt_options;
57 u8 baud_h;
58 u8 baud_l;
59 u8 dummys2[6];
60} __packed;
61
62/* CMD to read all parameters */
63struct mcp_read_all {
64 u8 cmd;
65 u8 dummys[15];
66} __packed;
67
68/* Response to the read all cmd */
69struct mcp_read_all_resp {
70 u8 cmd;
71 u8 eep_addr;
72 u8 dummy;
73 u8 eep_val;
74 u8 io_bmap;
75 u8 config_alt_pins;
76 u8 io_default_val_bmap;
77 u8 config_alt_options;
78 u8 baud_h;
79 u8 baud_l;
80 u8 io_port_val_bmap;
81 u8 dummys[5];
82} __packed;
83
84struct mcp2200 {
85 struct hid_device *hdev;
86 struct mutex lock;
87 struct completion wait_in_report;
88 u8 gpio_dir;
89 u8 gpio_val;
90 u8 gpio_inval;
91 u8 baud_h;
92 u8 baud_l;
93 u8 config_alt_pins;
94 u8 gpio_reset_val;
95 u8 config_alt_options;
96 int status;
97 struct gpio_chip gc;
98 u8 hid_report[16];
99};
100
101/* this executes the READ_ALL cmd */
102static int mcp_cmd_read_all(struct mcp2200 *mcp)
103{
104 struct mcp_read_all *read_all;
105 int len, t;
106
107 reinit_completion(x: &mcp->wait_in_report);
108
109 mutex_lock(&mcp->lock);
110
111 read_all = (struct mcp_read_all *) mcp->hid_report;
112 read_all->cmd = READ_ALL;
113 len = hid_hw_output_report(hdev: mcp->hdev, buf: (u8 *) read_all,
114 len: sizeof(struct mcp_read_all));
115
116 mutex_unlock(lock: &mcp->lock);
117
118 if (len != sizeof(struct mcp_read_all))
119 return -EINVAL;
120
121 t = wait_for_completion_timeout(x: &mcp->wait_in_report,
122 timeout: msecs_to_jiffies(m: 4000));
123 if (!t)
124 return -ETIMEDOUT;
125
126 /* return status, negative value if wrong response was received */
127 return mcp->status;
128}
129
130static void mcp_set_multiple(struct gpio_chip *gc, unsigned long *mask,
131 unsigned long *bits)
132{
133 struct mcp2200 *mcp = gpiochip_get_data(gc);
134 u8 value;
135 int status;
136 struct mcp_set_clear_outputs *cmd;
137
138 mutex_lock(&mcp->lock);
139 cmd = (struct mcp_set_clear_outputs *) mcp->hid_report;
140
141 value = mcp->gpio_val & ~*mask;
142 value |= (*mask & *bits);
143
144 cmd->cmd = SET_CLEAR_OUTPUTS;
145 cmd->set_bmap = value;
146 cmd->clear_bmap = ~(value);
147
148 status = hid_hw_output_report(hdev: mcp->hdev, buf: (u8 *) cmd,
149 len: sizeof(struct mcp_set_clear_outputs));
150
151 if (status == sizeof(struct mcp_set_clear_outputs))
152 mcp->gpio_val = value;
153
154 mutex_unlock(lock: &mcp->lock);
155}
156
157static void mcp_set(struct gpio_chip *gc, unsigned int gpio_nr, int value)
158{
159 unsigned long mask = 1 << gpio_nr;
160 unsigned long bmap_value = value << gpio_nr;
161
162 mcp_set_multiple(gc, mask: &mask, bits: &bmap_value);
163}
164
165static int mcp_get_multiple(struct gpio_chip *gc, unsigned long *mask,
166 unsigned long *bits)
167{
168 u32 val;
169 struct mcp2200 *mcp = gpiochip_get_data(gc);
170 int status;
171
172 status = mcp_cmd_read_all(mcp);
173 if (status)
174 return status;
175
176 val = mcp->gpio_inval;
177 *bits = (val & *mask);
178 return 0;
179}
180
181static int mcp_get(struct gpio_chip *gc, unsigned int gpio_nr)
182{
183 unsigned long mask = 0, bits = 0;
184
185 mask = (1 << gpio_nr);
186 mcp_get_multiple(gc, mask: &mask, bits: &bits);
187 return bits > 0;
188}
189
190static int mcp_get_direction(struct gpio_chip *gc, unsigned int gpio_nr)
191{
192 struct mcp2200 *mcp = gpiochip_get_data(gc);
193
194 return (mcp->gpio_dir & (MCP2200_DIR_IN << gpio_nr))
195 ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
196}
197
198static int mcp_set_direction(struct gpio_chip *gc, unsigned int gpio_nr,
199 enum MCP_IO_DIR io_direction)
200{
201 struct mcp2200 *mcp = gpiochip_get_data(gc);
202 struct mcp_configure *conf;
203 int status;
204 /* after the configure cmd we will need to set the outputs again */
205 unsigned long mask = ~(mcp->gpio_dir); /* only set outputs */
206 unsigned long bits = mcp->gpio_val;
207 /* Offsets of alternative pins in config_alt_pins, 0 is not used */
208 u8 alt_pin_conf[8] = {SSPND, USBCFG, 0, 0, 0, 0, RXLED, TXLED};
209 u8 config_alt_pins = mcp->config_alt_pins;
210
211 /* Read in the reset baudrate first, we need it later */
212 status = mcp_cmd_read_all(mcp);
213 if (status != 0)
214 return status;
215
216 mutex_lock(&mcp->lock);
217 conf = (struct mcp_configure *) mcp->hid_report;
218
219 /* configure will reset the chip! */
220 conf->cmd = CONFIGURE;
221 conf->io_bmap = (mcp->gpio_dir & ~(1 << gpio_nr))
222 | (io_direction << gpio_nr);
223 /* Don't overwrite the reset parameters */
224 conf->baud_h = mcp->baud_h;
225 conf->baud_l = mcp->baud_l;
226 conf->config_alt_options = mcp->config_alt_options;
227 conf->io_default_val_bmap = mcp->gpio_reset_val;
228 /* Adjust alt. func if necessary */
229 if (alt_pin_conf[gpio_nr])
230 config_alt_pins &= ~(1 << alt_pin_conf[gpio_nr]);
231 conf->config_alt_pins = config_alt_pins;
232
233 status = hid_hw_output_report(hdev: mcp->hdev, buf: (u8 *) conf,
234 len: sizeof(struct mcp_set_clear_outputs));
235
236 if (status == sizeof(struct mcp_set_clear_outputs)) {
237 mcp->gpio_dir = conf->io_bmap;
238 mcp->config_alt_pins = config_alt_pins;
239 } else {
240 mutex_unlock(lock: &mcp->lock);
241 return -EIO;
242 }
243
244 mutex_unlock(lock: &mcp->lock);
245
246 /* Configure CMD will clear all IOs -> rewrite them */
247 mcp_set_multiple(gc, mask: &mask, bits: &bits);
248 return 0;
249}
250
251static int mcp_direction_input(struct gpio_chip *gc, unsigned int gpio_nr)
252{
253 return mcp_set_direction(gc, gpio_nr, io_direction: MCP2200_DIR_IN);
254}
255
256static int mcp_direction_output(struct gpio_chip *gc, unsigned int gpio_nr,
257 int value)
258{
259 int ret;
260 unsigned long mask, bmap_value;
261
262 mask = 1 << gpio_nr;
263 bmap_value = value << gpio_nr;
264
265 ret = mcp_set_direction(gc, gpio_nr, io_direction: MCP2200_DIR_OUT);
266 if (!ret)
267 mcp_set_multiple(gc, mask: &mask, bits: &bmap_value);
268 return ret;
269}
270
271static const struct gpio_chip template_chip = {
272 .label = "mcp2200",
273 .owner = THIS_MODULE,
274 .get_direction = mcp_get_direction,
275 .direction_input = mcp_direction_input,
276 .direction_output = mcp_direction_output,
277 .set = mcp_set,
278 .set_multiple = mcp_set_multiple,
279 .get = mcp_get,
280 .get_multiple = mcp_get_multiple,
281 .base = -1,
282 .ngpio = MCP_NGPIO,
283 .can_sleep = true,
284};
285
286/*
287 * MCP2200 uses interrupt endpoint for input reports. This function
288 * is called by HID layer when it receives i/p report from mcp2200,
289 * which is actually a response to the previously sent command.
290 */
291static int mcp2200_raw_event(struct hid_device *hdev, struct hid_report *report,
292 u8 *data, int size)
293{
294 struct mcp2200 *mcp = hid_get_drvdata(hdev);
295 struct mcp_read_all_resp *all_resp;
296
297 switch (data[0]) {
298 case READ_ALL:
299 all_resp = (struct mcp_read_all_resp *) data;
300 mcp->status = 0;
301 mcp->gpio_inval = all_resp->io_port_val_bmap;
302 mcp->baud_h = all_resp->baud_h;
303 mcp->baud_l = all_resp->baud_l;
304 mcp->gpio_reset_val = all_resp->io_default_val_bmap;
305 mcp->config_alt_pins = all_resp->config_alt_pins;
306 mcp->config_alt_options = all_resp->config_alt_options;
307 break;
308 default:
309 mcp->status = -EIO;
310 break;
311 }
312
313 complete(&mcp->wait_in_report);
314 return 0;
315}
316
317static int mcp2200_probe(struct hid_device *hdev, const struct hid_device_id *id)
318{
319 int ret;
320 struct mcp2200 *mcp;
321
322 mcp = devm_kzalloc(dev: &hdev->dev, size: sizeof(*mcp), GFP_KERNEL);
323 if (!mcp)
324 return -ENOMEM;
325
326 ret = hid_parse(hdev);
327 if (ret) {
328 hid_err(hdev, "can't parse reports\n");
329 return ret;
330 }
331
332 ret = hid_hw_start(hdev, connect_mask: 0);
333 if (ret) {
334 hid_err(hdev, "can't start hardware\n");
335 return ret;
336 }
337
338 hid_info(hdev, "USB HID v%x.%02x Device [%s] on %s\n", hdev->version >> 8,
339 hdev->version & 0xff, hdev->name, hdev->phys);
340
341 ret = hid_hw_open(hdev);
342 if (ret) {
343 hid_err(hdev, "can't open device\n");
344 hid_hw_stop(hdev);
345 return ret;
346 }
347
348 mutex_init(&mcp->lock);
349 init_completion(x: &mcp->wait_in_report);
350 hid_set_drvdata(hdev, data: mcp);
351 mcp->hdev = hdev;
352
353 mcp->gc = template_chip;
354 mcp->gc.parent = &hdev->dev;
355
356 ret = devm_gpiochip_add_data(&hdev->dev, &mcp->gc, mcp);
357 if (ret < 0) {
358 hid_err(hdev, "Unable to register gpiochip\n");
359 hid_hw_close(hdev);
360 hid_hw_stop(hdev);
361 return ret;
362 }
363
364 return 0;
365}
366
367static void mcp2200_remove(struct hid_device *hdev)
368{
369 hid_hw_close(hdev);
370 hid_hw_stop(hdev);
371}
372
373static const struct hid_device_id mcp2200_devices[] = {
374 { HID_USB_DEVICE(USB_VENDOR_ID_MICROCHIP, USB_DEVICE_ID_MCP2200) },
375 { }
376};
377MODULE_DEVICE_TABLE(hid, mcp2200_devices);
378
379static struct hid_driver mcp2200_driver = {
380 .name = "mcp2200",
381 .id_table = mcp2200_devices,
382 .probe = mcp2200_probe,
383 .remove = mcp2200_remove,
384 .raw_event = mcp2200_raw_event,
385};
386
387/* Register with HID core */
388module_hid_driver(mcp2200_driver);
389
390MODULE_AUTHOR("Johannes Roith <johannes@gnu-linux.rocks>");
391MODULE_DESCRIPTION("MCP2200 Microchip HID USB to GPIO bridge");
392MODULE_LICENSE("GPL");
393

source code of linux/drivers/hid/hid-mcp2200.c