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