]>
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 | ||
10 | #include <common.h> | |
11 | #include <errno.h> | |
12 | #include <dm.h> | |
13 | #include <i2c.h> | |
14 | #include <asm/gpio.h> | |
15 | #include <dm/device_compat.h> | |
16 | #include <dt-bindings/gpio/gpio.h> | |
17 | ||
18 | enum mcp230xx_type { | |
19 | UNKNOWN = 0, | |
20 | MCP23008, | |
21 | MCP23017, | |
22 | MCP23018, | |
23 | }; | |
24 | ||
25 | #define MCP230XX_IODIR 0x00 | |
26 | #define MCP230XX_GPPU 0x06 | |
27 | #define MCP230XX_GPIO 0x09 | |
28 | #define MCP230XX_OLAT 0x0a | |
29 | ||
30 | #define BANKSIZE 8 | |
31 | ||
32 | static int mcp230xx_read(struct udevice *dev, uint reg, uint offset) | |
33 | { | |
34 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
35 | int bank = offset / BANKSIZE; | |
36 | int mask = 1 << (offset % BANKSIZE); | |
37 | int shift = (uc_priv->gpio_count / BANKSIZE) - 1; | |
38 | int ret; | |
39 | ||
40 | ret = dm_i2c_reg_read(dev, (reg << shift) | bank); | |
41 | if (ret < 0) | |
42 | return ret; | |
43 | ||
44 | return !!(ret & mask); | |
45 | } | |
46 | ||
47 | static int mcp230xx_write(struct udevice *dev, uint reg, uint offset, bool val) | |
48 | { | |
49 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
50 | int bank = offset / BANKSIZE; | |
51 | int mask = 1 << (offset % BANKSIZE); | |
52 | int shift = (uc_priv->gpio_count / BANKSIZE) - 1; | |
53 | ||
54 | return dm_i2c_reg_clrset(dev, (reg << shift) | bank, mask, val ? mask : 0); | |
55 | } | |
56 | ||
57 | static int mcp230xx_get_value(struct udevice *dev, uint offset) | |
58 | { | |
59 | int ret; | |
60 | ||
61 | ret = mcp230xx_read(dev, MCP230XX_GPIO, offset); | |
62 | if (ret < 0) { | |
63 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
64 | return ret; | |
65 | } | |
66 | ||
67 | return ret; | |
68 | } | |
69 | ||
70 | static int mcp230xx_set_value(struct udevice *dev, uint offset, int val) | |
71 | { | |
72 | int ret; | |
73 | ||
74 | ret = mcp230xx_write(dev, MCP230XX_GPIO, offset, val); | |
75 | if (ret < 0) { | |
76 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
77 | return ret; | |
78 | } | |
79 | ||
80 | return ret; | |
81 | } | |
82 | ||
83 | static int mcp230xx_get_flags(struct udevice *dev, unsigned int offset, | |
84 | ulong *flags) | |
85 | { | |
86 | int direction, pullup; | |
87 | ||
88 | pullup = mcp230xx_read(dev, MCP230XX_GPPU, offset); | |
89 | if (pullup < 0) { | |
90 | dev_err(dev, "%s error: %d\n", __func__, pullup); | |
91 | return pullup; | |
92 | } | |
93 | ||
94 | direction = mcp230xx_read(dev, MCP230XX_IODIR, offset); | |
95 | if (direction < 0) { | |
96 | dev_err(dev, "%s error: %d\n", __func__, direction); | |
97 | return direction; | |
98 | } | |
99 | ||
100 | *flags = direction ? GPIOD_IS_IN : GPIOD_IS_OUT; | |
101 | ||
102 | if (pullup) | |
103 | *flags |= GPIOD_PULL_UP; | |
104 | ||
105 | return 0; | |
106 | } | |
107 | ||
108 | static int mcp230xx_set_flags(struct udevice *dev, uint offset, ulong flags) | |
109 | { | |
110 | bool input = !(flags & (GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE)); | |
111 | bool pullup = flags & GPIOD_PULL_UP; | |
112 | ulong supported_mask; | |
113 | int ret; | |
114 | ||
115 | /* Note: active-low is ignored (handled by core) */ | |
116 | supported_mask = GPIOD_ACTIVE_LOW | GPIOD_MASK_DIR | GPIOD_PULL_UP; | |
117 | if (flags & ~supported_mask) { | |
118 | dev_err(dev, "%s unsupported flag(s): %lx\n", __func__, flags); | |
119 | return -EINVAL; | |
120 | } | |
121 | ||
122 | ret = mcp230xx_write(dev, MCP230XX_OLAT, offset, !!(flags & GPIOD_IS_OUT_ACTIVE)); | |
123 | if (ret) { | |
124 | dev_err(dev, "%s failed to setup output latch: %d\n", __func__, ret); | |
125 | return ret; | |
126 | } | |
127 | ||
128 | ret = mcp230xx_write(dev, MCP230XX_GPPU, offset, pullup); | |
129 | if (ret) { | |
130 | dev_err(dev, "%s failed to setup pull-up: %d\n", __func__, ret); | |
131 | return ret; | |
132 | } | |
133 | ||
134 | ret = mcp230xx_write(dev, MCP230XX_IODIR, offset, input); | |
135 | if (ret) { | |
136 | dev_err(dev, "%s failed to setup direction: %d\n", __func__, ret); | |
137 | return ret; | |
138 | } | |
139 | ||
140 | return 0; | |
141 | } | |
142 | ||
143 | static int mcp230xx_direction_input(struct udevice *dev, uint offset) | |
144 | { | |
145 | return mcp230xx_set_flags(dev, offset, GPIOD_IS_IN); | |
146 | } | |
147 | ||
148 | static int mcp230xx_direction_output(struct udevice *dev, uint offset, int val) | |
149 | { | |
150 | int ret = mcp230xx_set_value(dev, offset, val); | |
151 | if (ret < 0) { | |
152 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
153 | return ret; | |
154 | } | |
155 | return mcp230xx_set_flags(dev, offset, GPIOD_IS_OUT); | |
156 | } | |
157 | ||
158 | static int mcp230xx_get_function(struct udevice *dev, uint offset) | |
159 | { | |
160 | int ret; | |
161 | ||
162 | ret = mcp230xx_read(dev, MCP230XX_IODIR, offset); | |
163 | if (ret < 0) { | |
164 | dev_err(dev, "%s error: %d\n", __func__, ret); | |
165 | return ret; | |
166 | } | |
167 | ||
168 | return ret ? GPIOF_INPUT : GPIOF_OUTPUT; | |
169 | } | |
170 | ||
171 | static const struct dm_gpio_ops mcp230xx_ops = { | |
172 | .direction_input = mcp230xx_direction_input, | |
173 | .direction_output = mcp230xx_direction_output, | |
174 | .get_value = mcp230xx_get_value, | |
175 | .set_value = mcp230xx_set_value, | |
176 | .get_function = mcp230xx_get_function, | |
177 | .set_flags = mcp230xx_set_flags, | |
178 | .get_flags = mcp230xx_get_flags, | |
179 | }; | |
180 | ||
181 | static int mcp230xx_probe(struct udevice *dev) | |
182 | { | |
183 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
184 | char name[32], label[8], *str; | |
185 | int addr, gpio_count, size; | |
186 | const u8 *tmp; | |
187 | ||
188 | switch (dev_get_driver_data(dev)) { | |
189 | case MCP23008: | |
190 | gpio_count = 8; | |
191 | break; | |
192 | case MCP23017: | |
193 | case MCP23018: | |
194 | gpio_count = 16; | |
195 | break; | |
196 | default: | |
197 | return -ENODEV; | |
198 | } | |
199 | ||
200 | addr = dev_read_addr(dev); | |
201 | tmp = dev_read_prop(dev, "label", &size); | |
202 | if (tmp) { | |
203 | memcpy(label, tmp, sizeof(label) - 1); | |
204 | label[sizeof(label) - 1] = '\0'; | |
205 | snprintf(name, sizeof(name), "%s@%x_", label, addr); | |
206 | } else { | |
207 | snprintf(name, sizeof(name), "gpio@%x_", addr); | |
208 | } | |
209 | ||
210 | str = strdup(name); | |
211 | if (!str) | |
212 | return -ENOMEM; | |
213 | ||
214 | uc_priv->bank_name = str; | |
215 | uc_priv->gpio_count = gpio_count; | |
216 | ||
217 | dev_dbg(dev, "%s is ready\n", str); | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
222 | static const struct udevice_id mcp230xx_ids[] = { | |
223 | { .compatible = "microchip,mcp23008", .data = MCP23008, }, | |
224 | { .compatible = "microchip,mcp23017", .data = MCP23017, }, | |
225 | { .compatible = "microchip,mcp23018", .data = MCP23018, }, | |
226 | { } | |
227 | }; | |
228 | ||
229 | U_BOOT_DRIVER(mcp230xx) = { | |
230 | .name = "mcp230xx", | |
231 | .id = UCLASS_GPIO, | |
232 | .ops = &mcp230xx_ops, | |
233 | .probe = mcp230xx_probe, | |
234 | .of_match = mcp230xx_ids, | |
235 | }; |