1 /*
2  *
3  * Copyright (c) 2021 metraTec GmbH
4  * Copyright (c) 2021 Peter Johanson
5  *
6  * SPDX-License-Identifier: Apache-2.0
7  */
8 
9 /**
10  * @file Driver for MPC23Sxx SPI-based GPIO driver.
11  */
12 
13 #include <errno.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/device.h>
16 #include <zephyr/sys/byteorder.h>
17 #include <zephyr/drivers/gpio.h>
18 #include <zephyr/drivers/spi.h>
19 
20 #include <zephyr/drivers/gpio/gpio_utils.h>
21 #include "gpio_mcp23xxx.h"
22 
23 #define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
24 #include <zephyr/logging/log.h>
25 LOG_MODULE_REGISTER(gpio_mcp23sxx);
26 
mcp23sxx_read_port_regs(const struct device * dev,uint8_t reg,uint16_t * buf)27 static int mcp23sxx_read_port_regs(const struct device *dev, uint8_t reg, uint16_t *buf)
28 {
29 	const struct mcp23xxx_config *config = dev->config;
30 	uint16_t port_data = 0;
31 	int ret;
32 
33 	uint8_t nread = (config->ngpios == 8) ? 1 : 2;
34 
35 	uint8_t addr = MCP23SXX_ADDR | MCP23SXX_READBIT;
36 	uint8_t buffer_tx[4] = { addr, reg, 0, 0 };
37 	uint8_t buffer_rx[4] = { 0 };
38 
39 	const struct spi_buf tx_buf = {
40 		.buf = buffer_tx,
41 		.len = 4,
42 	};
43 	const struct spi_buf_set tx = {
44 		.buffers = &tx_buf,
45 		.count = 1,
46 	};
47 	const struct spi_buf rx_buf = {
48 		.buf = buffer_rx,
49 		.len = 2 + nread,
50 	};
51 	const struct spi_buf_set rx = {
52 		.buffers = &rx_buf,
53 		.count = 1,
54 	};
55 
56 	ret = spi_transceive_dt(&config->bus.spi, &tx, &rx);
57 	if (ret < 0) {
58 		LOG_ERR("spi_transceive FAIL %d\n", ret);
59 		return ret;
60 	}
61 
62 	port_data = ((uint16_t)buffer_rx[3] << 8 | buffer_rx[2]);
63 
64 	*buf = sys_le16_to_cpu(port_data);
65 
66 	return 0;
67 }
68 
mcp23sxx_write_port_regs(const struct device * dev,uint8_t reg,uint16_t value)69 static int mcp23sxx_write_port_regs(const struct device *dev, uint8_t reg, uint16_t value)
70 {
71 	const struct mcp23xxx_config *config = dev->config;
72 	int ret;
73 
74 	uint8_t nwrite = (config->ngpios == 8) ? 1 : 2;
75 	uint16_t port_data = sys_cpu_to_le16(value);
76 	uint8_t port_a_data = port_data & 0xFF;
77 	uint8_t port_b_data = (port_data >> 8) & 0xFF;
78 
79 	port_data = sys_cpu_to_le16(value);
80 
81 	uint8_t addr = MCP23SXX_ADDR;
82 	uint8_t buffer_tx[4] = { addr, reg, port_a_data, port_b_data };
83 
84 	const struct spi_buf tx_buf[1] = {
85 		{
86 			.buf = buffer_tx,
87 			.len = nwrite + 2,
88 		}
89 	};
90 	const struct spi_buf_set tx = {
91 		.buffers = tx_buf,
92 		.count = ARRAY_SIZE(tx_buf),
93 	};
94 
95 	ret = spi_write_dt(&config->bus.spi, &tx);
96 	if (ret < 0) {
97 		LOG_ERR("spi_write FAIL %d\n", ret);
98 		return ret;
99 	}
100 
101 	return 0;
102 }
103 
mcp23sxx_bus_is_ready(const struct device * dev)104 static int mcp23sxx_bus_is_ready(const struct device *dev)
105 {
106 	const struct mcp23xxx_config *config = dev->config;
107 
108 	if (!spi_is_ready_dt(&config->bus.spi)) {
109 		LOG_ERR("SPI bus %s not ready", config->bus.spi.bus->name);
110 		return -ENODEV;
111 	}
112 
113 	return 0;
114 }
115 
116 #define GPIO_MCP23SXX_DEVICE(inst, num_gpios, open_drain, model)                              \
117 	static struct mcp23xxx_drv_data mcp##model##_##inst##_drvdata = {                     \
118 		/* Default for registers according to datasheet */                            \
119 		.reg_cache.iodir = 0xFFFF, .reg_cache.ipol = 0x0,   .reg_cache.gpinten = 0x0, \
120 		.reg_cache.defval = 0x0,   .reg_cache.intcon = 0x0, .reg_cache.iocon = 0x0,   \
121 		.reg_cache.gppu = 0x0,	   .reg_cache.intf = 0x0,   .reg_cache.intcap = 0x0,  \
122 		.reg_cache.gpio = 0x0,	   .reg_cache.olat = 0x0,                             \
123 	};                                                                                    \
124 	static struct mcp23xxx_config mcp##model##_##inst##_config = {                        \
125 		.config = {					                              \
126 			.port_pin_mask = GPIO_PORT_PIN_MASK_FROM_DT_INST(inst),               \
127 		},						                              \
128 		.bus = {                                                                      \
129 			.spi = SPI_DT_SPEC_INST_GET(inst,                                     \
130 				SPI_OP_MODE_MASTER | SPI_MODE_CPOL |                          \
131 				SPI_MODE_CPHA | SPI_WORD_SET(8), 0)                           \
132 		},                                                                            \
133 		.gpio_int = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {0}),                   \
134 		.gpio_reset = GPIO_DT_SPEC_INST_GET_OR(inst, reset_gpios, {0}),               \
135 		.ngpios =  num_gpios,				                              \
136 		.is_open_drain = open_drain,                                                  \
137 		.read_fn = mcp23sxx_read_port_regs,                                           \
138 		.write_fn = mcp23sxx_write_port_regs,                                         \
139 		.bus_fn = mcp23sxx_bus_is_ready                                               \
140 	};                                                                                    \
141 	DEVICE_DT_INST_DEFINE(inst, gpio_mcp23xxx_init, NULL, &mcp##model##_##inst##_drvdata, \
142 			      &mcp##model##_##inst##_config, POST_KERNEL,                     \
143 			      CONFIG_GPIO_MCP23SXX_INIT_PRIORITY, &gpio_mcp23xxx_api_table);
144 
145 
146 #define DT_DRV_COMPAT microchip_mcp23s08
147 DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_MCP23SXX_DEVICE, 8, false, 23s08)
148 #undef DT_DRV_COMPAT
149 #define DT_DRV_COMPAT microchip_mcp23s09
150 DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_MCP23SXX_DEVICE, 8, true, 23s09)
151 #undef DT_DRV_COMPAT
152 #define DT_DRV_COMPAT microchip_mcp23s17
153 DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_MCP23SXX_DEVICE, 16, false, 23s17)
154 #undef DT_DRV_COMPAT
155 #define DT_DRV_COMPAT microchip_mcp23s18
156 DT_INST_FOREACH_STATUS_OKAY_VARGS(GPIO_MCP23SXX_DEVICE, 16, true, 23s18)
157 #undef DT_DRV_COMPAT
158