summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-fxl6408.c
blob: c8d2dff5f7bfd85e1c7b91b597f521e955d8ec16 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// SPDX-License-Identifier: GPL-2.0
/*
 *  Copyright (C) 2021 Toradex
 *  Copyright (C) 2016 Broadcom
 */

/**
 * DOC: FXL6408 I2C to GPIO expander.
 *
 * This chip has 8 GPIO lines out of it, and is controlled by an I2C
 * bus (a pair of lines), providing 4x expansion of GPIO lines. It
 * also provides an interrupt line out for notifying of state changes.
 *
 * Any preconfigured state will be left in place until the GPIO lines
 * get activated. At power on, everything is treated as an input,
 * default input is HIGH and pulled-up, all interrupts are masked.
 *
 * Documentation can be found at:
 * ------------------------------
 *
 * https://www.fairchildsemi.com/datasheets/FX/FXL6408.pdf
 *
 * This driver bases on:
 * ---------------------
 *
 * - the original driver by Eric Anholt <eric@anholt.net>:
 *   https://patchwork.kernel.org/patch/9148419/
 * - the Toradex version by Max Krummenacher <max.krummenacher@toradex.com>:
 *   http://git.toradex.com/cgit/linux-toradex.git/tree/drivers/gpio/gpio-fxl6408.c?h=toradex_5.4-2.3.x-imx
 * - the U-Boot PCA953x driver by Peng Fan <van.freenix@gmail.com>:
 *   drivers/gpio/pca953x_gpio.c
 *
 * TODO:
 *   - Add interrupts support
 *   - Replace deprecated callbacks direction_input/output() with set_flags()
 */

#include <asm-generic/gpio.h>
#include <asm/global_data.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <dt-bindings/gpio/gpio.h>
#include <i2c.h>
#include <linux/bitops.h>
#include <log.h>

#define REG_DEVID_CTRL		0x1
# define SW_RST			BIT(0)
# define RST_INT		BIT(1)
/** 0b101 is the Manufacturer's ID assigned to Fairchild by Nokia */
# define MF_ID_FAIRCHILD	5

/** Bits set here indicate that the GPIO is an output */
#define REG_IO_DIR		0x3

/**
 * REG_OUT_STATE - a high-output state register address
 *
 * Bits set here, when the corresponding bit of REG_IO_DIR is set,
 * drive the output high instead of low.
 */
#define REG_OUT_STATE		0x5

/** Bits here make the output High-Z, instead of the OUTPUT value */
#define REG_OUT_HIGH_Z		0x7

/**
 * REG_IN_DEFAULT_STATE - an interrupt state register address
 *
 * Bits here define the expected input state of the GPIO.
 * INTERRUPT_STATUS bits will be set when the INPUT transitions away
 * from this value.
 */
#define REG_IN_DEFAULT_STATE	0x9

/**
 * REG_PULL_ENABLE - a pull-up/down enable state register address
 *
 * Bits here enable either pull up or pull down according to
 * REG_PULL_MODE.
 */
#define REG_PULL_ENABLE		0xb

/**
 * REG_PULL_MODE - a pull-up/pull-down mode state register address
 *
 * Bits set here selects a pull-up/pull-down state of pin, which
 * is configured as Input and the corresponding REG_PULL_ENABLE bit is
 * set.
 */
#define REG_PULL_MODE		0xd

/** Returns the current status (1 = HIGH) of the input pins */
#define REG_IN_STATUS		0xf

/** Mask of pins which can generate interrupts */
#define REG_INT_MASK		0x11

/** Mask of pins which have generated an interrupt. Cleared on read */
#define REG_INT_STATUS		0x13

/* Manufacturer's ID getting from Device ID & Ctrl register */
enum {
	MF_ID_MASK = GENMASK(7, 5),
	MF_ID_SHIFT = 5,
};

/* Firmware revision getting from Device ID & Ctrl register */
enum {
	FW_REV_MASK = GENMASK(4, 2),
	FW_REV_SHIFT = 2,
};

enum io_direction {
	DIR_IN = 0,
	DIR_OUT = 1,
};

/**
 * struct fxl6408_info - Data for fxl6408
 *
 * @dev: udevice structure for the device
 * @addr: i2c slave address
 * @device_id: hold the value of device id register
 * @reg_io_dir: hold the value of direction register
 * @reg_output: hold the value of output register
 */
struct fxl6408_info {
	struct udevice *dev;
	int addr;
	u8 device_id;
	u8 reg_io_dir;
	u8 reg_output;
};

static inline int fxl6408_write(struct udevice *dev, int reg, u8 val)
{
	return dm_i2c_write(dev, reg, &val, 1);
}

static int fxl6408_read(struct udevice *dev, int reg)
{
	int ret;
	u8 tmp;

	ret = dm_i2c_read(dev, reg, &tmp, 1);
	if (!ret)
		ret = tmp;

	return ret;
}

/**
 * fxl6408_is_output() - check whether the gpio configures as either
 *			 output or input.
 *
 * @dev: an instance of a driver
 * @offset: a gpio offset
 *
 * Return: false - input, true - output.
 */
static bool fxl6408_is_output(struct udevice *dev, int offset)
{
	struct fxl6408_info *info = dev_get_plat(dev);

	return info->reg_io_dir & BIT(offset);
}

static int fxl6408_get_value(struct udevice *dev, uint offset)
{
	int ret, reg = fxl6408_is_output(dev, offset) ? REG_OUT_STATE : REG_IN_STATUS;

	ret = fxl6408_read(dev, reg);
	if (ret < 0)
		return ret;

	return !!(ret & BIT(offset));
}

static int fxl6408_set_value(struct udevice *dev, uint offset, int value)
{
	struct fxl6408_info *info = dev_get_plat(dev);
	u8 val;
	int ret;

	if (value)
		val = info->reg_output | BIT(offset);
	else
		val = info->reg_output & ~BIT(offset);

	ret = fxl6408_write(dev, REG_OUT_STATE, val);
	if (ret < 0)
		return ret;

	info->reg_output = val;

	return 0;
}

static int fxl6408_set_direction(struct udevice *dev, uint offset,
				 enum io_direction dir)
{
	struct fxl6408_info *info = dev_get_plat(dev);
	u8 val;
	int ret;

	if (dir == DIR_IN)
		val = info->reg_io_dir & ~BIT(offset);
	else
		val = info->reg_io_dir | BIT(offset);

	ret = fxl6408_write(dev, REG_IO_DIR, val);
	if (ret < 0)
		return ret;

	info->reg_io_dir = val;

	return 0;
}

static int fxl6408_direction_input(struct udevice *dev, uint offset)
{
	return fxl6408_set_direction(dev, offset, DIR_IN);
}

static int fxl6408_direction_output(struct udevice *dev, uint offset, int value)
{
	int ret;

	/* Configure output value */
	ret = fxl6408_set_value(dev, offset, value);
	if (ret < 0)
		return ret;

	/* Configure direction as output */
	fxl6408_set_direction(dev, offset, DIR_OUT);

	return 0;
}

static int fxl6408_get_function(struct udevice *dev, uint offset)
{
	if (fxl6408_is_output(dev, offset))
		return GPIOF_OUTPUT;

	return GPIOF_INPUT;
}

static int fxl6408_xlate(struct udevice *dev, struct gpio_desc *desc,
			 struct ofnode_phandle_args *args)
{
	desc->offset = args->args[0];
	desc->flags = args->args[1] & GPIO_ACTIVE_LOW ? GPIOD_ACTIVE_LOW : 0;

	return 0;
}

static const struct dm_gpio_ops fxl6408_ops = {
	.direction_input        = fxl6408_direction_input,
	.direction_output       = fxl6408_direction_output,
	.get_value              = fxl6408_get_value,
	.set_value              = fxl6408_set_value,
	.get_function           = fxl6408_get_function,
	.xlate                  = fxl6408_xlate,
};

static int fxl6408_probe(struct udevice *dev)
{
	struct fxl6408_info *info = dev_get_plat(dev);
	struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
	char bank_name[32], *tmp_str;
	int addr, ret, size;
	u32 val32;

	addr = dev_read_addr(dev);
	if (addr == 0)
		return -EINVAL;

	info->addr = addr;

	/*
	 * Check the device ID register to see if it's responding.
	 * This also clears RST_INT as a side effect, so we won't get
	 * the "we've been power cycled" interrupt once interrupts
	 * being enabled.
	 */
	ret = fxl6408_read(dev, REG_DEVID_CTRL);
	if (ret < 0) {
		dev_err(dev, "FXL6408 probe returned %d\n", ret);
		return ret;
	}

	if ((ret & MF_ID_MASK) >> MF_ID_SHIFT != MF_ID_FAIRCHILD) {
		dev_err(dev, "FXL6408 probe: wrong Manufacturer's ID: 0x%02x\n", ret);
		return -ENXIO;
	}
	info->device_id = ret;

	/*
	 * Disable High-Z of outputs, so that the OUTPUT updates
	 * actually take effect.
	 */
	ret = fxl6408_write(dev, REG_OUT_HIGH_Z, (u8)0);
	if (ret < 0) {
		dev_err(dev, "Error writing High-Z register\n");
		return ret;
	}

	/*
	 * If configured, set initial output state and direction,
	 * otherwise read them from the chip.
	 */
	if (dev_read_u32(dev, "initial_io_dir", &val32)) {
		ret = fxl6408_read(dev, REG_IO_DIR);
		if (ret < 0) {
			dev_err(dev, "Error reading direction register\n");
			return ret;
		}
		info->reg_io_dir = ret;
	} else {
		info->reg_io_dir = val32 & 0xFF;
		ret = fxl6408_write(dev, REG_IO_DIR, info->reg_io_dir);
		if (ret < 0) {
			dev_err(dev, "Error setting direction register\n");
			return ret;
		}
	}

	if (dev_read_u32(dev, "initial_output", &val32)) {
		ret = fxl6408_read(dev, REG_OUT_STATE);
		if (ret < 0) {
			dev_err(dev, "Error reading output register\n");
			return ret;
		}
		info->reg_output = ret;
	} else {
		info->reg_output = val32 & 0xFF;
		ret = fxl6408_write(dev, REG_OUT_STATE, info->reg_output);
		if (ret < 0) {
			dev_err(dev, "Error setting output register\n");
			return ret;
		}
	}

	tmp_str = (char *)dev_read_prop(dev, "bank-name", &size);
	if (tmp_str) {
		snprintf(bank_name, sizeof(bank_name), "%s@%x_", tmp_str,
			 info->addr);
	} else {
		snprintf(bank_name, sizeof(bank_name), "gpio@%x_", info->addr);
	}

	tmp_str = strdup(bank_name);
	if (!tmp_str)
		return -ENOMEM;

	uc_priv->bank_name = tmp_str;
	uc_priv->gpio_count = dev_get_driver_data(dev);
	uc_priv->gpio_base = -1;

	dev_dbg(dev, "%s (FW rev. %d) is ready\n", bank_name,
		(info->device_id & FW_REV_MASK) >> FW_REV_SHIFT);

	return 0;
}

static const struct udevice_id fxl6408_ids[] = {
	{ .compatible = "fcs,fxl6408", .data = 8 },
	{ }
};

U_BOOT_DRIVER(fxl6408_gpio) = {
	.name = "fxl6408_gpio",
	.id = UCLASS_GPIO,
	.ops = &fxl6408_ops,
	.probe = fxl6408_probe,
	.of_match = fxl6408_ids,
	.plat_auto = sizeof(struct fxl6408_info),
};