]> Git Repo - u-boot.git/blob - drivers/gpio/gpio-fxl6408.c
Merge branch 'u-boot-nand-20241012' of https://source.denx.de/u-boot/custodians/u...
[u-boot.git] / drivers / gpio / gpio-fxl6408.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  *  Copyright (C) 2021 Toradex
4  *  Copyright (C) 2016 Broadcom
5  */
6
7 /**
8  * DOC: FXL6408 I2C to GPIO expander.
9  *
10  * This chip has 8 GPIO lines out of it, and is controlled by an I2C
11  * bus (a pair of lines), providing 4x expansion of GPIO lines. It
12  * also provides an interrupt line out for notifying of state changes.
13  *
14  * Any preconfigured state will be left in place until the GPIO lines
15  * get activated. At power on, everything is treated as an input,
16  * default input is HIGH and pulled-up, all interrupts are masked.
17  *
18  * Documentation can be found at:
19  * ------------------------------
20  *
21  * https://www.fairchildsemi.com/datasheets/FX/FXL6408.pdf
22  *
23  * This driver bases on:
24  * ---------------------
25  *
26  * - the original driver by Eric Anholt <[email protected]>:
27  *   https://patchwork.kernel.org/patch/9148419/
28  * - the Toradex version by Max Krummenacher <[email protected]>:
29  *   http://git.toradex.com/cgit/linux-toradex.git/tree/drivers/gpio/gpio-fxl6408.c?h=toradex_5.4-2.3.x-imx
30  * - the U-Boot PCA953x driver by Peng Fan <[email protected]>:
31  *   drivers/gpio/pca953x_gpio.c
32  *
33  * TODO:
34  *   - Add interrupts support
35  *   - Replace deprecated callbacks direction_input/output() with set_flags()
36  */
37
38 #include <asm-generic/gpio.h>
39 #include <asm/global_data.h>
40 #include <dm.h>
41 #include <dm/device_compat.h>
42 #include <dt-bindings/gpio/gpio.h>
43 #include <i2c.h>
44 #include <linux/bitops.h>
45 #include <log.h>
46
47 #define REG_DEVID_CTRL          0x1
48 # define SW_RST                 BIT(0)
49 # define RST_INT                BIT(1)
50 /** 0b101 is the Manufacturer's ID assigned to Fairchild by Nokia */
51 # define MF_ID_FAIRCHILD        5
52
53 /** Bits set here indicate that the GPIO is an output */
54 #define REG_IO_DIR              0x3
55
56 /**
57  * REG_OUT_STATE - a high-output state register address
58  *
59  * Bits set here, when the corresponding bit of REG_IO_DIR is set,
60  * drive the output high instead of low.
61  */
62 #define REG_OUT_STATE           0x5
63
64 /** Bits here make the output High-Z, instead of the OUTPUT value */
65 #define REG_OUT_HIGH_Z          0x7
66
67 /**
68  * REG_IN_DEFAULT_STATE - an interrupt state register address
69  *
70  * Bits here define the expected input state of the GPIO.
71  * INTERRUPT_STATUS bits will be set when the INPUT transitions away
72  * from this value.
73  */
74 #define REG_IN_DEFAULT_STATE    0x9
75
76 /**
77  * REG_PULL_ENABLE - a pull-up/down enable state register address
78  *
79  * Bits here enable either pull up or pull down according to
80  * REG_PULL_MODE.
81  */
82 #define REG_PULL_ENABLE         0xb
83
84 /**
85  * REG_PULL_MODE - a pull-up/pull-down mode state register address
86  *
87  * Bits set here selects a pull-up/pull-down state of pin, which
88  * is configured as Input and the corresponding REG_PULL_ENABLE bit is
89  * set.
90  */
91 #define REG_PULL_MODE           0xd
92
93 /** Returns the current status (1 = HIGH) of the input pins */
94 #define REG_IN_STATUS           0xf
95
96 /** Mask of pins which can generate interrupts */
97 #define REG_INT_MASK            0x11
98
99 /** Mask of pins which have generated an interrupt. Cleared on read */
100 #define REG_INT_STATUS          0x13
101
102 /* Manufacturer's ID getting from Device ID & Ctrl register */
103 enum {
104         MF_ID_MASK = GENMASK(7, 5),
105         MF_ID_SHIFT = 5,
106 };
107
108 /* Firmware revision getting from Device ID & Ctrl register */
109 enum {
110         FW_REV_MASK = GENMASK(4, 2),
111         FW_REV_SHIFT = 2,
112 };
113
114 enum io_direction {
115         DIR_IN = 0,
116         DIR_OUT = 1,
117 };
118
119 /**
120  * struct fxl6408_info - Data for fxl6408
121  *
122  * @dev: udevice structure for the device
123  * @addr: i2c slave address
124  * @device_id: hold the value of device id register
125  * @reg_io_dir: hold the value of direction register
126  * @reg_output: hold the value of output register
127  */
128 struct fxl6408_info {
129         struct udevice *dev;
130         int addr;
131         u8 device_id;
132         u8 reg_io_dir;
133         u8 reg_output;
134 };
135
136 static inline int fxl6408_write(struct udevice *dev, int reg, u8 val)
137 {
138         return dm_i2c_write(dev, reg, &val, 1);
139 }
140
141 static int fxl6408_read(struct udevice *dev, int reg)
142 {
143         int ret;
144         u8 tmp;
145
146         ret = dm_i2c_read(dev, reg, &tmp, 1);
147         if (!ret)
148                 ret = tmp;
149
150         return ret;
151 }
152
153 /**
154  * fxl6408_is_output() - check whether the gpio configures as either
155  *                       output or input.
156  *
157  * @dev: an instance of a driver
158  * @offset: a gpio offset
159  *
160  * Return: false - input, true - output.
161  */
162 static bool fxl6408_is_output(struct udevice *dev, int offset)
163 {
164         struct fxl6408_info *info = dev_get_plat(dev);
165
166         return info->reg_io_dir & BIT(offset);
167 }
168
169 static int fxl6408_get_value(struct udevice *dev, uint offset)
170 {
171         int ret, reg = fxl6408_is_output(dev, offset) ? REG_OUT_STATE : REG_IN_STATUS;
172
173         ret = fxl6408_read(dev, reg);
174         if (ret < 0)
175                 return ret;
176
177         return !!(ret & BIT(offset));
178 }
179
180 static int fxl6408_set_value(struct udevice *dev, uint offset, int value)
181 {
182         struct fxl6408_info *info = dev_get_plat(dev);
183         u8 val;
184         int ret;
185
186         if (value)
187                 val = info->reg_output | BIT(offset);
188         else
189                 val = info->reg_output & ~BIT(offset);
190
191         ret = fxl6408_write(dev, REG_OUT_STATE, val);
192         if (ret < 0)
193                 return ret;
194
195         info->reg_output = val;
196
197         return 0;
198 }
199
200 static int fxl6408_set_direction(struct udevice *dev, uint offset,
201                                  enum io_direction dir)
202 {
203         struct fxl6408_info *info = dev_get_plat(dev);
204         u8 val;
205         int ret;
206
207         if (dir == DIR_IN)
208                 val = info->reg_io_dir & ~BIT(offset);
209         else
210                 val = info->reg_io_dir | BIT(offset);
211
212         ret = fxl6408_write(dev, REG_IO_DIR, val);
213         if (ret < 0)
214                 return ret;
215
216         info->reg_io_dir = val;
217
218         return 0;
219 }
220
221 static int fxl6408_direction_input(struct udevice *dev, uint offset)
222 {
223         return fxl6408_set_direction(dev, offset, DIR_IN);
224 }
225
226 static int fxl6408_direction_output(struct udevice *dev, uint offset, int value)
227 {
228         int ret;
229
230         /* Configure output value */
231         ret = fxl6408_set_value(dev, offset, value);
232         if (ret < 0)
233                 return ret;
234
235         /* Configure direction as output */
236         fxl6408_set_direction(dev, offset, DIR_OUT);
237
238         return 0;
239 }
240
241 static int fxl6408_get_function(struct udevice *dev, uint offset)
242 {
243         if (fxl6408_is_output(dev, offset))
244                 return GPIOF_OUTPUT;
245
246         return GPIOF_INPUT;
247 }
248
249 static int fxl6408_xlate(struct udevice *dev, struct gpio_desc *desc,
250                          struct ofnode_phandle_args *args)
251 {
252         desc->offset = args->args[0];
253         desc->flags = args->args[1] & GPIO_ACTIVE_LOW ? GPIOD_ACTIVE_LOW : 0;
254
255         return 0;
256 }
257
258 static const struct dm_gpio_ops fxl6408_ops = {
259         .direction_input        = fxl6408_direction_input,
260         .direction_output       = fxl6408_direction_output,
261         .get_value              = fxl6408_get_value,
262         .set_value              = fxl6408_set_value,
263         .get_function           = fxl6408_get_function,
264         .xlate                  = fxl6408_xlate,
265 };
266
267 static int fxl6408_probe(struct udevice *dev)
268 {
269         struct fxl6408_info *info = dev_get_plat(dev);
270         struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
271         char bank_name[32], *tmp_str;
272         int addr, ret, size;
273         u32 val32;
274
275         addr = dev_read_addr(dev);
276         if (addr == 0)
277                 return -EINVAL;
278
279         info->addr = addr;
280
281         /*
282          * Check the device ID register to see if it's responding.
283          * This also clears RST_INT as a side effect, so we won't get
284          * the "we've been power cycled" interrupt once interrupts
285          * being enabled.
286          */
287         ret = fxl6408_read(dev, REG_DEVID_CTRL);
288         if (ret < 0) {
289                 dev_err(dev, "FXL6408 probe returned %d\n", ret);
290                 return ret;
291         }
292
293         if ((ret & MF_ID_MASK) >> MF_ID_SHIFT != MF_ID_FAIRCHILD) {
294                 dev_err(dev, "FXL6408 probe: wrong Manufacturer's ID: 0x%02x\n", ret);
295                 return -ENXIO;
296         }
297         info->device_id = ret;
298
299         /*
300          * Disable High-Z of outputs, so that the OUTPUT updates
301          * actually take effect.
302          */
303         ret = fxl6408_write(dev, REG_OUT_HIGH_Z, (u8)0);
304         if (ret < 0) {
305                 dev_err(dev, "Error writing High-Z register\n");
306                 return ret;
307         }
308
309         /*
310          * If configured, set initial output state and direction,
311          * otherwise read them from the chip.
312          */
313         if (dev_read_u32(dev, "initial_io_dir", &val32)) {
314                 ret = fxl6408_read(dev, REG_IO_DIR);
315                 if (ret < 0) {
316                         dev_err(dev, "Error reading direction register\n");
317                         return ret;
318                 }
319                 info->reg_io_dir = ret;
320         } else {
321                 info->reg_io_dir = val32 & 0xFF;
322                 ret = fxl6408_write(dev, REG_IO_DIR, info->reg_io_dir);
323                 if (ret < 0) {
324                         dev_err(dev, "Error setting direction register\n");
325                         return ret;
326                 }
327         }
328
329         if (dev_read_u32(dev, "initial_output", &val32)) {
330                 ret = fxl6408_read(dev, REG_OUT_STATE);
331                 if (ret < 0) {
332                         dev_err(dev, "Error reading output register\n");
333                         return ret;
334                 }
335                 info->reg_output = ret;
336         } else {
337                 info->reg_output = val32 & 0xFF;
338                 ret = fxl6408_write(dev, REG_OUT_STATE, info->reg_output);
339                 if (ret < 0) {
340                         dev_err(dev, "Error setting output register\n");
341                         return ret;
342                 }
343         }
344
345         tmp_str = (char *)dev_read_prop(dev, "bank-name", &size);
346         if (tmp_str) {
347                 snprintf(bank_name, sizeof(bank_name), "%s@%x_", tmp_str,
348                          info->addr);
349         } else {
350                 snprintf(bank_name, sizeof(bank_name), "gpio@%x_", info->addr);
351         }
352
353         tmp_str = strdup(bank_name);
354         if (!tmp_str)
355                 return -ENOMEM;
356
357         uc_priv->bank_name = tmp_str;
358         uc_priv->gpio_count = dev_get_driver_data(dev);
359         uc_priv->gpio_base = -1;
360
361         dev_dbg(dev, "%s (FW rev. %d) is ready\n", bank_name,
362                 (info->device_id & FW_REV_MASK) >> FW_REV_SHIFT);
363
364         return 0;
365 }
366
367 static const struct udevice_id fxl6408_ids[] = {
368         { .compatible = "fcs,fxl6408", .data = 8 },
369         { }
370 };
371
372 U_BOOT_DRIVER(fxl6408_gpio) = {
373         .name = "fxl6408_gpio",
374         .id = UCLASS_GPIO,
375         .ops = &fxl6408_ops,
376         .probe = fxl6408_probe,
377         .of_match = fxl6408_ids,
378         .plat_auto = sizeof(struct fxl6408_info),
379 };
This page took 0.052159 seconds and 4 git commands to generate.