]>
Commit | Line | Data |
---|---|---|
0b3da993 SR |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Copyright (C) 2021, Collabora Ltd. | |
4 | * Copyright (C) 2021, General Electric Company | |
5 | * Author(s): Sebastian Reichel <[email protected]> | |
6 | */ | |
7 | ||
8 | #define LOG_CATEGORY UCLASS_GPIO | |
9 | ||
0b3da993 SR |
10 | #include <errno.h> |
11 | #include <dm.h> | |
12 | #include <i2c.h> | |
3b639f64 | 13 | #include <spi.h> |
0b3da993 SR |
14 | #include <asm/gpio.h> |
15 | #include <dm/device_compat.h> | |
16 | #include <dt-bindings/gpio/gpio.h> | |
3cf94b6f | 17 | #include <linux/delay.h> |
0b3da993 SR |
18 | |
19 | enum mcp230xx_type { | |
20 | UNKNOWN = 0, | |
21 | MCP23008, | |
22 | MCP23017, | |
23 | MCP23018, | |
3b639f64 PW |
24 | MCP23S08, |
25 | MCP23S17, | |
26 | MCP23S18, | |
27 | }; | |
28 | ||
29 | struct mcp230xx_info { | |
30 | uint dev_addr; | |
0b3da993 SR |
31 | }; |
32 | ||
33 | #define MCP230XX_IODIR 0x00 | |
34 | #define MCP230XX_GPPU 0x06 | |
35 | #define MCP230XX_GPIO 0x09 | |
36 | #define MCP230XX_OLAT 0x0a | |
37 | ||
38 | #define BANKSIZE 8 | |
39 | ||
3b639f64 PW |
40 | #define MCP230XX_ADDR 0x20 |
41 | ||
42 | static int mcp230xx_read_spi(struct udevice *dev, uint reg_addr) | |
43 | { | |
44 | struct mcp230xx_info *info = dev_get_plat(dev); | |
45 | uint dev_addr, value = 0; | |
46 | int ret; | |
47 | ||
48 | /* set R/W bit for reading */ | |
49 | dev_addr = (info->dev_addr << 1) | 1; | |
50 | ||
51 | ret = dm_spi_claim_bus(dev); | |
52 | if (ret) | |
53 | return ret; | |
54 | ||
55 | ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_BEGIN); | |
56 | if (ret < 0) | |
57 | goto fail; | |
58 | udelay(1); | |
59 | ||
60 | ret = dm_spi_xfer(dev, 8, &dev_addr, NULL, 0); | |
61 | if (ret < 0) | |
62 | goto fail; | |
63 | ||
64 | ret = dm_spi_xfer(dev, 8, ®_addr, NULL, 0); | |
65 | if (ret < 0) | |
66 | goto fail; | |
67 | ||
68 | ret = dm_spi_xfer(dev, 8, NULL, &value, 0); | |
69 | ||
70 | fail: | |
71 | dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); | |
72 | dm_spi_release_bus(dev); | |
73 | if (ret < 0) | |
74 | return ret; | |
75 | return value; | |
76 | } | |
77 | ||
0b3da993 SR |
78 | static int mcp230xx_read(struct udevice *dev, uint reg, uint offset) |
79 | { | |
80 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
81 | int bank = offset / BANKSIZE; | |
82 | int mask = 1 << (offset % BANKSIZE); | |
83 | int shift = (uc_priv->gpio_count / BANKSIZE) - 1; | |
3b639f64 PW |
84 | int reg_addr = (reg << shift) | bank; |
85 | int ret = 0; | |
86 | ||
87 | switch (dev_get_driver_data(dev)) { | |
88 | case MCP23008: | |
89 | case MCP23017: | |
90 | case MCP23018: | |
91 | ret = dm_i2c_reg_read(dev, reg_addr); | |
92 | break; | |
93 | case MCP23S08: | |
94 | case MCP23S17: | |
95 | case MCP23S18: | |
96 | ret = mcp230xx_read_spi(dev, reg_addr); | |
97 | break; | |
98 | default: | |
99 | return -ENODEV; | |
100 | } | |
0b3da993 | 101 | |
0b3da993 SR |
102 | if (ret < 0) |
103 | return ret; | |
104 | ||
105 | return !!(ret & mask); | |
106 | } | |
107 | ||
3b639f64 PW |
108 | static int mcp230xx_clrset_spi(struct udevice *dev, uint reg_addr, uint clr, uint set) |
109 | { | |
110 | struct mcp230xx_info *info = dev_get_plat(dev); | |
111 | int dev_addr, value; | |
112 | int ret; | |
113 | ||
114 | /* R/W bit = 0 for writing */ | |
115 | dev_addr = (info->dev_addr << 1); | |
116 | ||
117 | ret = mcp230xx_read_spi(dev, reg_addr); | |
118 | if (ret < 0) | |
119 | return ret; | |
120 | ||
121 | value = ret; | |
122 | value &= ~clr; | |
123 | value |= set; | |
124 | ||
125 | ret = dm_spi_claim_bus(dev); | |
126 | if (ret) | |
127 | return ret; | |
128 | ||
129 | ret = dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_BEGIN); | |
130 | if (ret < 0) | |
131 | goto fail; | |
132 | udelay(1); | |
133 | ||
134 | ret = dm_spi_xfer(dev, 8, &dev_addr, NULL, 0); | |
135 | if (ret < 0) | |
136 | goto fail; | |
137 | ||
138 | ret = dm_spi_xfer(dev, 8, ®_addr, NULL, 0); | |
139 | if (ret < 0) | |
140 | goto fail; | |
141 | ||
142 | ret = dm_spi_xfer(dev, 8, &value, NULL, 0); | |
143 | ||
144 | fail: | |
145 | dm_spi_xfer(dev, 0, NULL, NULL, SPI_XFER_END); | |
146 | dm_spi_release_bus(dev); | |
147 | return ret; | |
148 | } | |
149 | ||
0b3da993 SR |
150 | static int mcp230xx_write(struct udevice *dev, uint reg, uint offset, bool val) |
151 | { | |
152 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
153 | int bank = offset / BANKSIZE; | |
154 | int mask = 1 << (offset % BANKSIZE); | |
155 | int shift = (uc_priv->gpio_count / BANKSIZE) - 1; | |
3b639f64 | 156 | int reg_addr = (reg << shift) | bank; |
0b3da993 | 157 | |
3b639f64 PW |
158 | switch (dev_get_driver_data(dev)) { |
159 | case MCP23008: | |
160 | case MCP23017: | |
161 | case MCP23018: | |
162 | return dm_i2c_reg_clrset(dev, reg_addr, mask, val ? mask : 0); | |
163 | case MCP23S08: | |
164 | case MCP23S17: | |
165 | case MCP23S18: | |
166 | return mcp230xx_clrset_spi(dev, reg_addr, mask, val ? mask : 0); | |
167 | default: | |
168 | return -ENODEV; | |
169 | } | |
0b3da993 SR |
170 | } |
171 | ||
172 | static int mcp230xx_get_value(struct udevice *dev, uint offset) | |
173 | { | |
174 | int ret; | |
175 | ||
176 | ret = mcp230xx_read(dev, MCP230XX_GPIO, offset); | |
177 | if (ret < 0) { | |
178 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
179 | return ret; | |
180 | } | |
181 | ||
182 | return ret; | |
183 | } | |
184 | ||
185 | static int mcp230xx_set_value(struct udevice *dev, uint offset, int val) | |
186 | { | |
187 | int ret; | |
188 | ||
189 | ret = mcp230xx_write(dev, MCP230XX_GPIO, offset, val); | |
190 | if (ret < 0) { | |
191 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
192 | return ret; | |
193 | } | |
194 | ||
195 | return ret; | |
196 | } | |
197 | ||
198 | static int mcp230xx_get_flags(struct udevice *dev, unsigned int offset, | |
199 | ulong *flags) | |
200 | { | |
201 | int direction, pullup; | |
202 | ||
203 | pullup = mcp230xx_read(dev, MCP230XX_GPPU, offset); | |
204 | if (pullup < 0) { | |
205 | dev_err(dev, "%s error: %d\n", __func__, pullup); | |
206 | return pullup; | |
207 | } | |
208 | ||
209 | direction = mcp230xx_read(dev, MCP230XX_IODIR, offset); | |
210 | if (direction < 0) { | |
211 | dev_err(dev, "%s error: %d\n", __func__, direction); | |
212 | return direction; | |
213 | } | |
214 | ||
215 | *flags = direction ? GPIOD_IS_IN : GPIOD_IS_OUT; | |
216 | ||
217 | if (pullup) | |
218 | *flags |= GPIOD_PULL_UP; | |
219 | ||
220 | return 0; | |
221 | } | |
222 | ||
223 | static int mcp230xx_set_flags(struct udevice *dev, uint offset, ulong flags) | |
224 | { | |
225 | bool input = !(flags & (GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE)); | |
226 | bool pullup = flags & GPIOD_PULL_UP; | |
227 | ulong supported_mask; | |
228 | int ret; | |
229 | ||
230 | /* Note: active-low is ignored (handled by core) */ | |
231 | supported_mask = GPIOD_ACTIVE_LOW | GPIOD_MASK_DIR | GPIOD_PULL_UP; | |
232 | if (flags & ~supported_mask) { | |
233 | dev_err(dev, "%s unsupported flag(s): %lx\n", __func__, flags); | |
234 | return -EINVAL; | |
235 | } | |
236 | ||
237 | ret = mcp230xx_write(dev, MCP230XX_OLAT, offset, !!(flags & GPIOD_IS_OUT_ACTIVE)); | |
238 | if (ret) { | |
239 | dev_err(dev, "%s failed to setup output latch: %d\n", __func__, ret); | |
240 | return ret; | |
241 | } | |
242 | ||
243 | ret = mcp230xx_write(dev, MCP230XX_GPPU, offset, pullup); | |
244 | if (ret) { | |
245 | dev_err(dev, "%s failed to setup pull-up: %d\n", __func__, ret); | |
246 | return ret; | |
247 | } | |
248 | ||
249 | ret = mcp230xx_write(dev, MCP230XX_IODIR, offset, input); | |
250 | if (ret) { | |
251 | dev_err(dev, "%s failed to setup direction: %d\n", __func__, ret); | |
252 | return ret; | |
253 | } | |
254 | ||
255 | return 0; | |
256 | } | |
257 | ||
258 | static int mcp230xx_direction_input(struct udevice *dev, uint offset) | |
259 | { | |
260 | return mcp230xx_set_flags(dev, offset, GPIOD_IS_IN); | |
261 | } | |
262 | ||
263 | static int mcp230xx_direction_output(struct udevice *dev, uint offset, int val) | |
264 | { | |
265 | int ret = mcp230xx_set_value(dev, offset, val); | |
266 | if (ret < 0) { | |
267 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
268 | return ret; | |
269 | } | |
270 | return mcp230xx_set_flags(dev, offset, GPIOD_IS_OUT); | |
271 | } | |
272 | ||
273 | static int mcp230xx_get_function(struct udevice *dev, uint offset) | |
274 | { | |
275 | int ret; | |
276 | ||
277 | ret = mcp230xx_read(dev, MCP230XX_IODIR, offset); | |
278 | if (ret < 0) { | |
279 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
280 | return ret; | |
281 | } | |
282 | ||
283 | return ret ? GPIOF_INPUT : GPIOF_OUTPUT; | |
284 | } | |
285 | ||
286 | static const struct dm_gpio_ops mcp230xx_ops = { | |
287 | .direction_input = mcp230xx_direction_input, | |
288 | .direction_output = mcp230xx_direction_output, | |
289 | .get_value = mcp230xx_get_value, | |
290 | .set_value = mcp230xx_set_value, | |
291 | .get_function = mcp230xx_get_function, | |
292 | .set_flags = mcp230xx_set_flags, | |
293 | .get_flags = mcp230xx_get_flags, | |
294 | }; | |
295 | ||
296 | static int mcp230xx_probe(struct udevice *dev) | |
297 | { | |
298 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
3b639f64 PW |
299 | struct mcp230xx_info *info = dev_get_plat(dev); |
300 | char name[32], label[32], *str; | |
0b3da993 SR |
301 | int addr, gpio_count, size; |
302 | const u8 *tmp; | |
303 | ||
304 | switch (dev_get_driver_data(dev)) { | |
305 | case MCP23008: | |
3b639f64 | 306 | case MCP23S08: |
0b3da993 SR |
307 | gpio_count = 8; |
308 | break; | |
309 | case MCP23017: | |
310 | case MCP23018: | |
3b639f64 PW |
311 | case MCP23S17: |
312 | case MCP23S18: | |
0b3da993 SR |
313 | gpio_count = 16; |
314 | break; | |
315 | default: | |
316 | return -ENODEV; | |
317 | } | |
318 | ||
3b639f64 PW |
319 | switch (dev_get_driver_data(dev)) { |
320 | case MCP23S08: | |
321 | case MCP23S17: | |
322 | case MCP23S18: | |
323 | info->dev_addr = dev_read_u32_default(dev, "addr", MCP230XX_ADDR); | |
324 | break; | |
325 | default: | |
326 | info->dev_addr = 0; | |
327 | break; | |
328 | } | |
329 | ||
0b3da993 SR |
330 | addr = dev_read_addr(dev); |
331 | tmp = dev_read_prop(dev, "label", &size); | |
332 | if (tmp) { | |
333 | memcpy(label, tmp, sizeof(label) - 1); | |
334 | label[sizeof(label) - 1] = '\0'; | |
335 | snprintf(name, sizeof(name), "%s@%x_", label, addr); | |
336 | } else { | |
337 | snprintf(name, sizeof(name), "gpio@%x_", addr); | |
338 | } | |
339 | ||
340 | str = strdup(name); | |
341 | if (!str) | |
342 | return -ENOMEM; | |
343 | ||
344 | uc_priv->bank_name = str; | |
345 | uc_priv->gpio_count = gpio_count; | |
346 | ||
347 | dev_dbg(dev, "%s is ready\n", str); | |
348 | ||
349 | return 0; | |
350 | } | |
351 | ||
352 | static const struct udevice_id mcp230xx_ids[] = { | |
3b639f64 | 353 | /* i2c interface */ |
0b3da993 SR |
354 | { .compatible = "microchip,mcp23008", .data = MCP23008, }, |
355 | { .compatible = "microchip,mcp23017", .data = MCP23017, }, | |
356 | { .compatible = "microchip,mcp23018", .data = MCP23018, }, | |
3b639f64 PW |
357 | /* spi interface */ |
358 | { .compatible = "microchip,mcp23s08", .data = MCP23S08, }, | |
359 | { .compatible = "microchip,mcp23s17", .data = MCP23S17, }, | |
360 | { .compatible = "microchip,mcp23s18", .data = MCP23S18, }, | |
0b3da993 SR |
361 | { } |
362 | }; | |
363 | ||
364 | U_BOOT_DRIVER(mcp230xx) = { | |
365 | .name = "mcp230xx", | |
366 | .id = UCLASS_GPIO, | |
367 | .ops = &mcp230xx_ops, | |
368 | .probe = mcp230xx_probe, | |
3b639f64 | 369 | .plat_auto = sizeof(struct mcp230xx_info), |
0b3da993 SR |
370 | .of_match = mcp230xx_ids, |
371 | }; |