summaryrefslogtreecommitdiffstats
path: root/drivers/gpio/gpio-fxl6408.c
blob: c14b5cc5e5192dd6122777ab32c89869b47e47c5 (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
// SPDX-License-Identifier: GPL-2.0-only
/*
 * FXL6408 GPIO driver
 *
 * Copyright 2023 Toradex
 *
 * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
 */

#include <linux/err.h>
#include <linux/gpio/regmap.h>
#include <linux/i2c.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/regmap.h>

#define FXL6408_REG_DEVICE_ID		0x01
#define FXL6408_MF_FAIRCHILD		0b101
#define FXL6408_MF_SHIFT		5

/* Bits set here indicate that the GPIO is an output. */
#define FXL6408_REG_IO_DIR		0x03

/*
 * Bits set here, when the corresponding bit of IO_DIR is set, drive
 * the output high instead of low.
 */
#define FXL6408_REG_OUTPUT		0x05

/* Bits here make the output High-Z, instead of the OUTPUT value. */
#define FXL6408_REG_OUTPUT_HIGH_Z	0x07

/* Returns the current status (1 = HIGH) of the input pins. */
#define FXL6408_REG_INPUT_STATUS	0x0f

/*
 * Return the current interrupt status
 * This bit is HIGH if input GPIO != default state (register 09h).
 * The flag is cleared after being read (bit returns to 0).
 * The input must go back to default state and change again before this flag is raised again.
 */
#define FXL6408_REG_INT_STS		0x13

#define FXL6408_NGPIO			8

static const struct regmap_range rd_range[] = {
	{ FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
	{ FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
	{ FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
};

static const struct regmap_range wr_range[] = {
	{ FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
	{ FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
	{ FXL6408_REG_OUTPUT_HIGH_Z, FXL6408_REG_OUTPUT_HIGH_Z },
};

static const struct regmap_range volatile_range[] = {
	{ FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
	{ FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
};

static const struct regmap_access_table rd_table = {
	.yes_ranges = rd_range,
	.n_yes_ranges = ARRAY_SIZE(rd_range),
};

static const struct regmap_access_table wr_table = {
	.yes_ranges = wr_range,
	.n_yes_ranges = ARRAY_SIZE(wr_range),
};

static const struct regmap_access_table volatile_table = {
	.yes_ranges = volatile_range,
	.n_yes_ranges = ARRAY_SIZE(volatile_range),
};

static const struct regmap_config regmap = {
	.reg_bits = 8,
	.val_bits = 8,

	.max_register = FXL6408_REG_INT_STS,
	.wr_table = &wr_table,
	.rd_table = &rd_table,
	.volatile_table = &volatile_table,

	.cache_type = REGCACHE_RBTREE,
	.num_reg_defaults_raw = FXL6408_REG_INT_STS + 1,
};

static int fxl6408_identify(struct device *dev, struct regmap *regmap)
{
	int val, ret;

	ret = regmap_read(regmap, FXL6408_REG_DEVICE_ID, &val);
	if (ret)
		return dev_err_probe(dev, ret, "error reading DEVICE_ID\n");
	if (val >> FXL6408_MF_SHIFT != FXL6408_MF_FAIRCHILD)
		return dev_err_probe(dev, -ENODEV, "invalid device id 0x%02x\n", val);

	return 0;
}

static int fxl6408_probe(struct i2c_client *client)
{
	struct device *dev = &client->dev;
	int ret;
	struct gpio_regmap_config gpio_config = {
		.parent = dev,
		.ngpio = FXL6408_NGPIO,
		.reg_dat_base = GPIO_REGMAP_ADDR(FXL6408_REG_INPUT_STATUS),
		.reg_set_base = GPIO_REGMAP_ADDR(FXL6408_REG_OUTPUT),
		.reg_dir_out_base = GPIO_REGMAP_ADDR(FXL6408_REG_IO_DIR),
		.ngpio_per_reg = FXL6408_NGPIO,
	};

	gpio_config.regmap = devm_regmap_init_i2c(client, &regmap);
	if (IS_ERR(gpio_config.regmap))
		return dev_err_probe(dev, PTR_ERR(gpio_config.regmap),
				     "failed to allocate register map\n");

	ret = fxl6408_identify(dev, gpio_config.regmap);
	if (ret)
		return ret;

	/* Disable High-Z of outputs, so that our OUTPUT updates actually take effect. */
	ret = regmap_write(gpio_config.regmap, FXL6408_REG_OUTPUT_HIGH_Z, 0);
	if (ret)
		return dev_err_probe(dev, ret, "failed to write 'output high Z' register\n");

	return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
}

static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = {
	{ .compatible = "fcs,fxl6408" },
	{ }
};
MODULE_DEVICE_TABLE(of, fxl6408_dt_ids);

static const struct i2c_device_id fxl6408_id[] = {
	{ "fxl6408", 0 },
	{ }
};
MODULE_DEVICE_TABLE(i2c, fxl6408_id);

static struct i2c_driver fxl6408_driver = {
	.driver = {
		.name	= "fxl6408",
		.of_match_table = fxl6408_dt_ids,
	},
	.probe		= fxl6408_probe,
	.id_table	= fxl6408_id,
};
module_i2c_driver(fxl6408_driver);

MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
MODULE_DESCRIPTION("FXL6408 GPIO driver");
MODULE_LICENSE("GPL");