blob: ca7aa14eeb2310f9e601933419ba929acef8771c [file] [log] [blame]
Oleksandr Suvorov99dd2542021-11-21 18:05:16 +02001// 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
Michal Simek1be82af2023-05-17 09:17:16 +020030 * - the U-Boot PCA953x driver by Peng Fan <van.freenix@gmail.com>:
Oleksandr Suvorov99dd2542021-11-21 18:05:16 +020031 * 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 */
104enum {
105 MF_ID_MASK = GENMASK(7, 5),
106 MF_ID_SHIFT = 5,
107};
108
109/* Firmware revision getting from Device ID & Ctrl register */
110enum {
111 FW_REV_MASK = GENMASK(4, 2),
112 FW_REV_SHIFT = 2,
113};
114
115enum 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 */
129struct fxl6408_info {
130 struct udevice *dev;
131 int addr;
132 u8 device_id;
133 u8 reg_io_dir;
134 u8 reg_output;
135};
136
137static inline int fxl6408_write(struct udevice *dev, int reg, u8 val)
138{
139 return dm_i2c_write(dev, reg, &val, 1);
140}
141
142static 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 */
163static 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
170static 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
181static 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
201static 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
222static int fxl6408_direction_input(struct udevice *dev, uint offset)
223{
224 return fxl6408_set_direction(dev, offset, DIR_IN);
225}
226
227static 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
242static 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
250static 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
259static 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
268static 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
368static const struct udevice_id fxl6408_ids[] = {
369 { .compatible = "fcs,fxl6408", .data = 8 },
370 { }
371};
372
373U_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};