]>
Commit | Line | Data |
---|---|---|
03773439 PF |
1 | /* |
2 | * Take linux kernel driver drivers/gpio/gpio-pca953x.c for reference. | |
3 | * | |
4 | * Copyright (C) 2016 Peng Fan <van.freenix@gmail.com> | |
5 | * | |
6 | * SPDX-License-Identifier: GPL-2.0+ | |
7 | * | |
8 | */ | |
9 | ||
10 | /* | |
11 | * Note: | |
12 | * The driver's compatible table is borrowed from Linux Kernel, | |
13 | * but now max supported gpio pins is 24 and only PCA953X_TYPE | |
14 | * is supported. PCA957X_TYPE is not supported now. | |
15 | * Also the Polarity Inversion feature is not supported now. | |
16 | * | |
17 | * TODO: | |
18 | * 1. Support PCA957X_TYPE | |
71db3270 | 19 | * 2. Support 24 gpio pins |
20 | * 3. Support Polarity Inversion | |
03773439 PF |
21 | */ |
22 | ||
23 | #include <common.h> | |
24 | #include <errno.h> | |
25 | #include <dm.h> | |
26 | #include <fdtdec.h> | |
27 | #include <i2c.h> | |
28 | #include <malloc.h> | |
29 | #include <asm/gpio.h> | |
30 | #include <asm/io.h> | |
31 | #include <dt-bindings/gpio/gpio.h> | |
32 | ||
33 | #define PCA953X_INPUT 0 | |
34 | #define PCA953X_OUTPUT 1 | |
35 | #define PCA953X_INVERT 2 | |
36 | #define PCA953X_DIRECTION 3 | |
37 | ||
38 | #define PCA_GPIO_MASK 0x00FF | |
39 | #define PCA_INT 0x0100 | |
40 | #define PCA953X_TYPE 0x1000 | |
41 | #define PCA957X_TYPE 0x2000 | |
42 | #define PCA_TYPE_MASK 0xF000 | |
43 | #define PCA_CHIP_TYPE(x) ((x) & PCA_TYPE_MASK) | |
44 | ||
45 | enum { | |
46 | PCA953X_DIRECTION_IN, | |
47 | PCA953X_DIRECTION_OUT, | |
48 | }; | |
49 | ||
71db3270 | 50 | #define MAX_BANK 5 |
03773439 PF |
51 | #define BANK_SZ 8 |
52 | ||
53 | DECLARE_GLOBAL_DATA_PTR; | |
54 | ||
55 | /* | |
56 | * struct pca953x_info - Data for pca953x | |
57 | * | |
58 | * @dev: udevice structure for the device | |
59 | * @addr: i2c slave address | |
60 | * @invert: Polarity inversion or not | |
61 | * @gpio_count: the number of gpio pins that the device supports | |
62 | * @chip_type: indicate the chip type,PCA953X or PCA957X | |
63 | * @bank_count: the number of banks that the device supports | |
64 | * @reg_output: array to hold the value of output registers | |
65 | * @reg_direction: array to hold the value of direction registers | |
66 | */ | |
67 | struct pca953x_info { | |
68 | struct udevice *dev; | |
69 | int addr; | |
70 | int invert; | |
71 | int gpio_count; | |
72 | int chip_type; | |
73 | int bank_count; | |
74 | u8 reg_output[MAX_BANK]; | |
75 | u8 reg_direction[MAX_BANK]; | |
76 | }; | |
77 | ||
78 | static int pca953x_write_single(struct udevice *dev, int reg, u8 val, | |
79 | int offset) | |
80 | { | |
81 | struct pca953x_info *info = dev_get_platdata(dev); | |
82 | int bank_shift = fls((info->gpio_count - 1) / BANK_SZ); | |
83 | int off = offset / BANK_SZ; | |
84 | int ret = 0; | |
85 | ||
86 | ret = dm_i2c_write(dev, (reg << bank_shift) + off, &val, 1); | |
87 | if (ret) { | |
88 | dev_err(dev, "%s error\n", __func__); | |
89 | return ret; | |
90 | } | |
91 | ||
92 | return 0; | |
93 | } | |
94 | ||
95 | static int pca953x_read_single(struct udevice *dev, int reg, u8 *val, | |
96 | int offset) | |
97 | { | |
98 | struct pca953x_info *info = dev_get_platdata(dev); | |
99 | int bank_shift = fls((info->gpio_count - 1) / BANK_SZ); | |
100 | int off = offset / BANK_SZ; | |
101 | int ret; | |
102 | u8 byte; | |
103 | ||
104 | ret = dm_i2c_read(dev, (reg << bank_shift) + off, &byte, 1); | |
105 | if (ret) { | |
106 | dev_err(dev, "%s error\n", __func__); | |
107 | return ret; | |
108 | } | |
109 | ||
110 | *val = byte; | |
111 | ||
112 | return 0; | |
113 | } | |
114 | ||
115 | static int pca953x_read_regs(struct udevice *dev, int reg, u8 *val) | |
116 | { | |
117 | struct pca953x_info *info = dev_get_platdata(dev); | |
118 | int ret = 0; | |
119 | ||
120 | if (info->gpio_count <= 8) { | |
121 | ret = dm_i2c_read(dev, reg, val, 1); | |
122 | } else if (info->gpio_count <= 16) { | |
123 | ret = dm_i2c_read(dev, reg << 1, val, info->bank_count); | |
71db3270 | 124 | } else if (info->gpio_count == 40) { |
125 | /* Auto increment */ | |
126 | ret = dm_i2c_read(dev, (reg << 3) | 0x80, val, info->bank_count); | |
03773439 PF |
127 | } else { |
128 | dev_err(dev, "Unsupported now\n"); | |
129 | return -EINVAL; | |
130 | } | |
131 | ||
132 | return ret; | |
133 | } | |
134 | ||
135 | static int pca953x_is_output(struct udevice *dev, int offset) | |
136 | { | |
137 | struct pca953x_info *info = dev_get_platdata(dev); | |
138 | ||
139 | int bank = offset / BANK_SZ; | |
140 | int off = offset % BANK_SZ; | |
141 | ||
142 | /*0: output; 1: input */ | |
143 | return !(info->reg_direction[bank] & (1 << off)); | |
144 | } | |
145 | ||
146 | static int pca953x_get_value(struct udevice *dev, unsigned offset) | |
147 | { | |
148 | int ret; | |
149 | u8 val = 0; | |
150 | ||
fc76b698 | 151 | int off = offset % BANK_SZ; |
152 | ||
03773439 PF |
153 | ret = pca953x_read_single(dev, PCA953X_INPUT, &val, offset); |
154 | if (ret) | |
155 | return ret; | |
156 | ||
fc76b698 | 157 | return (val >> off) & 0x1; |
03773439 PF |
158 | } |
159 | ||
160 | static int pca953x_set_value(struct udevice *dev, unsigned offset, | |
161 | int value) | |
162 | { | |
163 | struct pca953x_info *info = dev_get_platdata(dev); | |
164 | int bank = offset / BANK_SZ; | |
165 | int off = offset % BANK_SZ; | |
166 | u8 val; | |
167 | int ret; | |
168 | ||
169 | if (value) | |
170 | val = info->reg_output[bank] | (1 << off); | |
171 | else | |
172 | val = info->reg_output[bank] & ~(1 << off); | |
173 | ||
174 | ret = pca953x_write_single(dev, PCA953X_OUTPUT, val, offset); | |
175 | if (ret) | |
176 | return ret; | |
177 | ||
178 | info->reg_output[bank] = val; | |
179 | ||
180 | return 0; | |
181 | } | |
182 | ||
183 | static int pca953x_set_direction(struct udevice *dev, unsigned offset, int dir) | |
184 | { | |
185 | struct pca953x_info *info = dev_get_platdata(dev); | |
186 | int bank = offset / BANK_SZ; | |
187 | int off = offset % BANK_SZ; | |
188 | u8 val; | |
189 | int ret; | |
190 | ||
191 | if (dir == PCA953X_DIRECTION_IN) | |
192 | val = info->reg_direction[bank] | (1 << off); | |
193 | else | |
194 | val = info->reg_direction[bank] & ~(1 << off); | |
195 | ||
196 | ret = pca953x_write_single(dev, PCA953X_DIRECTION, val, offset); | |
197 | if (ret) | |
198 | return ret; | |
199 | ||
200 | info->reg_direction[bank] = val; | |
201 | ||
202 | return 0; | |
203 | } | |
204 | ||
205 | static int pca953x_direction_input(struct udevice *dev, unsigned offset) | |
206 | { | |
207 | return pca953x_set_direction(dev, offset, PCA953X_DIRECTION_IN); | |
208 | } | |
209 | ||
210 | static int pca953x_direction_output(struct udevice *dev, unsigned offset, | |
211 | int value) | |
212 | { | |
213 | /* Configure output value. */ | |
214 | pca953x_set_value(dev, offset, value); | |
215 | ||
216 | /* Configure direction as output. */ | |
217 | pca953x_set_direction(dev, offset, PCA953X_DIRECTION_OUT); | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
222 | static int pca953x_get_function(struct udevice *dev, unsigned offset) | |
223 | { | |
224 | if (pca953x_is_output(dev, offset)) | |
225 | return GPIOF_OUTPUT; | |
226 | else | |
227 | return GPIOF_INPUT; | |
228 | } | |
229 | ||
230 | static int pca953x_xlate(struct udevice *dev, struct gpio_desc *desc, | |
231 | struct fdtdec_phandle_args *args) | |
232 | { | |
233 | desc->offset = args->args[0]; | |
234 | desc->flags = args->args[1] & GPIO_ACTIVE_LOW ? GPIOD_ACTIVE_LOW : 0; | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
239 | static const struct dm_gpio_ops pca953x_ops = { | |
240 | .direction_input = pca953x_direction_input, | |
241 | .direction_output = pca953x_direction_output, | |
242 | .get_value = pca953x_get_value, | |
243 | .set_value = pca953x_set_value, | |
244 | .get_function = pca953x_get_function, | |
245 | .xlate = pca953x_xlate, | |
246 | }; | |
247 | ||
248 | static int pca953x_probe(struct udevice *dev) | |
249 | { | |
250 | struct pca953x_info *info = dev_get_platdata(dev); | |
251 | struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev); | |
252 | struct dm_i2c_chip *chip = dev_get_parent_platdata(dev); | |
253 | char name[32], *str; | |
254 | int addr; | |
255 | ulong driver_data; | |
256 | int ret; | |
257 | ||
258 | if (!info) { | |
259 | dev_err(dev, "platdata not ready\n"); | |
260 | return -ENOMEM; | |
261 | } | |
262 | ||
263 | if (!chip) { | |
264 | dev_err(dev, "i2c not ready\n"); | |
265 | return -ENODEV; | |
266 | } | |
267 | ||
268 | addr = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "reg", 0); | |
269 | if (addr == 0) | |
270 | return -ENODEV; | |
271 | ||
272 | info->addr = addr; | |
273 | ||
274 | driver_data = dev_get_driver_data(dev); | |
275 | ||
276 | info->gpio_count = driver_data & PCA_GPIO_MASK; | |
277 | if (info->gpio_count > MAX_BANK * BANK_SZ) { | |
278 | dev_err(dev, "Max support %d pins now\n", MAX_BANK * BANK_SZ); | |
279 | return -EINVAL; | |
280 | } | |
281 | ||
282 | info->chip_type = PCA_CHIP_TYPE(driver_data); | |
283 | if (info->chip_type != PCA953X_TYPE) { | |
284 | dev_err(dev, "Only support PCA953X chip type now.\n"); | |
285 | return -EINVAL; | |
286 | } | |
287 | ||
288 | info->bank_count = DIV_ROUND_UP(info->gpio_count, BANK_SZ); | |
289 | ||
290 | ret = pca953x_read_regs(dev, PCA953X_OUTPUT, info->reg_output); | |
291 | if (ret) { | |
292 | dev_err(dev, "Error reading output register\n"); | |
293 | return ret; | |
294 | } | |
295 | ||
296 | ret = pca953x_read_regs(dev, PCA953X_DIRECTION, info->reg_direction); | |
297 | if (ret) { | |
298 | dev_err(dev, "Error reading direction register\n"); | |
299 | return ret; | |
300 | } | |
301 | ||
302 | snprintf(name, sizeof(name), "gpio@%x_", info->addr); | |
303 | str = strdup(name); | |
304 | if (!str) | |
305 | return -ENOMEM; | |
306 | uc_priv->bank_name = str; | |
307 | uc_priv->gpio_count = info->gpio_count; | |
308 | ||
309 | dev_dbg(dev, "%s is ready\n", str); | |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
314 | #define OF_953X(__nrgpio, __int) (ulong)(__nrgpio | PCA953X_TYPE | __int) | |
315 | #define OF_957X(__nrgpio, __int) (ulong)(__nrgpio | PCA957X_TYPE | __int) | |
316 | ||
317 | static const struct udevice_id pca953x_ids[] = { | |
318 | { .compatible = "nxp,pca9505", .data = OF_953X(40, PCA_INT), }, | |
319 | { .compatible = "nxp,pca9534", .data = OF_953X(8, PCA_INT), }, | |
320 | { .compatible = "nxp,pca9535", .data = OF_953X(16, PCA_INT), }, | |
321 | { .compatible = "nxp,pca9536", .data = OF_953X(4, 0), }, | |
322 | { .compatible = "nxp,pca9537", .data = OF_953X(4, PCA_INT), }, | |
323 | { .compatible = "nxp,pca9538", .data = OF_953X(8, PCA_INT), }, | |
324 | { .compatible = "nxp,pca9539", .data = OF_953X(16, PCA_INT), }, | |
325 | { .compatible = "nxp,pca9554", .data = OF_953X(8, PCA_INT), }, | |
326 | { .compatible = "nxp,pca9555", .data = OF_953X(16, PCA_INT), }, | |
327 | { .compatible = "nxp,pca9556", .data = OF_953X(8, 0), }, | |
328 | { .compatible = "nxp,pca9557", .data = OF_953X(8, 0), }, | |
329 | { .compatible = "nxp,pca9574", .data = OF_957X(8, PCA_INT), }, | |
330 | { .compatible = "nxp,pca9575", .data = OF_957X(16, PCA_INT), }, | |
331 | { .compatible = "nxp,pca9698", .data = OF_953X(40, 0), }, | |
332 | ||
333 | { .compatible = "maxim,max7310", .data = OF_953X(8, 0), }, | |
334 | { .compatible = "maxim,max7312", .data = OF_953X(16, PCA_INT), }, | |
335 | { .compatible = "maxim,max7313", .data = OF_953X(16, PCA_INT), }, | |
336 | { .compatible = "maxim,max7315", .data = OF_953X(8, PCA_INT), }, | |
337 | ||
338 | { .compatible = "ti,pca6107", .data = OF_953X(8, PCA_INT), }, | |
339 | { .compatible = "ti,tca6408", .data = OF_953X(8, PCA_INT), }, | |
340 | { .compatible = "ti,tca6416", .data = OF_953X(16, PCA_INT), }, | |
341 | { .compatible = "ti,tca6424", .data = OF_953X(24, PCA_INT), }, | |
342 | ||
343 | { .compatible = "onsemi,pca9654", .data = OF_953X(8, PCA_INT), }, | |
344 | ||
345 | { .compatible = "exar,xra1202", .data = OF_953X(8, 0), }, | |
346 | { } | |
347 | }; | |
348 | ||
349 | U_BOOT_DRIVER(pca953x) = { | |
350 | .name = "pca953x", | |
351 | .id = UCLASS_GPIO, | |
352 | .ops = &pca953x_ops, | |
353 | .probe = pca953x_probe, | |
354 | .platdata_auto_alloc_size = sizeof(struct pca953x_info), | |
355 | .of_match = pca953x_ids, | |
356 | }; |