Skip to content

Commit b3fdd32

Browse files
York SunWolfram Sang
authored andcommitted
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>
1 parent 7a59b00 commit b3fdd32

File tree

5 files changed

+424
-0
lines changed

5 files changed

+424
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
};

drivers/i2c/muxes/Kconfig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,15 @@ config I2C_MUX_PINCTRL
6161
This driver can also be built as a module. If so, the module will be
6262
called pinctrl-i2cmux.
6363

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+
6475
endmenu

drivers/i2c/muxes/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o
77
obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o
88
obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o
99
obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o
10+
obj-$(CONFIG_I2C_MUX_REG) += i2c-mux-reg.o
1011

1112
ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG

drivers/i2c/muxes/i2c-mux-reg.c

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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");

0 commit comments

Comments
 (0)