1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* MCP23S08 SPI GPIO driver */ |
3 | |
4 | #include <linux/mod_devicetable.h> |
5 | #include <linux/module.h> |
6 | #include <linux/property.h> |
7 | #include <linux/regmap.h> |
8 | #include <linux/spi/spi.h> |
9 | |
10 | #include "pinctrl-mcp23s08.h" |
11 | |
12 | #define MCP_MAX_DEV_PER_CS 8 |
13 | |
14 | /* |
15 | * A given spi_device can represent up to eight mcp23sxx chips |
16 | * sharing the same chipselect but using different addresses |
17 | * (e.g. chips #0 and #3 might be populated, but not #1 or #2). |
18 | * Driver data holds all the per-chip data. |
19 | */ |
20 | struct mcp23s08_driver_data { |
21 | unsigned ngpio; |
22 | struct mcp23s08 *mcp[8]; |
23 | struct mcp23s08 chip[]; |
24 | }; |
25 | |
26 | static int mcp23sxx_spi_write(void *context, const void *data, size_t count) |
27 | { |
28 | struct mcp23s08 *mcp = context; |
29 | struct spi_device *spi = to_spi_device(dev: mcp->dev); |
30 | struct spi_message m; |
31 | struct spi_transfer t[2] = { { .tx_buf = &mcp->addr, .len = 1, }, |
32 | { .tx_buf = data, .len = count, }, }; |
33 | |
34 | spi_message_init(m: &m); |
35 | spi_message_add_tail(t: &t[0], m: &m); |
36 | spi_message_add_tail(t: &t[1], m: &m); |
37 | |
38 | return spi_sync(spi, message: &m); |
39 | } |
40 | |
41 | static int mcp23sxx_spi_gather_write(void *context, |
42 | const void *reg, size_t reg_size, |
43 | const void *val, size_t val_size) |
44 | { |
45 | struct mcp23s08 *mcp = context; |
46 | struct spi_device *spi = to_spi_device(dev: mcp->dev); |
47 | struct spi_message m; |
48 | struct spi_transfer t[3] = { { .tx_buf = &mcp->addr, .len = 1, }, |
49 | { .tx_buf = reg, .len = reg_size, }, |
50 | { .tx_buf = val, .len = val_size, }, }; |
51 | |
52 | spi_message_init(m: &m); |
53 | spi_message_add_tail(t: &t[0], m: &m); |
54 | spi_message_add_tail(t: &t[1], m: &m); |
55 | spi_message_add_tail(t: &t[2], m: &m); |
56 | |
57 | return spi_sync(spi, message: &m); |
58 | } |
59 | |
60 | static int mcp23sxx_spi_read(void *context, const void *reg, size_t reg_size, |
61 | void *val, size_t val_size) |
62 | { |
63 | struct mcp23s08 *mcp = context; |
64 | struct spi_device *spi = to_spi_device(dev: mcp->dev); |
65 | u8 tx[2]; |
66 | |
67 | if (reg_size != 1) |
68 | return -EINVAL; |
69 | |
70 | tx[0] = mcp->addr | 0x01; |
71 | tx[1] = *((u8 *) reg); |
72 | |
73 | return spi_write_then_read(spi, txbuf: tx, n_tx: sizeof(tx), rxbuf: val, n_rx: val_size); |
74 | } |
75 | |
76 | static const struct regmap_bus mcp23sxx_spi_regmap = { |
77 | .write = mcp23sxx_spi_write, |
78 | .gather_write = mcp23sxx_spi_gather_write, |
79 | .read = mcp23sxx_spi_read, |
80 | }; |
81 | |
82 | static int mcp23s08_spi_regmap_init(struct mcp23s08 *mcp, struct device *dev, |
83 | unsigned int addr, |
84 | const struct mcp23s08_info *info) |
85 | { |
86 | struct regmap_config *copy; |
87 | const char *name; |
88 | |
89 | switch (info->type) { |
90 | case MCP_TYPE_S08: |
91 | mcp->chip.label = devm_kasprintf(dev, GFP_KERNEL, fmt: "mcp23s08.%d" , addr); |
92 | if (!mcp->chip.label) |
93 | return -ENOMEM; |
94 | |
95 | name = devm_kasprintf(dev, GFP_KERNEL, fmt: "%d" , addr); |
96 | if (!name) |
97 | return -ENOMEM; |
98 | |
99 | break; |
100 | |
101 | case MCP_TYPE_S17: |
102 | mcp->chip.label = devm_kasprintf(dev, GFP_KERNEL, fmt: "mcp23s17.%d" , addr); |
103 | if (!mcp->chip.label) |
104 | return -ENOMEM; |
105 | |
106 | name = devm_kasprintf(dev, GFP_KERNEL, fmt: "%d" , addr); |
107 | if (!name) |
108 | return -ENOMEM; |
109 | |
110 | break; |
111 | |
112 | case MCP_TYPE_S18: |
113 | mcp->chip.label = info->label; |
114 | name = info->regmap->name; |
115 | break; |
116 | |
117 | default: |
118 | dev_err(dev, "invalid device type (%d)\n" , info->type); |
119 | return -EINVAL; |
120 | } |
121 | |
122 | mcp->reg_shift = info->reg_shift; |
123 | mcp->chip.ngpio = info->ngpio; |
124 | copy = devm_kmemdup(dev, src: info->regmap, len: sizeof(*info->regmap), GFP_KERNEL); |
125 | if (!copy) |
126 | return -ENOMEM; |
127 | |
128 | copy->name = name; |
129 | |
130 | mcp->regmap = devm_regmap_init(dev, &mcp23sxx_spi_regmap, mcp, copy); |
131 | if (IS_ERR(ptr: mcp->regmap)) |
132 | dev_err(dev, "regmap init failed for %s\n" , mcp->chip.label); |
133 | return PTR_ERR_OR_ZERO(ptr: mcp->regmap); |
134 | } |
135 | |
136 | static int mcp23s08_probe(struct spi_device *spi) |
137 | { |
138 | struct mcp23s08_driver_data *data; |
139 | const struct mcp23s08_info *info; |
140 | struct device *dev = &spi->dev; |
141 | unsigned long spi_present_mask; |
142 | unsigned int ngpio = 0; |
143 | unsigned int addr; |
144 | int chips; |
145 | int ret; |
146 | u32 v; |
147 | |
148 | info = spi_get_device_match_data(sdev: spi); |
149 | |
150 | ret = device_property_read_u32(dev, propname: "microchip,spi-present-mask" , val: &v); |
151 | if (ret) { |
152 | ret = device_property_read_u32(dev, propname: "mcp,spi-present-mask" , val: &v); |
153 | if (ret) { |
154 | dev_err(dev, "missing spi-present-mask" ); |
155 | return ret; |
156 | } |
157 | } |
158 | spi_present_mask = v; |
159 | |
160 | if (!spi_present_mask || spi_present_mask >= BIT(MCP_MAX_DEV_PER_CS)) { |
161 | dev_err(dev, "invalid spi-present-mask" ); |
162 | return -ENODEV; |
163 | } |
164 | |
165 | chips = hweight_long(w: spi_present_mask); |
166 | |
167 | data = devm_kzalloc(dev, struct_size(data, chip, chips), GFP_KERNEL); |
168 | if (!data) |
169 | return -ENOMEM; |
170 | |
171 | spi_set_drvdata(spi, data); |
172 | |
173 | for_each_set_bit(addr, &spi_present_mask, MCP_MAX_DEV_PER_CS) { |
174 | data->mcp[addr] = &data->chip[--chips]; |
175 | data->mcp[addr]->irq = spi->irq; |
176 | |
177 | ret = mcp23s08_spi_regmap_init(mcp: data->mcp[addr], dev, addr, info); |
178 | if (ret) |
179 | return ret; |
180 | |
181 | data->mcp[addr]->pinctrl_desc.name = devm_kasprintf(dev, GFP_KERNEL, |
182 | fmt: "mcp23xxx-pinctrl.%d" , |
183 | addr); |
184 | if (!data->mcp[addr]->pinctrl_desc.name) |
185 | return -ENOMEM; |
186 | |
187 | ret = mcp23s08_probe_one(mcp: data->mcp[addr], dev, addr: 0x40 | (addr << 1), |
188 | type: info->type, base: -1); |
189 | if (ret < 0) |
190 | return ret; |
191 | |
192 | ngpio += data->mcp[addr]->chip.ngpio; |
193 | } |
194 | data->ngpio = ngpio; |
195 | |
196 | return 0; |
197 | } |
198 | |
199 | static const struct mcp23s08_info mcp23s08_spi = { |
200 | .regmap = &mcp23x08_regmap, |
201 | .type = MCP_TYPE_S08, |
202 | .ngpio = 8, |
203 | .reg_shift = 0, |
204 | }; |
205 | |
206 | static const struct mcp23s08_info mcp23s17_spi = { |
207 | .regmap = &mcp23x17_regmap, |
208 | .type = MCP_TYPE_S17, |
209 | .ngpio = 16, |
210 | .reg_shift = 1, |
211 | }; |
212 | |
213 | static const struct mcp23s08_info mcp23s18_spi = { |
214 | .regmap = &mcp23x17_regmap, |
215 | .label = "mcp23s18" , |
216 | .type = MCP_TYPE_S18, |
217 | .ngpio = 16, |
218 | .reg_shift = 1, |
219 | }; |
220 | |
221 | static const struct spi_device_id mcp23s08_ids[] = { |
222 | { "mcp23s08" , (kernel_ulong_t)&mcp23s08_spi }, |
223 | { "mcp23s17" , (kernel_ulong_t)&mcp23s17_spi }, |
224 | { "mcp23s18" , (kernel_ulong_t)&mcp23s18_spi }, |
225 | { } |
226 | }; |
227 | MODULE_DEVICE_TABLE(spi, mcp23s08_ids); |
228 | |
229 | static const struct of_device_id mcp23s08_spi_of_match[] = { |
230 | { .compatible = "microchip,mcp23s08" , .data = &mcp23s08_spi }, |
231 | { .compatible = "microchip,mcp23s17" , .data = &mcp23s17_spi }, |
232 | { .compatible = "microchip,mcp23s18" , .data = &mcp23s18_spi }, |
233 | /* NOTE: The use of the mcp prefix is deprecated and will be removed. */ |
234 | { .compatible = "mcp,mcp23s08" , .data = &mcp23s08_spi }, |
235 | { .compatible = "mcp,mcp23s17" , .data = &mcp23s17_spi }, |
236 | { } |
237 | }; |
238 | MODULE_DEVICE_TABLE(of, mcp23s08_spi_of_match); |
239 | |
240 | static struct spi_driver mcp23s08_driver = { |
241 | .probe = mcp23s08_probe, |
242 | .id_table = mcp23s08_ids, |
243 | .driver = { |
244 | .name = "mcp23s08" , |
245 | .of_match_table = mcp23s08_spi_of_match, |
246 | }, |
247 | }; |
248 | |
249 | static int __init mcp23s08_spi_init(void) |
250 | { |
251 | return spi_register_driver(&mcp23s08_driver); |
252 | } |
253 | |
254 | /* |
255 | * Register after SPI postcore initcall and before |
256 | * subsys initcalls that may rely on these GPIOs. |
257 | */ |
258 | subsys_initcall(mcp23s08_spi_init); |
259 | |
260 | static void mcp23s08_spi_exit(void) |
261 | { |
262 | spi_unregister_driver(sdrv: &mcp23s08_driver); |
263 | } |
264 | module_exit(mcp23s08_spi_exit); |
265 | |
266 | MODULE_LICENSE("GPL" ); |
267 | |