1 /*
2 * Copyright (c) 2020 Geanix ApS
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #define DT_DRV_COMPAT microchip_mcp23s17
8
9 #include <errno.h>
10
11 #include <zephyr/kernel.h>
12 #include <zephyr/device.h>
13 #include <zephyr/init.h>
14 #include <zephyr/sys/byteorder.h>
15 #include <zephyr/drivers/gpio.h>
16 #include <zephyr/drivers/spi.h>
17
18 #include <zephyr/drivers/gpio/gpio_utils.h>
19
20 #define LOG_LEVEL CONFIG_GPIO_LOG_LEVEL
21 #include <zephyr/logging/log.h>
22 LOG_MODULE_REGISTER(gpio_mcp23s17);
23
24 /* Register definitions */
25 #define REG_IODIR_PORTA 0x00
26 #define REG_IODIR_PORTB 0x01
27 #define REG_IPOL_PORTA 0x02
28 #define REG_IPOL_PORTB 0x03
29 #define REG_GPINTEN_PORTA 0x04
30 #define REG_GPINTEN_PORTB 0x05
31 #define REG_DEFVAL_PORTA 0x06
32 #define REG_DEFVAL_PORTB 0x07
33 #define REG_INTCON_PORTA 0x08
34 #define REG_INTCON_PORTB 0x09
35 #define REG_GPPU_PORTA 0x0C
36 #define REG_GPPU_PORTB 0x0D
37 #define REG_INTF_PORTA 0x0E
38 #define REG_INTF_PORTB 0x0F
39 #define REG_INTCAP_PORTA 0x10
40 #define REG_INTCAP_PORTB 0x11
41 #define REG_GPIO_PORTA 0x12
42 #define REG_GPIO_PORTB 0x13
43 #define REG_OLAT_PORTA 0x14
44 #define REG_OLAT_PORTB 0x15
45
46 #define MCP23S17_ADDR 0x40
47 #define MCP23S17_READBIT 0x01
48
49 /** Configuration data */
50 struct mcp23s17_config {
51 /* gpio_driver_config needs to be first */
52 struct gpio_driver_config common;
53
54 struct spi_dt_spec bus;
55 };
56
57 /** Runtime driver data */
58 struct mcp23s17_drv_data {
59 /* gpio_driver_data needs to be first */
60 struct gpio_driver_data data;
61
62 struct k_sem lock;
63
64 struct {
65 uint16_t iodir;
66 uint16_t ipol;
67 uint16_t gpinten;
68 uint16_t defval;
69 uint16_t intcon;
70 uint16_t iocon;
71 uint16_t gppu;
72 uint16_t intf;
73 uint16_t intcap;
74 uint16_t gpio;
75 uint16_t olat;
76 } reg_cache;
77 };
78
read_port_regs(const struct device * dev,uint8_t reg,uint16_t * buf)79 static int read_port_regs(const struct device *dev, uint8_t reg,
80 uint16_t *buf)
81 {
82 const struct mcp23s17_config *config = dev->config;
83 int ret;
84 uint16_t port_data;
85
86 uint8_t addr = MCP23S17_ADDR | MCP23S17_READBIT;
87 uint8_t buffer_tx[4] = { addr, reg, 0, 0 };
88
89 const struct spi_buf tx_buf = {
90 .buf = buffer_tx,
91 .len = 4,
92 };
93 const struct spi_buf_set tx = {
94 .buffers = &tx_buf,
95 .count = 1,
96 };
97 const struct spi_buf rx_buf[2] = {
98 {
99 .buf = NULL,
100 .len = 2
101 },
102 {
103 .buf = (uint8_t *)&port_data,
104 .len = 2
105 }
106 };
107 const struct spi_buf_set rx = {
108 .buffers = rx_buf,
109 .count = ARRAY_SIZE(rx_buf),
110 };
111
112 ret = spi_transceive_dt(&config->bus, &tx, &rx);
113 if (ret) {
114 LOG_DBG("spi_transceive FAIL %d\n", ret);
115 return ret;
116 }
117
118 *buf = sys_le16_to_cpu(port_data);
119
120 LOG_DBG("MCP23S17: Read: REG[0x%X] = 0x%X, REG[0x%X] = 0x%X",
121 reg, (*buf & 0xFF), (reg + 1), (*buf >> 8));
122
123 return 0;
124 }
125
write_port_regs(const struct device * dev,uint8_t reg,uint16_t value)126 static int write_port_regs(const struct device *dev, uint8_t reg,
127 uint16_t value)
128 {
129 const struct mcp23s17_config *config = dev->config;
130 int ret;
131 uint16_t port_data;
132
133 LOG_DBG("MCP23S17: Write: REG[0x%X] = 0x%X, REG[0x%X] = 0x%X",
134 reg, (value & 0xFF), (reg + 1), (value >> 8));
135
136 port_data = sys_cpu_to_le16(value);
137
138 uint8_t addr = MCP23S17_ADDR;
139 uint8_t buffer_tx[2] = { addr, reg };
140
141 const struct spi_buf tx_buf[2] = {
142 {
143 .buf = buffer_tx,
144 .len = 2,
145 },
146 {
147 .buf = (uint8_t *)&port_data,
148 .len = 2,
149 }
150 };
151 const struct spi_buf_set tx = {
152 .buffers = tx_buf,
153 .count = ARRAY_SIZE(tx_buf),
154 };
155
156 ret = spi_write_dt(&config->bus, &tx);
157 if (ret) {
158 LOG_DBG("spi_write FAIL %d\n", ret);
159 return ret;
160 }
161
162 return 0;
163 }
164
setup_pin_dir(const struct device * dev,uint32_t pin,int flags)165 static int setup_pin_dir(const struct device *dev, uint32_t pin, int flags)
166 {
167 struct mcp23s17_drv_data *drv_data = dev->data;
168 uint16_t *dir = &drv_data->reg_cache.iodir;
169 uint16_t *output = &drv_data->reg_cache.gpio;
170 int ret;
171
172 if ((flags & GPIO_OUTPUT) != 0U) {
173 if ((flags & GPIO_OUTPUT_INIT_HIGH) != 0U) {
174 *output |= BIT(pin);
175 } else if ((flags & GPIO_OUTPUT_INIT_LOW) != 0U) {
176 *output &= ~BIT(pin);
177 }
178 *dir &= ~BIT(pin);
179 } else {
180 *dir |= BIT(pin);
181 }
182
183 ret = write_port_regs(dev, REG_GPIO_PORTA, *output);
184 if (ret != 0) {
185 return ret;
186 }
187
188 ret = write_port_regs(dev, REG_IODIR_PORTA, *dir);
189
190 return ret;
191 }
192
setup_pin_pullupdown(const struct device * dev,uint32_t pin,int flags)193 static int setup_pin_pullupdown(const struct device *dev, uint32_t pin,
194 int flags)
195 {
196 struct mcp23s17_drv_data *drv_data = dev->data;
197 uint16_t port;
198 int ret;
199
200 /* Setup pin pull up or pull down */
201 port = drv_data->reg_cache.gppu;
202
203 /* pull down == 0, pull up == 1 */
204 if ((flags & GPIO_PULL_DOWN) != 0U) {
205 return -ENOTSUP;
206 }
207
208 WRITE_BIT(port, pin, (flags & GPIO_PULL_UP) != 0U);
209
210 ret = write_port_regs(dev, REG_GPPU_PORTA, port);
211 if (ret == 0) {
212 drv_data->reg_cache.gppu = port;
213 }
214
215 return ret;
216 }
217
mcp23s17_config(const struct device * dev,gpio_pin_t pin,gpio_flags_t flags)218 static int mcp23s17_config(const struct device *dev,
219 gpio_pin_t pin, gpio_flags_t flags)
220 {
221 struct mcp23s17_drv_data *drv_data = dev->data;
222 int ret;
223
224 /* Can't do SPI bus operations from an ISR */
225 if (k_is_in_isr()) {
226 return -EWOULDBLOCK;
227 }
228
229 k_sem_take(&drv_data->lock, K_FOREVER);
230
231 if ((flags & GPIO_OPEN_DRAIN) != 0U) {
232 ret = -ENOTSUP;
233 goto done;
234 }
235
236 ret = setup_pin_dir(dev, pin, flags);
237 if (ret) {
238 LOG_ERR("MCP23S17: error setting pin direction (%d)", ret);
239 goto done;
240 }
241
242 ret = setup_pin_pullupdown(dev, pin, flags);
243 if (ret) {
244 LOG_ERR("MCP23S17: error setting pin pull up/down (%d)", ret);
245 goto done;
246 }
247
248 done:
249 k_sem_give(&drv_data->lock);
250 return ret;
251 }
252
mcp23s17_port_get_raw(const struct device * dev,uint32_t * value)253 static int mcp23s17_port_get_raw(const struct device *dev, uint32_t *value)
254 {
255 struct mcp23s17_drv_data *drv_data = dev->data;
256 uint16_t buf;
257 int ret;
258
259 /* Can't do SPI bus operations from an ISR */
260 if (k_is_in_isr()) {
261 return -EWOULDBLOCK;
262 }
263
264 k_sem_take(&drv_data->lock, K_FOREVER);
265
266 ret = read_port_regs(dev, REG_GPIO_PORTA, &buf);
267 if (ret != 0) {
268 goto done;
269 }
270
271 *value = buf;
272
273 done:
274 k_sem_give(&drv_data->lock);
275 return ret;
276 }
277
mcp23s17_port_set_masked_raw(const struct device * dev,uint32_t mask,uint32_t value)278 static int mcp23s17_port_set_masked_raw(const struct device *dev,
279 uint32_t mask, uint32_t value)
280 {
281 struct mcp23s17_drv_data *drv_data = dev->data;
282 uint16_t buf;
283 int ret;
284
285 /* Can't do SPI bus operations from an ISR */
286 if (k_is_in_isr()) {
287 return -EWOULDBLOCK;
288 }
289
290 k_sem_take(&drv_data->lock, K_FOREVER);
291
292 buf = drv_data->reg_cache.gpio;
293 buf = (buf & ~mask) | (mask & value);
294
295 ret = write_port_regs(dev, REG_GPIO_PORTA, buf);
296 if (ret == 0) {
297 drv_data->reg_cache.gpio = buf;
298 }
299
300 k_sem_give(&drv_data->lock);
301
302 return ret;
303 }
304
mcp23s17_port_set_bits_raw(const struct device * dev,uint32_t mask)305 static int mcp23s17_port_set_bits_raw(const struct device *dev, uint32_t mask)
306 {
307 return mcp23s17_port_set_masked_raw(dev, mask, mask);
308 }
309
mcp23s17_port_clear_bits_raw(const struct device * dev,uint32_t mask)310 static int mcp23s17_port_clear_bits_raw(const struct device *dev,
311 uint32_t mask)
312 {
313 return mcp23s17_port_set_masked_raw(dev, mask, 0);
314 }
315
mcp23s17_port_toggle_bits(const struct device * dev,uint32_t mask)316 static int mcp23s17_port_toggle_bits(const struct device *dev, uint32_t mask)
317 {
318 struct mcp23s17_drv_data *const drv_data =
319 (struct mcp23s17_drv_data *const)dev->data;
320 uint16_t buf;
321 int ret;
322
323 /* Can't do SPI bus operations from an ISR */
324 if (k_is_in_isr()) {
325 return -EWOULDBLOCK;
326 }
327
328 k_sem_take(&drv_data->lock, K_FOREVER);
329
330 buf = drv_data->reg_cache.gpio;
331 buf ^= mask;
332
333 ret = write_port_regs(dev, REG_GPIO_PORTA, buf);
334 if (ret == 0) {
335 drv_data->reg_cache.gpio = buf;
336 }
337
338 k_sem_give(&drv_data->lock);
339
340 return ret;
341 }
342
343 static const struct gpio_driver_api api_table = {
344 .pin_configure = mcp23s17_config,
345 .port_get_raw = mcp23s17_port_get_raw,
346 .port_set_masked_raw = mcp23s17_port_set_masked_raw,
347 .port_set_bits_raw = mcp23s17_port_set_bits_raw,
348 .port_clear_bits_raw = mcp23s17_port_clear_bits_raw,
349 .port_toggle_bits = mcp23s17_port_toggle_bits,
350 };
351
mcp23s17_init(const struct device * dev)352 static int mcp23s17_init(const struct device *dev)
353 {
354 const struct mcp23s17_config *config = dev->config;
355 struct mcp23s17_drv_data *drv_data = dev->data;
356
357 if (!spi_is_ready_dt(&config->bus)) {
358 LOG_ERR("SPI bus %s not ready", config->bus.bus->name);
359 return -ENODEV;
360 }
361
362 k_sem_init(&drv_data->lock, 1, 1);
363
364 return 0;
365 }
366
367 #define MCP23S17_INIT(inst) \
368 static const struct mcp23s17_config mcp23s17_##inst##_config = { \
369 .common = { \
370 .port_pin_mask = \
371 GPIO_PORT_PIN_MASK_FROM_DT_INST(inst), \
372 }, \
373 .bus = SPI_DT_SPEC_INST_GET( \
374 inst, \
375 SPI_OP_MODE_MASTER | SPI_MODE_CPOL | \
376 SPI_MODE_CPHA | SPI_WORD_SET(8), 0), \
377 }; \
378 \
379 static struct mcp23s17_drv_data mcp23s17_##inst##_drvdata = { \
380 /* Default for registers according to datasheet */ \
381 .reg_cache.iodir = 0xFFFF, \
382 .reg_cache.ipol = 0x0, \
383 .reg_cache.gpinten = 0x0, \
384 .reg_cache.defval = 0x0, \
385 .reg_cache.intcon = 0x0, \
386 .reg_cache.iocon = 0x0, \
387 .reg_cache.gppu = 0x0, \
388 .reg_cache.intf = 0x0, \
389 .reg_cache.intcap = 0x0, \
390 .reg_cache.gpio = 0x0, \
391 .reg_cache.olat = 0x0, \
392 }; \
393 \
394 /* This has to init after SPI master */ \
395 DEVICE_DT_INST_DEFINE(inst, mcp23s17_init, \
396 NULL, \
397 &mcp23s17_##inst##_drvdata, \
398 &mcp23s17_##inst##_config, \
399 POST_KERNEL, \
400 CONFIG_GPIO_MCP23S17_INIT_PRIORITY, \
401 &api_table);
402
403 DT_INST_FOREACH_STATUS_OKAY(MCP23S17_INIT)
404