1*03810031SEmanuele Ghidoli // SPDX-License-Identifier: GPL-2.0-only
2*03810031SEmanuele Ghidoli /*
3*03810031SEmanuele Ghidoli * FXL6408 GPIO driver
4*03810031SEmanuele Ghidoli *
5*03810031SEmanuele Ghidoli * Copyright 2023 Toradex
6*03810031SEmanuele Ghidoli *
7*03810031SEmanuele Ghidoli * Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
8*03810031SEmanuele Ghidoli */
9*03810031SEmanuele Ghidoli
10*03810031SEmanuele Ghidoli #include <linux/err.h>
11*03810031SEmanuele Ghidoli #include <linux/gpio/regmap.h>
12*03810031SEmanuele Ghidoli #include <linux/i2c.h>
13*03810031SEmanuele Ghidoli #include <linux/kernel.h>
14*03810031SEmanuele Ghidoli #include <linux/module.h>
15*03810031SEmanuele Ghidoli #include <linux/regmap.h>
16*03810031SEmanuele Ghidoli
17*03810031SEmanuele Ghidoli #define FXL6408_REG_DEVICE_ID 0x01
18*03810031SEmanuele Ghidoli #define FXL6408_MF_FAIRCHILD 0b101
19*03810031SEmanuele Ghidoli #define FXL6408_MF_SHIFT 5
20*03810031SEmanuele Ghidoli
21*03810031SEmanuele Ghidoli /* Bits set here indicate that the GPIO is an output. */
22*03810031SEmanuele Ghidoli #define FXL6408_REG_IO_DIR 0x03
23*03810031SEmanuele Ghidoli
24*03810031SEmanuele Ghidoli /*
25*03810031SEmanuele Ghidoli * Bits set here, when the corresponding bit of IO_DIR is set, drive
26*03810031SEmanuele Ghidoli * the output high instead of low.
27*03810031SEmanuele Ghidoli */
28*03810031SEmanuele Ghidoli #define FXL6408_REG_OUTPUT 0x05
29*03810031SEmanuele Ghidoli
30*03810031SEmanuele Ghidoli /* Bits here make the output High-Z, instead of the OUTPUT value. */
31*03810031SEmanuele Ghidoli #define FXL6408_REG_OUTPUT_HIGH_Z 0x07
32*03810031SEmanuele Ghidoli
33*03810031SEmanuele Ghidoli /* Returns the current status (1 = HIGH) of the input pins. */
34*03810031SEmanuele Ghidoli #define FXL6408_REG_INPUT_STATUS 0x0f
35*03810031SEmanuele Ghidoli
36*03810031SEmanuele Ghidoli /*
37*03810031SEmanuele Ghidoli * Return the current interrupt status
38*03810031SEmanuele Ghidoli * This bit is HIGH if input GPIO != default state (register 09h).
39*03810031SEmanuele Ghidoli * The flag is cleared after being read (bit returns to 0).
40*03810031SEmanuele Ghidoli * The input must go back to default state and change again before this flag is raised again.
41*03810031SEmanuele Ghidoli */
42*03810031SEmanuele Ghidoli #define FXL6408_REG_INT_STS 0x13
43*03810031SEmanuele Ghidoli
44*03810031SEmanuele Ghidoli #define FXL6408_NGPIO 8
45*03810031SEmanuele Ghidoli
46*03810031SEmanuele Ghidoli static const struct regmap_range rd_range[] = {
47*03810031SEmanuele Ghidoli { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
48*03810031SEmanuele Ghidoli { FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
49*03810031SEmanuele Ghidoli { FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
50*03810031SEmanuele Ghidoli };
51*03810031SEmanuele Ghidoli
52*03810031SEmanuele Ghidoli static const struct regmap_range wr_range[] = {
53*03810031SEmanuele Ghidoli { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
54*03810031SEmanuele Ghidoli { FXL6408_REG_IO_DIR, FXL6408_REG_OUTPUT },
55*03810031SEmanuele Ghidoli { FXL6408_REG_OUTPUT_HIGH_Z, FXL6408_REG_OUTPUT_HIGH_Z },
56*03810031SEmanuele Ghidoli };
57*03810031SEmanuele Ghidoli
58*03810031SEmanuele Ghidoli static const struct regmap_range volatile_range[] = {
59*03810031SEmanuele Ghidoli { FXL6408_REG_DEVICE_ID, FXL6408_REG_DEVICE_ID },
60*03810031SEmanuele Ghidoli { FXL6408_REG_INPUT_STATUS, FXL6408_REG_INPUT_STATUS },
61*03810031SEmanuele Ghidoli };
62*03810031SEmanuele Ghidoli
63*03810031SEmanuele Ghidoli static const struct regmap_access_table rd_table = {
64*03810031SEmanuele Ghidoli .yes_ranges = rd_range,
65*03810031SEmanuele Ghidoli .n_yes_ranges = ARRAY_SIZE(rd_range),
66*03810031SEmanuele Ghidoli };
67*03810031SEmanuele Ghidoli
68*03810031SEmanuele Ghidoli static const struct regmap_access_table wr_table = {
69*03810031SEmanuele Ghidoli .yes_ranges = wr_range,
70*03810031SEmanuele Ghidoli .n_yes_ranges = ARRAY_SIZE(wr_range),
71*03810031SEmanuele Ghidoli };
72*03810031SEmanuele Ghidoli
73*03810031SEmanuele Ghidoli static const struct regmap_access_table volatile_table = {
74*03810031SEmanuele Ghidoli .yes_ranges = volatile_range,
75*03810031SEmanuele Ghidoli .n_yes_ranges = ARRAY_SIZE(volatile_range),
76*03810031SEmanuele Ghidoli };
77*03810031SEmanuele Ghidoli
78*03810031SEmanuele Ghidoli static const struct regmap_config regmap = {
79*03810031SEmanuele Ghidoli .reg_bits = 8,
80*03810031SEmanuele Ghidoli .val_bits = 8,
81*03810031SEmanuele Ghidoli
82*03810031SEmanuele Ghidoli .max_register = FXL6408_REG_INT_STS,
83*03810031SEmanuele Ghidoli .wr_table = &wr_table,
84*03810031SEmanuele Ghidoli .rd_table = &rd_table,
85*03810031SEmanuele Ghidoli .volatile_table = &volatile_table,
86*03810031SEmanuele Ghidoli
87*03810031SEmanuele Ghidoli .cache_type = REGCACHE_RBTREE,
88*03810031SEmanuele Ghidoli .num_reg_defaults_raw = FXL6408_REG_INT_STS + 1,
89*03810031SEmanuele Ghidoli };
90*03810031SEmanuele Ghidoli
fxl6408_identify(struct device * dev,struct regmap * regmap)91*03810031SEmanuele Ghidoli static int fxl6408_identify(struct device *dev, struct regmap *regmap)
92*03810031SEmanuele Ghidoli {
93*03810031SEmanuele Ghidoli int val, ret;
94*03810031SEmanuele Ghidoli
95*03810031SEmanuele Ghidoli ret = regmap_read(regmap, FXL6408_REG_DEVICE_ID, &val);
96*03810031SEmanuele Ghidoli if (ret)
97*03810031SEmanuele Ghidoli return dev_err_probe(dev, ret, "error reading DEVICE_ID\n");
98*03810031SEmanuele Ghidoli if (val >> FXL6408_MF_SHIFT != FXL6408_MF_FAIRCHILD)
99*03810031SEmanuele Ghidoli return dev_err_probe(dev, -ENODEV, "invalid device id 0x%02x\n", val);
100*03810031SEmanuele Ghidoli
101*03810031SEmanuele Ghidoli return 0;
102*03810031SEmanuele Ghidoli }
103*03810031SEmanuele Ghidoli
fxl6408_probe(struct i2c_client * client)104*03810031SEmanuele Ghidoli static int fxl6408_probe(struct i2c_client *client)
105*03810031SEmanuele Ghidoli {
106*03810031SEmanuele Ghidoli struct device *dev = &client->dev;
107*03810031SEmanuele Ghidoli int ret;
108*03810031SEmanuele Ghidoli struct gpio_regmap_config gpio_config = {
109*03810031SEmanuele Ghidoli .parent = dev,
110*03810031SEmanuele Ghidoli .ngpio = FXL6408_NGPIO,
111*03810031SEmanuele Ghidoli .reg_dat_base = GPIO_REGMAP_ADDR(FXL6408_REG_INPUT_STATUS),
112*03810031SEmanuele Ghidoli .reg_set_base = GPIO_REGMAP_ADDR(FXL6408_REG_OUTPUT),
113*03810031SEmanuele Ghidoli .reg_dir_out_base = GPIO_REGMAP_ADDR(FXL6408_REG_IO_DIR),
114*03810031SEmanuele Ghidoli .ngpio_per_reg = FXL6408_NGPIO,
115*03810031SEmanuele Ghidoli };
116*03810031SEmanuele Ghidoli
117*03810031SEmanuele Ghidoli gpio_config.regmap = devm_regmap_init_i2c(client, ®map);
118*03810031SEmanuele Ghidoli if (IS_ERR(gpio_config.regmap))
119*03810031SEmanuele Ghidoli return dev_err_probe(dev, PTR_ERR(gpio_config.regmap),
120*03810031SEmanuele Ghidoli "failed to allocate register map\n");
121*03810031SEmanuele Ghidoli
122*03810031SEmanuele Ghidoli ret = fxl6408_identify(dev, gpio_config.regmap);
123*03810031SEmanuele Ghidoli if (ret)
124*03810031SEmanuele Ghidoli return ret;
125*03810031SEmanuele Ghidoli
126*03810031SEmanuele Ghidoli /* Disable High-Z of outputs, so that our OUTPUT updates actually take effect. */
127*03810031SEmanuele Ghidoli ret = regmap_write(gpio_config.regmap, FXL6408_REG_OUTPUT_HIGH_Z, 0);
128*03810031SEmanuele Ghidoli if (ret)
129*03810031SEmanuele Ghidoli return dev_err_probe(dev, ret, "failed to write 'output high Z' register\n");
130*03810031SEmanuele Ghidoli
131*03810031SEmanuele Ghidoli return PTR_ERR_OR_ZERO(devm_gpio_regmap_register(dev, &gpio_config));
132*03810031SEmanuele Ghidoli }
133*03810031SEmanuele Ghidoli
134*03810031SEmanuele Ghidoli static const __maybe_unused struct of_device_id fxl6408_dt_ids[] = {
135*03810031SEmanuele Ghidoli { .compatible = "fcs,fxl6408" },
136*03810031SEmanuele Ghidoli { }
137*03810031SEmanuele Ghidoli };
138*03810031SEmanuele Ghidoli MODULE_DEVICE_TABLE(of, fxl6408_dt_ids);
139*03810031SEmanuele Ghidoli
140*03810031SEmanuele Ghidoli static const struct i2c_device_id fxl6408_id[] = {
141*03810031SEmanuele Ghidoli { "fxl6408", 0 },
142*03810031SEmanuele Ghidoli { }
143*03810031SEmanuele Ghidoli };
144*03810031SEmanuele Ghidoli MODULE_DEVICE_TABLE(i2c, fxl6408_id);
145*03810031SEmanuele Ghidoli
146*03810031SEmanuele Ghidoli static struct i2c_driver fxl6408_driver = {
147*03810031SEmanuele Ghidoli .driver = {
148*03810031SEmanuele Ghidoli .name = "fxl6408",
149*03810031SEmanuele Ghidoli .of_match_table = fxl6408_dt_ids,
150*03810031SEmanuele Ghidoli },
151*03810031SEmanuele Ghidoli .probe = fxl6408_probe,
152*03810031SEmanuele Ghidoli .id_table = fxl6408_id,
153*03810031SEmanuele Ghidoli };
154*03810031SEmanuele Ghidoli module_i2c_driver(fxl6408_driver);
155*03810031SEmanuele Ghidoli
156*03810031SEmanuele Ghidoli MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
157*03810031SEmanuele Ghidoli MODULE_DESCRIPTION("FXL6408 GPIO driver");
158*03810031SEmanuele Ghidoli MODULE_LICENSE("GPL");
159