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 int ret;
175
176 if (k_is_in_isr()) {
177 return -EWOULDBLOCK;
178 }
179
180 k_sem_take(&drv_data->lock, K_FOREVER);
181
182 if ((flags & GPIO_SINGLE_ENDED) != 0U) {
183 ret = -ENOTSUP;
184 goto done;
185 }
186
187 ret = setup_pin_dir(dev, pin, flags);
188 if (ret < 0) {
189 LOG_ERR("Error setting pin direction (%d)", ret);
190 goto done;
191 }
192
193 ret = setup_pin_pull(dev, pin, flags);
194 if (ret < 0) {
195 LOG_ERR("Error setting pin pull up/pull down (%d)", ret);
196 goto done;
197 }
198
199 done:
200 k_sem_give(&drv_data->lock);
201 return ret;
202 }
203
mcp23xxx_port_get_raw(const struct device * dev,uint32_t * value)204 static int mcp23xxx_port_get_raw(const struct device *dev, uint32_t *value)
205 {
206 struct mcp23xxx_drv_data *drv_data = dev->data;
207 uint16_t buf;
208 int ret;
209
210 if (k_is_in_isr()) {
211 return -EWOULDBLOCK;
212 }
213
214 k_sem_take(&drv_data->lock, K_FOREVER);
215
216 ret = read_port_regs(dev, REG_GPIO, &buf);
217 if (ret == 0) {
218 *value = buf;
219 }
220
221 k_sem_give(&drv_data->lock);
222 return ret;
223 }
224
mcp23xxx_port_set_masked_raw(const struct device * dev,uint32_t mask,uint32_t value)225 static int mcp23xxx_port_set_masked_raw(const struct device *dev, uint32_t mask, uint32_t value)
226 {
227 struct mcp23xxx_drv_data *drv_data = dev->data;
228 uint16_t buf;
229 int ret;
230
231 if (k_is_in_isr()) {
232 return -EWOULDBLOCK;
233 }
234
235 k_sem_take(&drv_data->lock, K_FOREVER);
236
237 buf = drv_data->reg_cache.gpio;
238 buf = (buf & ~mask) | (mask & value);
239
240 ret = write_port_regs(dev, REG_GPIO, buf);
241 if (ret == 0) {
242 drv_data->reg_cache.gpio = buf;
243 }
244
245 k_sem_give(&drv_data->lock);
246 return ret;
247 }
248
mcp23xxx_port_set_bits_raw(const struct device * dev,uint32_t mask)249 static int mcp23xxx_port_set_bits_raw(const struct device *dev, uint32_t mask)
250 {
251 return mcp23xxx_port_set_masked_raw(dev, mask, mask);
252 }
253
mcp23xxx_port_clear_bits_raw(const struct device * dev,uint32_t mask)254 static int mcp23xxx_port_clear_bits_raw(const struct device *dev, uint32_t mask)
255 {
256 return mcp23xxx_port_set_masked_raw(dev, mask, 0);
257 }
258
mcp23xxx_port_toggle_bits(const struct device * dev,uint32_t mask)259 static int mcp23xxx_port_toggle_bits(const struct device *dev, uint32_t mask)
260 {
261 struct mcp23xxx_drv_data *drv_data = dev->data;
262 uint16_t buf;
263 int ret;
264
265 if (k_is_in_isr()) {
266 return -EWOULDBLOCK;
267 }
268
269 k_sem_take(&drv_data->lock, K_FOREVER);
270
271 buf = drv_data->reg_cache.gpio;
272 buf ^= mask;
273
274 ret = write_port_regs(dev, REG_GPIO, buf);
275 if (ret == 0) {
276 drv_data->reg_cache.gpio = buf;
277 }
278
279 k_sem_give(&drv_data->lock);
280
281 return ret;
282 }
283
mcp23xxx_pin_interrupt_configure(const struct device * dev,gpio_pin_t pin,enum gpio_int_mode mode,enum gpio_int_trig trig)284 static int mcp23xxx_pin_interrupt_configure(const struct device *dev, gpio_pin_t pin,
285 enum gpio_int_mode mode, enum gpio_int_trig trig)
286 {
287 struct mcp23xxx_drv_data *drv_data = dev->data;
288 const struct mcp23xxx_config *config = dev->config;
289
290 if (!config->gpio_int.port) {
291 return -ENOTSUP;
292 }
293
294 if (k_is_in_isr()) {
295 return -EWOULDBLOCK;
296 }
297
298 k_sem_take(&drv_data->lock, K_FOREVER);
299
300 uint16_t gpinten = drv_data->reg_cache.gpinten;
301 uint16_t defval = drv_data->reg_cache.defval;
302 uint16_t intcon = drv_data->reg_cache.intcon;
303
304 int ret;
305
306 switch (mode) {
307 case GPIO_INT_MODE_DISABLED:
308 gpinten &= ~BIT(pin);
309 break;
310
311 case GPIO_INT_MODE_LEVEL:
312 gpinten |= BIT(pin);
313 intcon |= BIT(pin);
314
315 switch (trig) {
316 case GPIO_INT_TRIG_LOW:
317 defval |= BIT(pin);
318 break;
319 case GPIO_INT_TRIG_HIGH:
320 defval &= ~BIT(pin);
321 break;
322 case GPIO_INT_TRIG_BOTH:
323 /* can't happen */
324 ret = -ENOTSUP;
325 goto done;
326 default:
327 ret = -EINVAL;
328 goto done;
329 }
330 break;
331
332 case GPIO_INT_MODE_EDGE:
333 gpinten |= BIT(pin);
334 intcon &= ~BIT(pin);
335
336 switch (trig) {
337 case GPIO_INT_TRIG_LOW:
338 drv_data->rising_edge_ints &= ~BIT(pin);
339 drv_data->falling_edge_ints |= BIT(pin);
340 break;
341 case GPIO_INT_TRIG_HIGH:
342 drv_data->rising_edge_ints |= BIT(pin);
343 drv_data->falling_edge_ints &= ~BIT(pin);
344 break;
345 case GPIO_INT_TRIG_BOTH:
346 drv_data->rising_edge_ints |= BIT(pin);
347 drv_data->falling_edge_ints |= BIT(pin);
348 break;
349 default:
350 ret = -EINVAL;
351 goto done;
352 }
353 break;
354 }
355
356 ret = write_port_regs(dev, REG_GPINTEN, gpinten);
357 if (ret != 0) {
358 goto done;
359 }
360 drv_data->reg_cache.gpinten = gpinten;
361
362 ret = write_port_regs(dev, REG_DEFVAL, defval);
363 if (ret != 0) {
364 goto done;
365 }
366 drv_data->reg_cache.defval = defval;
367
368 ret = write_port_regs(dev, REG_INTCON, intcon);
369 if (ret != 0) {
370 goto done;
371 }
372 drv_data->reg_cache.intcon = intcon;
373
374 done:
375 k_sem_give(&drv_data->lock);
376
377 return ret;
378 }
379
mcp23xxx_manage_callback(const struct device * dev,struct gpio_callback * callback,bool set)380 static int mcp23xxx_manage_callback(const struct device *dev, struct gpio_callback *callback,
381 bool set)
382 {
383 struct mcp23xxx_drv_data *drv_data = dev->data;
384 const struct mcp23xxx_config *config = dev->config;
385
386 if (!config->gpio_int.port) {
387 return -ENOTSUP;
388 }
389
390 if (k_is_in_isr()) {
391 return -EWOULDBLOCK;
392 }
393
394 k_sem_take(&drv_data->lock, K_FOREVER);
395
396 int ret = gpio_manage_callback(&drv_data->callbacks, callback, set);
397
398 k_sem_give(&drv_data->lock);
399
400 return ret;
401 }
402
mcp23xxx_work_handler(struct k_work * work)403 static void mcp23xxx_work_handler(struct k_work *work)
404 {
405 struct mcp23xxx_drv_data *drv_data = CONTAINER_OF(work, struct mcp23xxx_drv_data, work);
406 const struct device *dev = drv_data->dev;
407
408 int ret;
409
410 k_sem_take(&drv_data->lock, K_FOREVER);
411
412 uint16_t intf;
413
414 ret = read_port_regs(dev, REG_INTF, &intf);
415 if (ret != 0) {
416 LOG_ERR("Failed to read INTF");
417 goto fail;
418 }
419
420 if (!intf) {
421 /* Probable causes:
422 * - REG_GPIO was read from somewhere else before the interrupt handler had a chance
423 * to run
424 * - Even though the datasheet says differently, reading INTCAP while a level
425 * interrupt is active briefly (~2ns) causes the interrupt line to go high and
426 * low again. This causes a second ISR to be scheduled, which then won't
427 * find any active interrupts if the callback has disabled the level interrupt.
428 */
429 LOG_ERR("Spurious interrupt");
430 goto fail;
431 }
432
433 uint16_t intcap;
434
435 /* Read INTCAP to acknowledge the interrupt */
436 ret = read_port_regs(dev, REG_INTCAP, &intcap);
437 if (ret != 0) {
438 LOG_ERR("Failed to read INTCAP");
439 goto fail;
440 }
441
442 /* mcp23xxx does not support single-edge interrupts in hardware, filter them out manually */
443 uint16_t level_ints = drv_data->reg_cache.gpinten & drv_data->reg_cache.intcon;
444
445 intf &= level_ints | (intcap & drv_data->rising_edge_ints) |
446 (~intcap & drv_data->falling_edge_ints);
447
448 k_sem_give(&drv_data->lock);
449 gpio_fire_callbacks(&drv_data->callbacks, dev, intf);
450 return;
451
452 fail:
453 k_sem_give(&drv_data->lock);
454 }
455
mcp23xxx_int_gpio_handler(const struct device * port,struct gpio_callback * cb,gpio_port_pins_t pins)456 static void mcp23xxx_int_gpio_handler(const struct device *port, struct gpio_callback *cb,
457 gpio_port_pins_t pins)
458 {
459 struct mcp23xxx_drv_data *drv_data =
460 CONTAINER_OF(cb, struct mcp23xxx_drv_data, int_gpio_cb);
461
462 k_work_submit(&drv_data->work);
463 }
464
465 const struct gpio_driver_api gpio_mcp23xxx_api_table = {
466 .pin_configure = mcp23xxx_pin_cfg,
467 .port_get_raw = mcp23xxx_port_get_raw,
468 .port_set_masked_raw = mcp23xxx_port_set_masked_raw,
469 .port_set_bits_raw = mcp23xxx_port_set_bits_raw,
470 .port_clear_bits_raw = mcp23xxx_port_clear_bits_raw,
471 .port_toggle_bits = mcp23xxx_port_toggle_bits,
472 .pin_interrupt_configure = mcp23xxx_pin_interrupt_configure,
473 .manage_callback = mcp23xxx_manage_callback,
474 };
475
476 /**
477 * @brief Initialization function of MCP23XXX
478 *
479 * @param dev Device struct.
480 * @return 0 if successful. Otherwise <0 is returned.
481 */
gpio_mcp23xxx_init(const struct device * dev)482 int gpio_mcp23xxx_init(const struct device *dev)
483 {
484 const struct mcp23xxx_config *config = dev->config;
485 struct mcp23xxx_drv_data *drv_data = dev->data;
486 int err;
487
488 if (config->ngpios != 8U && config->ngpios != 16U) {
489 LOG_ERR("Invalid value ngpios=%u. Expected 8 or 16!", config->ngpios);
490 return -EINVAL;
491 }
492
493 err = config->bus_fn(dev);
494 if (err < 0) {
495 return err;
496 }
497
498 k_sem_init(&drv_data->lock, 0, 1);
499
500 /* If the RESET line is available, pulse it. */
501 if (config->gpio_reset.port) {
502 err = gpio_pin_configure_dt(&config->gpio_reset, GPIO_OUTPUT_ACTIVE);
503 if (err != 0) {
504 LOG_ERR("Failed to configure RESET line: %d", err);
505 return -EIO;
506 }
507
508 k_usleep(MCP23XXX_RESET_TIME_US);
509
510 err = gpio_pin_set_dt(&config->gpio_reset, 0);
511 if (err != 0) {
512 LOG_ERR("Failed to deactivate RESET line: %d", err);
513 return -EIO;
514 }
515 }
516
517 /* If the INT line is available, configure the callback for it. */
518 if (config->gpio_int.port) {
519 if (config->ngpios == 16) {
520 /* send both ports' interrupts through one IRQ pin */
521 err = write_iocon(dev, REG_IOCON_MIRROR);
522
523 if (err != 0) {
524 LOG_ERR("Failed to enable mirrored IRQ pins: %d", err);
525 return -EIO;
526 }
527 }
528
529 if (!gpio_is_ready_dt(&config->gpio_int)) {
530 LOG_ERR("INT port is not ready");
531 return -ENODEV;
532 }
533
534 drv_data->dev = dev;
535 k_work_init(&drv_data->work, mcp23xxx_work_handler);
536
537 err = gpio_pin_configure_dt(&config->gpio_int, GPIO_INPUT);
538 if (err != 0) {
539 LOG_ERR("Failed to configure INT line: %d", err);
540 return -EIO;
541 }
542
543 gpio_init_callback(&drv_data->int_gpio_cb, mcp23xxx_int_gpio_handler,
544 BIT(config->gpio_int.pin));
545 err = gpio_add_callback(config->gpio_int.port, &drv_data->int_gpio_cb);
546 if (err != 0) {
547 LOG_ERR("Failed to add INT callback: %d", err);
548 return -EIO;
549 }
550
551 err = gpio_pin_interrupt_configure_dt(&config->gpio_int, GPIO_INT_EDGE_TO_ACTIVE);
552 if (err != 0) {
553 LOG_ERR("Failed to configure INT interrupt: %d", err);
554 return -EIO;
555 }
556 }
557
558 k_sem_give(&drv_data->lock);
559
560 return 0;
561 }
562