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