1 /*
2 *
3 * Copyright (c) 2021 metraTec GmbH
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 /**
9 * @file Driver for MPC23xxx I2C/SPI-based GPIO driver.
10 */
11
12 #include <errno.h>
13 #include <zephyr/kernel.h>
14 #include <zephyr/device.h>
15 #include <zephyr/sys/byteorder.h>
16 #include <zephyr/sys/util.h>
17 #include <zephyr/drivers/gpio.h>
18
19 #include <zephyr/drivers/gpio/gpio_utils.h>
20 #include "gpio_mcp23xxx.h"
21
22 #define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
23 #include <zephyr/logging/log.h>
24 LOG_MODULE_REGISTER(gpio_mcp23xxx);
25
26 #define MCP23XXX_RESET_TIME_US 1
27
28 /**
29 * @brief Reads given register from mcp23xxx.
30 *
31 * The registers of the mcp23x0x consist of one 8 bit port.
32 * The registers of the mcp23x1x consist of two 8 bit ports.
33 *
34 * @param dev The mcp23xxx device.
35 * @param reg The register to be read.
36 * @param buf The buffer to read data to.
37 * @return 0 if successful.
38 * Otherwise <0 will be returned.
39 */
read_port_regs(const struct device * dev,uint8_t reg,uint16_t * buf)40 static int read_port_regs(const struct device *dev, uint8_t reg, uint16_t *buf)
41 {
42 const struct mcp23xxx_config *config = dev->config;
43
44 if (config->ngpios == 16U) {
45 reg *= 2;
46 }
47
48 return config->read_fn(dev, reg, buf);
49 }
50
51 /**
52 * @brief Writes registers of the mcp23xxx.
53 *
54 * On the mcp23x08 one 8 bit port will be written.
55 * On the mcp23x17 two 8 bit ports will be written.
56 *
57 * @param dev The mcp23xxx device.
58 * @param reg Register to be written.
59 * @param buf The new register value.
60 *
61 * @return 0 if successful. Otherwise <0 will be returned.
62 */
write_port_regs(const struct device * dev,uint8_t reg,uint16_t value)63 static int write_port_regs(const struct device *dev, uint8_t reg, uint16_t value)
64 {
65 const struct mcp23xxx_config *config = dev->config;
66
67 if (config->ngpios == 16U) {
68 reg *= 2;
69 }
70
71 return config->write_fn(dev, reg, value);
72 }
73
74 /**
75 * @brief Writes to the IOCON register of the mcp23xxx.
76 *
77 * IOCON is the only register that is not 16 bits wide on 16-pin devices; instead, it is mirrored in
78 * two adjacent memory locations. Because the underlying `write_fn` always does a 16-bit write for
79 * 16-pin devices, make sure we write the same value to both IOCON locations.
80 *
81 * @param dev The mcp23xxx device.
82 * @param value the IOCON value to write
83 *
84 * @return 0 if successful. Otherwise <0 will be returned.
85 */
write_iocon(const struct device * dev,uint8_t value)86 static int write_iocon(const struct device *dev, uint8_t value)
87 {
88 struct mcp23xxx_drv_data *drv_data = dev->data;
89
90 uint16_t extended_value = value | (value << 8);
91 int ret = write_port_regs(dev, REG_IOCON, extended_value);
92
93 if (ret == 0) {
94 drv_data->reg_cache.iocon = extended_value;
95 }
96
97 return ret;
98 }
99
100 /**
101 * @brief Setup the pin direction.
102 *
103 * @param dev The mcp23xxx device.
104 * @param pin The pin number.
105 * @param flags Flags of pin or port.
106 * @return 0 if successful. Otherwise <0 will be returned.
107 */
setup_pin_dir(const struct device * dev,uint32_t pin,int flags)108 static int setup_pin_dir(const struct device *dev, uint32_t pin, int flags)
109 {
110 struct mcp23xxx_drv_data *drv_data = dev->data;
111 uint16_t dir = drv_data->reg_cache.iodir;
112 uint16_t output = drv_data->reg_cache.gpio;
113 int ret;
114
115 if ((flags & GPIO_OUTPUT) != 0U) {
116 if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0U) {
117 output |= BIT(pin);
118 } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0U) {
119 output &= ~BIT(pin);
120 }
121 dir &= ~BIT(pin);
122 } else {
123 dir |= BIT(pin);
124 }
125
126 ret = write_port_regs(dev, REG_GPIO, output);
127 if (ret != 0) {
128 return ret;
129 }
130
131 drv_data->reg_cache.gpio = output;
132
133 ret = write_port_regs(dev, REG_IODIR, dir);
134 if (ret == 0) {
135 drv_data->reg_cache.iodir = dir;
136 }
137
138 return ret;
139 }
140
141 /**
142 * @brief Setup pin pull up/pull down.
143 *
144 * @param dev The mcp23xxx device.
145 * @param pin The pin number.
146 * @param flags Flags of pin or port.
147 * @return 0 if successful. Otherwise <0 will be returned.
148 */
setup_pin_pull(const struct device * dev,uint32_t pin,int flags)149 static int setup_pin_pull(const struct device *dev, uint32_t pin, int flags)
150 {
151 struct mcp23xxx_drv_data *drv_data = dev->data;
152 uint16_t port;
153 int ret;
154
155 port = drv_data->reg_cache.gppu;
156
157 if ((flags & GPIO_PULL_DOWN) != 0U) {
158 return -ENOTSUP;
159 }
160
161 WRITE_BIT(port, pin, (flags & GPIO_PULL_UP) != 0);
162
163 ret = write_port_regs(dev, REG_GPPU, port);
164 if (ret == 0) {
165 drv_data->reg_cache.gppu = port;
166 }
167
168 return ret;
169 }
170
mcp23xxx_pin_cfg(const struct device * dev,gpio_pin_t pin,gpio_flags_t flags)171 static int mcp23xxx_pin_cfg(const struct device *dev, gpio_pin_t pin, gpio_flags_t flags)
172 {
173 struct mcp23xxx_drv_data *drv_data = dev->data;
174 const struct mcp23xxx_config *config = dev->config;
175 int ret;
176
177 if (k_is_in_isr()) {
178 return -EWOULDBLOCK;
179 }
180
181 k_sem_take(&drv_data->lock, K_FOREVER);
182
183 if ((bool)(flags & GPIO_SINGLE_ENDED) != config->is_open_drain ||
184 (bool)(flags & GPIO_LINE_OPEN_DRAIN) != config->is_open_drain) {
185 ret = -ENOTSUP;
186 goto done;
187 }
188
189 ret = setup_pin_dir(dev, pin, flags);
190 if (ret < 0) {
191 LOG_ERR("Error setting pin direction (%d)", ret);
192 goto done;
193 }
194
195 ret = setup_pin_pull(dev, pin, flags);
196 if (ret < 0) {
197 LOG_ERR("Error setting pin pull up/pull down (%d)", ret);
198 goto done;
199 }
200
201 done:
202 k_sem_give(&drv_data->lock);
203 return ret;
204 }
205
mcp23xxx_port_get_raw(const struct device * dev,uint32_t * value)206 static int mcp23xxx_port_get_raw(const struct device *dev, uint32_t *value)
207 {
208 struct mcp23xxx_drv_data *drv_data = dev->data;
209 uint16_t buf;
210 int ret;
211
212 if (k_is_in_isr()) {
213 return -EWOULDBLOCK;
214 }
215
216 k_sem_take(&drv_data->lock, K_FOREVER);
217
218 ret = read_port_regs(dev, REG_GPIO, &buf);
219 if (ret == 0) {
220 *value = buf;
221 }
222
223 k_sem_give(&drv_data->lock);
224 return ret;
225 }
226
mcp23xxx_port_set_masked_raw(const struct device * dev,uint32_t mask,uint32_t value)227 static int mcp23xxx_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value)
228 {
229 struct mcp23xxx_drv_data *drv_data = dev->data;
230 uint16_t buf;
231 int ret;
232
233 if (k_is_in_isr()) {
234 return -EWOULDBLOCK;
235 }
236
237 k_sem_take(&drv_data->lock, K_FOREVER);
238
239 buf = drv_data->reg_cache.gpio;
240 buf = (buf & ~mask) | (mask & value);
241
242 ret = write_port_regs(dev, REG_GPIO, buf);
243 if (ret == 0) {
244 drv_data->reg_cache.gpio = buf;
245 }
246
247 k_sem_give(&drv_data->lock);
248 return ret;
249 }
250
mcp23xxx_port_set_bits_raw(const struct device * dev,uint32_t mask)251 static int mcp23xxx_port_set_bits_raw(const struct device *dev, uint32_t mask)
252 {
253 return mcp23xxx_port_set_masked_raw(dev, mask, mask);
254 }
255
mcp23xxx_port_clear_bits_raw(const struct device * dev,uint32_t mask)256 static int mcp23xxx_port_clear_bits_raw(const struct device *dev, uint32_t mask)
257 {
258 return mcp23xxx_port_set_masked_raw(dev, mask, 0);
259 }
260
mcp23xxx_port_toggle_bits(const struct device * dev,uint32_t mask)261 static int mcp23xxx_port_toggle_bits(const struct device *dev, uint32_t mask)
262 {
263 struct mcp23xxx_drv_data *drv_data = dev->data;
264 uint16_t buf;
265 int ret;
266
267 if (k_is_in_isr()) {
268 return -EWOULDBLOCK;
269 }
270
271 k_sem_take(&drv_data->lock, K_FOREVER);
272
273 buf = drv_data->reg_cache.gpio;
274 buf ^= mask;
275
276 ret = write_port_regs(dev, REG_GPIO, buf);
277 if (ret == 0) {
278 drv_data->reg_cache.gpio = buf;
279 }
280
281 k_sem_give(&drv_data->lock);
282
283 return ret;
284 }
285
mcp23xxx_pin_interrupt_configure(const struct device * dev,gpio_pin_t pin,enum gpio_int_mode mode,enum gpio_int_trig trig)286 static int mcp23xxx_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin,
287 enum gpio_int_mode mode, enum gpio_int_trig trig)
288 {
289 struct mcp23xxx_drv_data *drv_data = dev->data;
290 const struct mcp23xxx_config *config = dev->config;
291
292 if (!config->gpio_int.port) {
293 return -ENOTSUP;
294 }
295
296 if (k_is_in_isr()) {
297 return -EWOULDBLOCK;
298 }
299
300 k_sem_take(&drv_data->lock, K_FOREVER);
301
302 uint16_t gpinten = drv_data->reg_cache.gpinten;
303 uint16_t defval = drv_data->reg_cache.defval;
304 uint16_t intcon = drv_data->reg_cache.intcon;
305
306 int ret;
307
308 switch (mode) {
309 case GPIO_INT_MODE_DISABLED:
310 gpinten &= ~BIT(pin);
311 break;
312
313 case GPIO_INT_MODE_LEVEL:
314 gpinten |= BIT(pin);
315 intcon |= BIT(pin);
316
317 switch (trig) {
318 case GPIO_INT_TRIG_LOW:
319 defval |= BIT(pin);
320 break;
321 case GPIO_INT_TRIG_HIGH:
322 defval &= ~BIT(pin);
323 break;
324 case GPIO_INT_TRIG_BOTH:
325 /* can't happen */
326 ret = -ENOTSUP;
327 goto done;
328 default:
329 ret = -EINVAL;
330 goto done;
331 }
332 break;
333
334 case GPIO_INT_MODE_EDGE:
335 gpinten |= BIT(pin);
336 intcon &= ~BIT(pin);
337
338 switch (trig) {
339 case GPIO_INT_TRIG_LOW:
340 drv_data->rising_edge_ints &= ~BIT(pin);
341 drv_data->falling_edge_ints |= BIT(pin);
342 break;
343 case GPIO_INT_TRIG_HIGH:
344 drv_data->rising_edge_ints |= BIT(pin);
345 drv_data->falling_edge_ints &= ~BIT(pin);
346 break;
347 case GPIO_INT_TRIG_BOTH:
348 drv_data->rising_edge_ints |= BIT(pin);
349 drv_data->falling_edge_ints |= BIT(pin);
350 break;
351 default:
352 ret = -EINVAL;
353 goto done;
354 }
355 break;
356 }
357
358 ret = write_port_regs(dev, REG_GPINTEN, gpinten);
359 if (ret != 0) {
360 goto done;
361 }
362 drv_data->reg_cache.gpinten = gpinten;
363
364 ret = write_port_regs(dev, REG_DEFVAL, defval);
365 if (ret != 0) {
366 goto done;
367 }
368 drv_data->reg_cache.defval = defval;
369
370 ret = write_port_regs(dev, REG_INTCON, intcon);
371 if (ret != 0) {
372 goto done;
373 }
374 drv_data->reg_cache.intcon = intcon;
375
376 done:
377 k_sem_give(&drv_data->lock);
378
379 return ret;
380 }
381
mcp23xxx_manage_callback(const struct device * dev,struct gpio_callback * callback,bool set)382 static int mcp23xxx_manage_callback(const struct device *dev, struct gpio_callback *callback,
383 bool set)
384 {
385 struct mcp23xxx_drv_data *drv_data = dev->data;
386 const struct mcp23xxx_config *config = dev->config;
387
388 if (!config->gpio_int.port) {
389 return -ENOTSUP;
390 }
391
392 if (k_is_in_isr()) {
393 return -EWOULDBLOCK;
394 }
395
396 k_sem_take(&drv_data->lock, K_FOREVER);
397
398 int ret = gpio_manage_callback(&drv_data->callbacks, callback, set);
399
400 k_sem_give(&drv_data->lock);
401
402 return ret;
403 }
404
mcp23xxx_work_handler(struct k_work * work)405 static void mcp23xxx_work_handler(struct k_work *work)
406 {
407 struct mcp23xxx_drv_data *drv_data = CONTAINER_OF(work, struct mcp23xxx_drv_data, work);
408 const struct device *dev = drv_data->dev;
409
410 int ret;
411
412 k_sem_take(&drv_data->lock, K_FOREVER);
413
414 uint16_t intf;
415
416 ret = read_port_regs(dev, REG_INTF, &intf);
417 if (ret != 0) {
418 LOG_ERR("Failed to read INTF");
419 goto fail;
420 }
421
422 if (!intf) {
423 /* Probable causes:
424 * - REG_GPIO was read from somewhere else before the interrupt handler had a chance
425 * to run
426 * - Even though the datasheet says differently, reading INTCAP while a level
427 * interrupt is active briefly (~2ns) causes the interrupt line to go high and
428 * low again. This causes a second ISR to be scheduled, which then won't
429 * find any active interrupts if the callback has disabled the level interrupt.
430 */
431 LOG_ERR("Spurious interrupt");
432 goto fail;
433 }
434
435 uint16_t intcap;
436
437 /* Read INTCAP to acknowledge the interrupt */
438 ret = read_port_regs(dev, REG_INTCAP, &intcap);
439 if (ret != 0) {
440 LOG_ERR("Failed to read INTCAP");
441 goto fail;
442 }
443
444 /* mcp23xxx does not support single-edge interrupts in hardware, filter them out manually */
445 uint16_t level_ints = drv_data->reg_cache.gpinten & drv_data->reg_cache.intcon;
446
447 intf &= level_ints | (intcap & drv_data->rising_edge_ints) |
448 (~intcap & drv_data->falling_edge_ints);
449
450 k_sem_give(&drv_data->lock);
451 gpio_fire_callbacks(&drv_data->callbacks, dev, intf);
452 return;
453
454 fail:
455 k_sem_give(&drv_data->lock);
456 }
457
mcp23xxx_int_gpio_handler(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)458 static void mcp23xxx_int_gpio_handler(const struct device *port, struct gpio_callback *cb,
459 gpio_port_pins_t pins)
460 {
461 struct mcp23xxx_drv_data *drv_data =
462 CONTAINER_OF(cb, struct mcp23xxx_drv_data, int_gpio_cb);
463
464 k_work_submit(&drv_data->work);
465 }
466
467 DEVICE_API(gpio, gpio_mcp23xxx_api_table) = {
468 .pin_configure = mcp23xxx_pin_cfg,
469 .port_get_raw = mcp23xxx_port_get_raw,
470 .port_set_masked_raw = mcp23xxx_port_set_masked_raw,
471 .port_set_bits_raw = mcp23xxx_port_set_bits_raw,
472 .port_clear_bits_raw = mcp23xxx_port_clear_bits_raw,
473 .port_toggle_bits = mcp23xxx_port_toggle_bits,
474 .pin_interrupt_configure = mcp23xxx_pin_interrupt_configure,
475 .manage_callback = mcp23xxx_manage_callback,
476 };
477
478 /**
479 * @brief Initialization function of MCP23XXX
480 *
481 * @param dev Device struct.
482 * @return 0 if successful. Otherwise <0 is returned.
483 */
gpio_mcp23xxx_init(const struct device * dev)484 int gpio_mcp23xxx_init(const struct device *dev)
485 {
486 const struct mcp23xxx_config *config = dev->config;
487 struct mcp23xxx_drv_data *drv_data = dev->data;
488 int err;
489
490 if (config->ngpios != 8U && config->ngpios != 16U) {
491 LOG_ERR("Invalid value ngpios=%u. Expected 8 or 16!", config->ngpios);
492 return -EINVAL;
493 }
494
495 err = config->bus_fn(dev);
496 if (err < 0) {
497 return err;
498 }
499
500 k_sem_init(&drv_data->lock, 0, 1);
501
502 /* If the RESET line is available, pulse it. */
503 if (config->gpio_reset.port) {
504 err = gpio_pin_configure_dt(&config->gpio_reset, GPIO_OUTPUT_ACTIVE);
505 if (err != 0) {
506 LOG_ERR("Failed to configure RESET line: %d", err);
507 return -EIO;
508 }
509
510 k_usleep(MCP23XXX_RESET_TIME_US);
511
512 err = gpio_pin_set_dt(&config->gpio_reset, 0);
513 if (err != 0) {
514 LOG_ERR("Failed to deactivate RESET line: %d", err);
515 return -EIO;
516 }
517 }
518
519 /* If the INT line is available, configure the callback for it. */
520 if (config->gpio_int.port) {
521 if (config->ngpios == 16) {
522 /* send both ports' interrupts through one IRQ pin */
523 err = write_iocon(dev, REG_IOCON_MIRROR);
524
525 if (err != 0) {
526 LOG_ERR("Failed to enable mirrored IRQ pins: %d", err);
527 return -EIO;
528 }
529 }
530
531 if (!gpio_is_ready_dt(&config->gpio_int)) {
532 LOG_ERR("INT port is not ready");
533 return -ENODEV;
534 }
535
536 drv_data->dev = dev;
537 k_work_init(&drv_data->work, mcp23xxx_work_handler);
538
539 err = gpio_pin_configure_dt(&config->gpio_int, GPIO_INPUT);
540 if (err != 0) {
541 LOG_ERR("Failed to configure INT line: %d", err);
542 return -EIO;
543 }
544
545 gpio_init_callback(&drv_data->int_gpio_cb, mcp23xxx_int_gpio_handler,
546 BIT(config->gpio_int.pin));
547 err = gpio_add_callback(config->gpio_int.port, &drv_data->int_gpio_cb);
548 if (err != 0) {
549 LOG_ERR("Failed to add INT callback: %d", err);
550 return -EIO;
551 }
552
553 err = gpio_pin_interrupt_configure_dt(&config->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
554 if (err != 0) {
555 LOG_ERR("Failed to configure INT interrupt: %d", err);
556 return -EIO;
557 }
558 }
559
560 k_sem_give(&drv_data->lock);
561
562 return 0;
563 }
564