]>
Commit | Line | Data |
---|---|---|
b3fdd327 YS |
1 | /* |
2 | * I2C multiplexer using a single register | |
3 | * | |
4 | * Copyright 2015 Freescale Semiconductor | |
5 | * York Sun <yorksun@freescale.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License as published by the | |
9 | * Free Software Foundation; either version 2 of the License, or (at your | |
10 | * option) any later version. | |
11 | */ | |
12 | ||
13 | #include <linux/i2c.h> | |
14 | #include <linux/i2c-mux.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/io.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/of_address.h> | |
19 | #include <linux/platform_data/i2c-mux-reg.h> | |
20 | #include <linux/platform_device.h> | |
21 | #include <linux/slab.h> | |
22 | ||
23 | struct regmux { | |
24 | struct i2c_adapter *parent; | |
25 | struct i2c_adapter **adap; /* child busses */ | |
26 | struct i2c_mux_reg_platform_data data; | |
27 | }; | |
28 | ||
29 | static int i2c_mux_reg_set(const struct regmux *mux, unsigned int chan_id) | |
30 | { | |
31 | if (!mux->data.reg) | |
32 | return -EINVAL; | |
33 | ||
5a73882f YS |
34 | /* |
35 | * Write to the register, followed by a read to ensure the write is | |
36 | * completed on a "posted" bus, for example PCI or write buffers. | |
37 | * The endianness of reading doesn't matter and the return data | |
38 | * is not used. | |
39 | */ | |
b3fdd327 YS |
40 | switch (mux->data.reg_size) { |
41 | case 4: | |
5a73882f | 42 | if (mux->data.little_endian) |
b3fdd327 | 43 | iowrite32(chan_id, mux->data.reg); |
5a73882f | 44 | else |
b3fdd327 | 45 | iowrite32be(chan_id, mux->data.reg); |
5a73882f YS |
46 | if (!mux->data.write_only) |
47 | ioread32(mux->data.reg); | |
b3fdd327 YS |
48 | break; |
49 | case 2: | |
5a73882f | 50 | if (mux->data.little_endian) |
b3fdd327 | 51 | iowrite16(chan_id, mux->data.reg); |
5a73882f | 52 | else |
b3fdd327 | 53 | iowrite16be(chan_id, mux->data.reg); |
5a73882f YS |
54 | if (!mux->data.write_only) |
55 | ioread16(mux->data.reg); | |
b3fdd327 YS |
56 | break; |
57 | case 1: | |
58 | iowrite8(chan_id, mux->data.reg); | |
59 | if (!mux->data.write_only) | |
60 | ioread8(mux->data.reg); | |
61 | break; | |
b3fdd327 YS |
62 | } |
63 | ||
64 | return 0; | |
65 | } | |
66 | ||
67 | static int i2c_mux_reg_select(struct i2c_adapter *adap, void *data, | |
68 | unsigned int chan) | |
69 | { | |
70 | struct regmux *mux = data; | |
71 | ||
72 | return i2c_mux_reg_set(mux, chan); | |
73 | } | |
74 | ||
75 | static int i2c_mux_reg_deselect(struct i2c_adapter *adap, void *data, | |
76 | unsigned int chan) | |
77 | { | |
78 | struct regmux *mux = data; | |
79 | ||
80 | if (mux->data.idle_in_use) | |
81 | return i2c_mux_reg_set(mux, mux->data.idle); | |
82 | ||
83 | return 0; | |
84 | } | |
85 | ||
86 | #ifdef CONFIG_OF | |
87 | static int i2c_mux_reg_probe_dt(struct regmux *mux, | |
88 | struct platform_device *pdev) | |
89 | { | |
90 | struct device_node *np = pdev->dev.of_node; | |
91 | struct device_node *adapter_np, *child; | |
92 | struct i2c_adapter *adapter; | |
93 | struct resource res; | |
94 | unsigned *values; | |
95 | int i = 0; | |
96 | ||
97 | if (!np) | |
98 | return -ENODEV; | |
99 | ||
100 | adapter_np = of_parse_phandle(np, "i2c-parent", 0); | |
101 | if (!adapter_np) { | |
102 | dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); | |
103 | return -ENODEV; | |
104 | } | |
105 | adapter = of_find_i2c_adapter_by_node(adapter_np); | |
bdbf4a29 | 106 | of_node_put(adapter_np); |
b3fdd327 YS |
107 | if (!adapter) |
108 | return -EPROBE_DEFER; | |
109 | ||
110 | mux->parent = adapter; | |
111 | mux->data.parent = i2c_adapter_id(adapter); | |
112 | put_device(&adapter->dev); | |
113 | ||
114 | mux->data.n_values = of_get_child_count(np); | |
115 | if (of_find_property(np, "little-endian", NULL)) { | |
116 | mux->data.little_endian = true; | |
117 | } else if (of_find_property(np, "big-endian", NULL)) { | |
118 | mux->data.little_endian = false; | |
119 | } else { | |
120 | #if defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : \ | |
121 | defined(__LITTLE_ENDIAN) | |
122 | mux->data.little_endian = true; | |
123 | #elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : \ | |
124 | defined(__BIG_ENDIAN) | |
125 | mux->data.little_endian = false; | |
126 | #else | |
127 | #error Endianness not defined? | |
128 | #endif | |
129 | } | |
130 | if (of_find_property(np, "write-only", NULL)) | |
131 | mux->data.write_only = true; | |
132 | else | |
133 | mux->data.write_only = false; | |
134 | ||
135 | values = devm_kzalloc(&pdev->dev, | |
136 | sizeof(*mux->data.values) * mux->data.n_values, | |
137 | GFP_KERNEL); | |
138 | if (!values) { | |
139 | dev_err(&pdev->dev, "Cannot allocate values array"); | |
140 | return -ENOMEM; | |
141 | } | |
142 | ||
143 | for_each_child_of_node(np, child) { | |
144 | of_property_read_u32(child, "reg", values + i); | |
145 | i++; | |
146 | } | |
147 | mux->data.values = values; | |
148 | ||
149 | if (!of_property_read_u32(np, "idle-state", &mux->data.idle)) | |
150 | mux->data.idle_in_use = true; | |
151 | ||
152 | /* map address from "reg" if exists */ | |
153 | if (of_address_to_resource(np, 0, &res)) { | |
154 | mux->data.reg_size = resource_size(&res); | |
b3fdd327 YS |
155 | mux->data.reg = devm_ioremap_resource(&pdev->dev, &res); |
156 | if (IS_ERR(mux->data.reg)) | |
157 | return PTR_ERR(mux->data.reg); | |
158 | } | |
159 | ||
160 | return 0; | |
161 | } | |
162 | #else | |
a05a34e7 | 163 | static int i2c_mux_reg_probe_dt(struct regmux *mux, |
b3fdd327 YS |
164 | struct platform_device *pdev) |
165 | { | |
166 | return 0; | |
167 | } | |
168 | #endif | |
169 | ||
170 | static int i2c_mux_reg_probe(struct platform_device *pdev) | |
171 | { | |
172 | struct regmux *mux; | |
173 | struct i2c_adapter *parent; | |
174 | struct resource *res; | |
175 | int (*deselect)(struct i2c_adapter *, void *, u32); | |
176 | unsigned int class; | |
177 | int i, ret, nr; | |
178 | ||
179 | mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); | |
180 | if (!mux) | |
181 | return -ENOMEM; | |
182 | ||
183 | platform_set_drvdata(pdev, mux); | |
184 | ||
185 | if (dev_get_platdata(&pdev->dev)) { | |
186 | memcpy(&mux->data, dev_get_platdata(&pdev->dev), | |
187 | sizeof(mux->data)); | |
188 | ||
189 | parent = i2c_get_adapter(mux->data.parent); | |
190 | if (!parent) | |
191 | return -EPROBE_DEFER; | |
192 | ||
193 | mux->parent = parent; | |
194 | } else { | |
195 | ret = i2c_mux_reg_probe_dt(mux, pdev); | |
196 | if (ret < 0) { | |
197 | dev_err(&pdev->dev, "Error parsing device tree"); | |
198 | return ret; | |
199 | } | |
200 | } | |
201 | ||
202 | if (!mux->data.reg) { | |
203 | dev_info(&pdev->dev, | |
204 | "Register not set, using platform resource\n"); | |
205 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
206 | mux->data.reg_size = resource_size(res); | |
b3fdd327 YS |
207 | mux->data.reg = devm_ioremap_resource(&pdev->dev, res); |
208 | if (IS_ERR(mux->data.reg)) | |
209 | return PTR_ERR(mux->data.reg); | |
210 | } | |
211 | ||
fce388af WS |
212 | if (mux->data.reg_size != 4 && mux->data.reg_size != 2 && |
213 | mux->data.reg_size != 1) { | |
214 | dev_err(&pdev->dev, "Invalid register size\n"); | |
215 | return -EINVAL; | |
216 | } | |
217 | ||
b3fdd327 YS |
218 | mux->adap = devm_kzalloc(&pdev->dev, |
219 | sizeof(*mux->adap) * mux->data.n_values, | |
220 | GFP_KERNEL); | |
221 | if (!mux->adap) { | |
222 | dev_err(&pdev->dev, "Cannot allocate i2c_adapter structure"); | |
223 | return -ENOMEM; | |
224 | } | |
225 | ||
226 | if (mux->data.idle_in_use) | |
227 | deselect = i2c_mux_reg_deselect; | |
228 | else | |
229 | deselect = NULL; | |
230 | ||
231 | for (i = 0; i < mux->data.n_values; i++) { | |
232 | nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; | |
233 | class = mux->data.classes ? mux->data.classes[i] : 0; | |
234 | ||
235 | mux->adap[i] = i2c_add_mux_adapter(mux->parent, &pdev->dev, mux, | |
236 | nr, mux->data.values[i], | |
237 | class, i2c_mux_reg_select, | |
238 | deselect); | |
239 | if (!mux->adap[i]) { | |
240 | ret = -ENODEV; | |
241 | dev_err(&pdev->dev, "Failed to add adapter %d\n", i); | |
242 | goto add_adapter_failed; | |
243 | } | |
244 | } | |
245 | ||
246 | dev_dbg(&pdev->dev, "%d port mux on %s adapter\n", | |
247 | mux->data.n_values, mux->parent->name); | |
248 | ||
249 | return 0; | |
250 | ||
251 | add_adapter_failed: | |
252 | for (; i > 0; i--) | |
253 | i2c_del_mux_adapter(mux->adap[i - 1]); | |
254 | ||
255 | return ret; | |
256 | } | |
257 | ||
258 | static int i2c_mux_reg_remove(struct platform_device *pdev) | |
259 | { | |
260 | struct regmux *mux = platform_get_drvdata(pdev); | |
261 | int i; | |
262 | ||
263 | for (i = 0; i < mux->data.n_values; i++) | |
264 | i2c_del_mux_adapter(mux->adap[i]); | |
265 | ||
266 | i2c_put_adapter(mux->parent); | |
267 | ||
268 | return 0; | |
269 | } | |
270 | ||
271 | static const struct of_device_id i2c_mux_reg_of_match[] = { | |
272 | { .compatible = "i2c-mux-reg", }, | |
273 | {}, | |
274 | }; | |
275 | MODULE_DEVICE_TABLE(of, i2c_mux_reg_of_match); | |
276 | ||
277 | static struct platform_driver i2c_mux_reg_driver = { | |
278 | .probe = i2c_mux_reg_probe, | |
279 | .remove = i2c_mux_reg_remove, | |
280 | .driver = { | |
281 | .name = "i2c-mux-reg", | |
282 | }, | |
283 | }; | |
284 | ||
285 | module_platform_driver(i2c_mux_reg_driver); | |
286 | ||
287 | MODULE_DESCRIPTION("Register-based I2C multiplexer driver"); | |
288 | MODULE_AUTHOR("York Sun <yorksun@freescale.com>"); | |
289 | MODULE_LICENSE("GPL"); | |
290 | MODULE_ALIAS("platform:i2c-mux-reg"); |