diff options
author | York Sun <yorksun@freescale.com> | 2015-08-17 14:53:48 -0400 |
---|---|---|
committer | Wolfram Sang <wsa@the-dreams.de> | 2015-08-24 08:05:18 -0400 |
commit | b3fdd32799d834e2626fae087906e886037350c6 (patch) | |
tree | 38d3df37fe04e8631ba955a3dfd6ed2a80dd7ff8 | |
parent | 7a59b00a0906945f7fe25a10332ac0820491a0c3 (diff) |
i2c: mux: Add register-based mux i2c-mux-reg
Based on i2c-mux-gpio driver, similarly the register-based mux
switch from one bus to another by setting a single register.
The register can be on PCIe bus, local bus, or any memory-mapped
address. The endianness of such register can be specified in device
tree if used, or in platform data.
Signed-off-by: York Sun <yorksun@freescale.com>
Acked-by: Alexander Sverdlin <alexander.sverdlin@nokia.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
-rw-r--r-- | Documentation/devicetree/bindings/i2c/i2c-mux-reg.txt | 74 | ||||
-rw-r--r-- | drivers/i2c/muxes/Kconfig | 11 | ||||
-rw-r--r-- | drivers/i2c/muxes/Makefile | 1 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-reg.c | 294 | ||||
-rw-r--r-- | include/linux/platform_data/i2c-mux-reg.h | 44 |
5 files changed, 424 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/i2c/i2c-mux-reg.txt b/Documentation/devicetree/bindings/i2c/i2c-mux-reg.txt new file mode 100644 index 000000000000..688783fbe696 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/i2c-mux-reg.txt | |||
@@ -0,0 +1,74 @@ | |||
1 | Register-based I2C Bus Mux | ||
2 | |||
3 | This binding describes an I2C bus multiplexer that uses a single register | ||
4 | to route the I2C signals. | ||
5 | |||
6 | Required properties: | ||
7 | - compatible: i2c-mux-reg | ||
8 | - i2c-parent: The phandle of the I2C bus that this multiplexer's master-side | ||
9 | port is connected to. | ||
10 | * Standard I2C mux properties. See mux.txt in this directory. | ||
11 | * I2C child bus nodes. See mux.txt in this directory. | ||
12 | |||
13 | Optional properties: | ||
14 | - reg: this pair of <offset size> specifies the register to control the mux. | ||
15 | The <offset size> depends on its parent node. It can be any memory-mapped | ||
16 | address. The size must be either 1, 2, or 4 bytes. If reg is omitted, the | ||
17 | resource of this device will be used. | ||
18 | - little-endian: The existence indicates the register is in little endian. | ||
19 | - big-endian: The existence indicates the register is in big endian. | ||
20 | If both little-endian and big-endian are omitted, the endianness of the | ||
21 | CPU will be used. | ||
22 | - write-only: The existence indicates the register is write-only. | ||
23 | - idle-state: value to set the muxer to when idle. When no value is | ||
24 | given, it defaults to the last value used. | ||
25 | |||
26 | Whenever an access is made to a device on a child bus, the value set | ||
27 | in the revelant node's reg property will be output to the register. | ||
28 | |||
29 | If an idle state is defined, using the idle-state (optional) property, | ||
30 | whenever an access is not being made to a device on a child bus, the | ||
31 | register will be set according to the idle value. | ||
32 | |||
33 | If an idle state is not defined, the most recently used value will be | ||
34 | left programmed into the register. | ||
35 | |||
36 | Example of a mux on PCIe card, the host is a powerpc SoC (big endian): | ||
37 | |||
38 | i2c-mux { | ||
39 | /* the <offset size> depends on the address translation | ||
40 | * of the parent device. If omitted, device resource | ||
41 | * will be used instead. The size is to determine | ||
42 | * whether iowrite32, iowrite16, or iowrite8 will be used. | ||
43 | */ | ||
44 | reg = <0x6028 0x4>; | ||
45 | little-endian; /* little endian register on PCIe */ | ||
46 | compatible = "i2c-mux-reg"; | ||
47 | #address-cells = <1>; | ||
48 | #size-cells = <0>; | ||
49 | i2c-parent = <&i2c1>; | ||
50 | i2c@0 { | ||
51 | reg = <0>; | ||
52 | #address-cells = <1>; | ||
53 | #size-cells = <0>; | ||
54 | |||
55 | si5338: clock-generator@70 { | ||
56 | compatible = "silabs,si5338"; | ||
57 | reg = <0x70>; | ||
58 | /* other stuff */ | ||
59 | }; | ||
60 | }; | ||
61 | |||
62 | i2c@1 { | ||
63 | /* data is written using iowrite32 */ | ||
64 | reg = <1>; | ||
65 | #address-cells = <1>; | ||
66 | #size-cells = <0>; | ||
67 | |||
68 | si5338: clock-generator@70 { | ||
69 | compatible = "silabs,si5338"; | ||
70 | reg = <0x70>; | ||
71 | /* other stuff */ | ||
72 | }; | ||
73 | }; | ||
74 | }; | ||
diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig index fdd0769c84a3..f06b0e24673b 100644 --- a/drivers/i2c/muxes/Kconfig +++ b/drivers/i2c/muxes/Kconfig | |||
@@ -61,4 +61,15 @@ config I2C_MUX_PINCTRL | |||
61 | This driver can also be built as a module. If so, the module will be | 61 | This driver can also be built as a module. If so, the module will be |
62 | called pinctrl-i2cmux. | 62 | called pinctrl-i2cmux. |
63 | 63 | ||
64 | config I2C_MUX_REG | ||
65 | tristate "Register-based I2C multiplexer" | ||
66 | help | ||
67 | If you say yes to this option, support will be included for a | ||
68 | register based I2C multiplexer. This driver provides access to | ||
69 | I2C busses connected through a MUX, which is controlled | ||
70 | by a single register. | ||
71 | |||
72 | This driver can also be built as a module. If so, the module | ||
73 | will be called i2c-mux-reg. | ||
74 | |||
64 | endmenu | 75 | endmenu |
diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile index 465778b5d5dc..e89799b76a92 100644 --- a/drivers/i2c/muxes/Makefile +++ b/drivers/i2c/muxes/Makefile | |||
@@ -7,5 +7,6 @@ obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o | |||
7 | obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o | 7 | obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o |
8 | obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o | 8 | obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o |
9 | obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o | 9 | obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o |
10 | obj-$(CONFIG_I2C_MUX_REG) += i2c-mux-reg.o | ||
10 | 11 | ||
11 | ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG | 12 | ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG |
diff --git a/drivers/i2c/muxes/i2c-mux-reg.c b/drivers/i2c/muxes/i2c-mux-reg.c new file mode 100644 index 000000000000..86d41d36a783 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-reg.c | |||
@@ -0,0 +1,294 @@ | |||
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 | |||
34 | switch (mux->data.reg_size) { | ||
35 | case 4: | ||
36 | if (mux->data.little_endian) { | ||
37 | iowrite32(chan_id, mux->data.reg); | ||
38 | if (!mux->data.write_only) | ||
39 | ioread32(mux->data.reg); | ||
40 | } else { | ||
41 | iowrite32be(chan_id, mux->data.reg); | ||
42 | if (!mux->data.write_only) | ||
43 | ioread32(mux->data.reg); | ||
44 | } | ||
45 | break; | ||
46 | case 2: | ||
47 | if (mux->data.little_endian) { | ||
48 | iowrite16(chan_id, mux->data.reg); | ||
49 | if (!mux->data.write_only) | ||
50 | ioread16(mux->data.reg); | ||
51 | } else { | ||
52 | iowrite16be(chan_id, mux->data.reg); | ||
53 | if (!mux->data.write_only) | ||
54 | ioread16be(mux->data.reg); | ||
55 | } | ||
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; | ||
62 | default: | ||
63 | pr_err("Invalid register size\n"); | ||
64 | return -EINVAL; | ||
65 | } | ||
66 | |||
67 | return 0; | ||
68 | } | ||
69 | |||
70 | static int i2c_mux_reg_select(struct i2c_adapter *adap, void *data, | ||
71 | unsigned int chan) | ||
72 | { | ||
73 | struct regmux *mux = data; | ||
74 | |||
75 | return i2c_mux_reg_set(mux, chan); | ||
76 | } | ||
77 | |||
78 | static int i2c_mux_reg_deselect(struct i2c_adapter *adap, void *data, | ||
79 | unsigned int chan) | ||
80 | { | ||
81 | struct regmux *mux = data; | ||
82 | |||
83 | if (mux->data.idle_in_use) | ||
84 | return i2c_mux_reg_set(mux, mux->data.idle); | ||
85 | |||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | #ifdef CONFIG_OF | ||
90 | static int i2c_mux_reg_probe_dt(struct regmux *mux, | ||
91 | struct platform_device *pdev) | ||
92 | { | ||
93 | struct device_node *np = pdev->dev.of_node; | ||
94 | struct device_node *adapter_np, *child; | ||
95 | struct i2c_adapter *adapter; | ||
96 | struct resource res; | ||
97 | unsigned *values; | ||
98 | int i = 0; | ||
99 | |||
100 | if (!np) | ||
101 | return -ENODEV; | ||
102 | |||
103 | adapter_np = of_parse_phandle(np, "i2c-parent", 0); | ||
104 | if (!adapter_np) { | ||
105 | dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); | ||
106 | return -ENODEV; | ||
107 | } | ||
108 | adapter = of_find_i2c_adapter_by_node(adapter_np); | ||
109 | if (!adapter) | ||
110 | return -EPROBE_DEFER; | ||
111 | |||
112 | mux->parent = adapter; | ||
113 | mux->data.parent = i2c_adapter_id(adapter); | ||
114 | put_device(&adapter->dev); | ||
115 | |||
116 | mux->data.n_values = of_get_child_count(np); | ||
117 | if (of_find_property(np, "little-endian", NULL)) { | ||
118 | mux->data.little_endian = true; | ||
119 | } else if (of_find_property(np, "big-endian", NULL)) { | ||
120 | mux->data.little_endian = false; | ||
121 | } else { | ||
122 | #if defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : \ | ||
123 | defined(__LITTLE_ENDIAN) | ||
124 | mux->data.little_endian = true; | ||
125 | #elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : \ | ||
126 | defined(__BIG_ENDIAN) | ||
127 | mux->data.little_endian = false; | ||
128 | #else | ||
129 | #error Endianness not defined? | ||
130 | #endif | ||
131 | } | ||
132 | if (of_find_property(np, "write-only", NULL)) | ||
133 | mux->data.write_only = true; | ||
134 | else | ||
135 | mux->data.write_only = false; | ||
136 | |||
137 | values = devm_kzalloc(&pdev->dev, | ||
138 | sizeof(*mux->data.values) * mux->data.n_values, | ||
139 | GFP_KERNEL); | ||
140 | if (!values) { | ||
141 | dev_err(&pdev->dev, "Cannot allocate values array"); | ||
142 | return -ENOMEM; | ||
143 | } | ||
144 | |||
145 | for_each_child_of_node(np, child) { | ||
146 | of_property_read_u32(child, "reg", values + i); | ||
147 | i++; | ||
148 | } | ||
149 | mux->data.values = values; | ||
150 | |||
151 | if (!of_property_read_u32(np, "idle-state", &mux->data.idle)) | ||
152 | mux->data.idle_in_use = true; | ||
153 | |||
154 | /* map address from "reg" if exists */ | ||
155 | if (of_address_to_resource(np, 0, &res)) { | ||
156 | mux->data.reg_size = resource_size(&res); | ||
157 | if (mux->data.reg_size > 4) { | ||
158 | dev_err(&pdev->dev, "Invalid address size\n"); | ||
159 | return -EINVAL; | ||
160 | } | ||
161 | mux->data.reg = devm_ioremap_resource(&pdev->dev, &res); | ||
162 | if (IS_ERR(mux->data.reg)) | ||
163 | return PTR_ERR(mux->data.reg); | ||
164 | } | ||
165 | |||
166 | return 0; | ||
167 | } | ||
168 | #else | ||
169 | static int i2c_mux_reg_probe_dt(struct gpiomux *mux, | ||
170 | struct platform_device *pdev) | ||
171 | { | ||
172 | return 0; | ||
173 | } | ||
174 | #endif | ||
175 | |||
176 | static int i2c_mux_reg_probe(struct platform_device *pdev) | ||
177 | { | ||
178 | struct regmux *mux; | ||
179 | struct i2c_adapter *parent; | ||
180 | struct resource *res; | ||
181 | int (*deselect)(struct i2c_adapter *, void *, u32); | ||
182 | unsigned int class; | ||
183 | int i, ret, nr; | ||
184 | |||
185 | mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); | ||
186 | if (!mux) | ||
187 | return -ENOMEM; | ||
188 | |||
189 | platform_set_drvdata(pdev, mux); | ||
190 | |||
191 | if (dev_get_platdata(&pdev->dev)) { | ||
192 | memcpy(&mux->data, dev_get_platdata(&pdev->dev), | ||
193 | sizeof(mux->data)); | ||
194 | |||
195 | parent = i2c_get_adapter(mux->data.parent); | ||
196 | if (!parent) | ||
197 | return -EPROBE_DEFER; | ||
198 | |||
199 | mux->parent = parent; | ||
200 | } else { | ||
201 | ret = i2c_mux_reg_probe_dt(mux, pdev); | ||
202 | if (ret < 0) { | ||
203 | dev_err(&pdev->dev, "Error parsing device tree"); | ||
204 | return ret; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | if (!mux->data.reg) { | ||
209 | dev_info(&pdev->dev, | ||
210 | "Register not set, using platform resource\n"); | ||
211 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | ||
212 | mux->data.reg_size = resource_size(res); | ||
213 | if (mux->data.reg_size > 4) { | ||
214 | dev_err(&pdev->dev, "Invalid resource size\n"); | ||
215 | return -EINVAL; | ||
216 | } | ||
217 | mux->data.reg = devm_ioremap_resource(&pdev->dev, res); | ||
218 | if (IS_ERR(mux->data.reg)) | ||
219 | return PTR_ERR(mux->data.reg); | ||
220 | } | ||
221 | |||
222 | mux->adap = devm_kzalloc(&pdev->dev, | ||
223 | sizeof(*mux->adap) * mux->data.n_values, | ||
224 | GFP_KERNEL); | ||
225 | if (!mux->adap) { | ||
226 | dev_err(&pdev->dev, "Cannot allocate i2c_adapter structure"); | ||
227 | return -ENOMEM; | ||
228 | } | ||
229 | |||
230 | if (mux->data.idle_in_use) | ||
231 | deselect = i2c_mux_reg_deselect; | ||
232 | else | ||
233 | deselect = NULL; | ||
234 | |||
235 | for (i = 0; i < mux->data.n_values; i++) { | ||
236 | nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; | ||
237 | class = mux->data.classes ? mux->data.classes[i] : 0; | ||
238 | |||
239 | mux->adap[i] = i2c_add_mux_adapter(mux->parent, &pdev->dev, mux, | ||
240 | nr, mux->data.values[i], | ||
241 | class, i2c_mux_reg_select, | ||
242 | deselect); | ||
243 | if (!mux->adap[i]) { | ||
244 | ret = -ENODEV; | ||
245 | dev_err(&pdev->dev, "Failed to add adapter %d\n", i); | ||
246 | goto add_adapter_failed; | ||
247 | } | ||
248 | } | ||
249 | |||
250 | dev_dbg(&pdev->dev, "%d port mux on %s adapter\n", | ||
251 | mux->data.n_values, mux->parent->name); | ||
252 | |||
253 | return 0; | ||
254 | |||
255 | add_adapter_failed: | ||
256 | for (; i > 0; i--) | ||
257 | i2c_del_mux_adapter(mux->adap[i - 1]); | ||
258 | |||
259 | return ret; | ||
260 | } | ||
261 | |||
262 | static int i2c_mux_reg_remove(struct platform_device *pdev) | ||
263 | { | ||
264 | struct regmux *mux = platform_get_drvdata(pdev); | ||
265 | int i; | ||
266 | |||
267 | for (i = 0; i < mux->data.n_values; i++) | ||
268 | i2c_del_mux_adapter(mux->adap[i]); | ||
269 | |||
270 | i2c_put_adapter(mux->parent); | ||
271 | |||
272 | return 0; | ||
273 | } | ||
274 | |||
275 | static const struct of_device_id i2c_mux_reg_of_match[] = { | ||
276 | { .compatible = "i2c-mux-reg", }, | ||
277 | {}, | ||
278 | }; | ||
279 | MODULE_DEVICE_TABLE(of, i2c_mux_reg_of_match); | ||
280 | |||
281 | static struct platform_driver i2c_mux_reg_driver = { | ||
282 | .probe = i2c_mux_reg_probe, | ||
283 | .remove = i2c_mux_reg_remove, | ||
284 | .driver = { | ||
285 | .name = "i2c-mux-reg", | ||
286 | }, | ||
287 | }; | ||
288 | |||
289 | module_platform_driver(i2c_mux_reg_driver); | ||
290 | |||
291 | MODULE_DESCRIPTION("Register-based I2C multiplexer driver"); | ||
292 | MODULE_AUTHOR("York Sun <yorksun@freescale.com>"); | ||
293 | MODULE_LICENSE("GPL"); | ||
294 | MODULE_ALIAS("platform:i2c-mux-reg"); | ||
diff --git a/include/linux/platform_data/i2c-mux-reg.h b/include/linux/platform_data/i2c-mux-reg.h new file mode 100644 index 000000000000..c68712aadf43 --- /dev/null +++ b/include/linux/platform_data/i2c-mux-reg.h | |||
@@ -0,0 +1,44 @@ | |||
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 | #ifndef __LINUX_PLATFORM_DATA_I2C_MUX_REG_H | ||
14 | #define __LINUX_PLATFORM_DATA_I2C_MUX_REG_H | ||
15 | |||
16 | /** | ||
17 | * struct i2c_mux_reg_platform_data - Platform-dependent data for i2c-mux-reg | ||
18 | * @parent: Parent I2C bus adapter number | ||
19 | * @base_nr: Base I2C bus number to number adapters from or zero for dynamic | ||
20 | * @values: Array of value for each channel | ||
21 | * @n_values: Number of multiplexer channels | ||
22 | * @little_endian: Indicating if the register is in little endian | ||
23 | * @write_only: Reading the register is not allowed by hardware | ||
24 | * @classes: Optional I2C auto-detection classes | ||
25 | * @idle: Value to write to mux when idle | ||
26 | * @idle_in_use: indicate if idle value is in use | ||
27 | * @reg: Virtual address of the register to switch channel | ||
28 | * @reg_size: register size in bytes | ||
29 | */ | ||
30 | struct i2c_mux_reg_platform_data { | ||
31 | int parent; | ||
32 | int base_nr; | ||
33 | const unsigned int *values; | ||
34 | int n_values; | ||
35 | bool little_endian; | ||
36 | bool write_only; | ||
37 | const unsigned int *classes; | ||
38 | u32 idle; | ||
39 | bool idle_in_use; | ||
40 | void __iomem *reg; | ||
41 | resource_size_t reg_size; | ||
42 | }; | ||
43 | |||
44 | #endif /* __LINUX_PLATFORM_DATA_I2C_MUX_REG_H */ | ||