gpio: mcp230xx: Introduce new driver
[platform/kernel/u-boot.git] / drivers / gpio / mcp230xx_gpio.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (C) 2021, Collabora Ltd.
4  * Copyright (C) 2021, General Electric Company
5  * Author(s): Sebastian Reichel <sebastian.reichel@collabora.com>
6  */
7
8 #define LOG_CATEGORY UCLASS_GPIO
9
10 #include <common.h>
11 #include <errno.h>
12 #include <dm.h>
13 #include <i2c.h>
14 #include <asm/gpio.h>
15 #include <dm/device_compat.h>
16 #include <dt-bindings/gpio/gpio.h>
17
18 enum mcp230xx_type {
19         UNKNOWN = 0,
20         MCP23008,
21         MCP23017,
22         MCP23018,
23 };
24
25 #define MCP230XX_IODIR 0x00
26 #define MCP230XX_GPPU 0x06
27 #define MCP230XX_GPIO 0x09
28 #define MCP230XX_OLAT 0x0a
29
30 #define BANKSIZE 8
31
32 static int mcp230xx_read(struct udevice *dev, uint reg, uint offset)
33 {
34         struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
35         int bank = offset / BANKSIZE;
36         int mask = 1 << (offset % BANKSIZE);
37         int shift = (uc_priv->gpio_count / BANKSIZE) - 1;
38         int ret;
39
40         ret = dm_i2c_reg_read(dev, (reg << shift) | bank);
41         if (ret < 0)
42                 return ret;
43
44         return !!(ret & mask);
45 }
46
47 static int mcp230xx_write(struct udevice *dev, uint reg, uint offset, bool val)
48 {
49         struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
50         int bank = offset / BANKSIZE;
51         int mask = 1 << (offset % BANKSIZE);
52         int shift = (uc_priv->gpio_count / BANKSIZE) - 1;
53
54         return dm_i2c_reg_clrset(dev, (reg << shift) | bank, mask, val ? mask : 0);
55 }
56
57 static int mcp230xx_get_value(struct udevice *dev, uint offset)
58 {
59         int ret;
60
61         ret = mcp230xx_read(dev, MCP230XX_GPIO, offset);
62         if (ret < 0) {
63                 dev_err(dev, "%s error: %d\n", __func__, ret);
64                 return ret;
65         }
66
67         return ret;
68 }
69
70 static int mcp230xx_set_value(struct udevice *dev, uint offset, int val)
71 {
72         int ret;
73
74         ret = mcp230xx_write(dev, MCP230XX_GPIO, offset, val);
75         if (ret < 0) {
76                 dev_err(dev, "%s error: %d\n", __func__, ret);
77                 return ret;
78         }
79
80         return ret;
81 }
82
83 static int mcp230xx_get_flags(struct udevice *dev, unsigned int offset,
84                                   ulong *flags)
85 {
86         int direction, pullup;
87
88         pullup = mcp230xx_read(dev, MCP230XX_GPPU, offset);
89         if (pullup < 0) {
90                 dev_err(dev, "%s error: %d\n", __func__, pullup);
91                 return pullup;
92         }
93
94         direction = mcp230xx_read(dev, MCP230XX_IODIR, offset);
95         if (direction < 0) {
96                 dev_err(dev, "%s error: %d\n", __func__, direction);
97                 return direction;
98         }
99
100         *flags = direction ? GPIOD_IS_IN : GPIOD_IS_OUT;
101
102         if (pullup)
103                 *flags |= GPIOD_PULL_UP;
104
105         return 0;
106 }
107
108 static int mcp230xx_set_flags(struct udevice *dev, uint offset, ulong flags)
109 {
110         bool input = !(flags & (GPIOD_IS_OUT | GPIOD_IS_OUT_ACTIVE));
111         bool pullup = flags & GPIOD_PULL_UP;
112         ulong supported_mask;
113         int ret;
114
115         /* Note: active-low is ignored (handled by core) */
116         supported_mask = GPIOD_ACTIVE_LOW | GPIOD_MASK_DIR | GPIOD_PULL_UP;
117         if (flags & ~supported_mask) {
118                 dev_err(dev, "%s unsupported flag(s): %lx\n", __func__, flags);
119                 return -EINVAL;
120         }
121
122         ret = mcp230xx_write(dev, MCP230XX_OLAT, offset, !!(flags & GPIOD_IS_OUT_ACTIVE));
123         if (ret) {
124                 dev_err(dev, "%s failed to setup output latch: %d\n", __func__, ret);
125                 return ret;
126         }
127
128         ret = mcp230xx_write(dev, MCP230XX_GPPU, offset, pullup);
129         if (ret) {
130                 dev_err(dev, "%s failed to setup pull-up: %d\n", __func__, ret);
131                 return ret;
132         }
133
134         ret = mcp230xx_write(dev, MCP230XX_IODIR, offset, input);
135         if (ret) {
136                 dev_err(dev, "%s failed to setup direction: %d\n", __func__, ret);
137                 return ret;
138         }
139
140         return 0;
141 }
142
143 static int mcp230xx_direction_input(struct udevice *dev, uint offset)
144 {
145         return mcp230xx_set_flags(dev, offset, GPIOD_IS_IN);
146 }
147
148 static int mcp230xx_direction_output(struct udevice *dev, uint offset, int val)
149 {
150         int ret = mcp230xx_set_value(dev, offset, val);
151         if (ret < 0) {
152                 dev_err(dev, "%s error: %d\n", __func__, ret);
153                 return ret;
154         }
155         return mcp230xx_set_flags(dev, offset, GPIOD_IS_OUT);
156 }
157
158 static int mcp230xx_get_function(struct udevice *dev, uint offset)
159 {
160         int ret;
161
162         ret = mcp230xx_read(dev, MCP230XX_IODIR, offset);
163         if (ret < 0) {
164                 dev_err(dev, "%s error: %d\n", __func__, ret);
165                 return ret;
166         }
167
168         return ret ? GPIOF_INPUT : GPIOF_OUTPUT;
169 }
170
171 static const struct dm_gpio_ops mcp230xx_ops = {
172         .direction_input        = mcp230xx_direction_input,
173         .direction_output       = mcp230xx_direction_output,
174         .get_value              = mcp230xx_get_value,
175         .set_value              = mcp230xx_set_value,
176         .get_function           = mcp230xx_get_function,
177         .set_flags              = mcp230xx_set_flags,
178         .get_flags              = mcp230xx_get_flags,
179 };
180
181 static int mcp230xx_probe(struct udevice *dev)
182 {
183         struct gpio_dev_priv *uc_priv = dev_get_uclass_priv(dev);
184         char name[32], label[8], *str;
185         int addr, gpio_count, size;
186         const u8 *tmp;
187
188         switch (dev_get_driver_data(dev)) {
189         case MCP23008:
190                 gpio_count = 8;
191                 break;
192         case MCP23017:
193         case MCP23018:
194                 gpio_count = 16;
195                 break;
196         default:
197                 return -ENODEV;
198         }
199
200         addr = dev_read_addr(dev);
201         tmp = dev_read_prop(dev, "label", &size);
202         if (tmp) {
203                 memcpy(label, tmp, sizeof(label) - 1);
204                 label[sizeof(label) - 1] = '\0';
205                 snprintf(name, sizeof(name), "%s@%x_", label, addr);
206         } else {
207                 snprintf(name, sizeof(name), "gpio@%x_", addr);
208         }
209
210         str = strdup(name);
211         if (!str)
212                 return -ENOMEM;
213
214         uc_priv->bank_name = str;
215         uc_priv->gpio_count = gpio_count;
216
217         dev_dbg(dev, "%s is ready\n", str);
218
219         return 0;
220 }
221
222 static const struct udevice_id mcp230xx_ids[] = {
223         { .compatible = "microchip,mcp23008", .data = MCP23008, },
224         { .compatible = "microchip,mcp23017", .data = MCP23017, },
225         { .compatible = "microchip,mcp23018", .data = MCP23018, },
226         { }
227 };
228
229 U_BOOT_DRIVER(mcp230xx) = {
230         .name           = "mcp230xx",
231         .id             = UCLASS_GPIO,
232         .ops            = &mcp230xx_ops,
233         .probe          = mcp230xx_probe,
234         .of_match       = mcp230xx_ids,
235 };