1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
2 | /* |
3 | * Mellanox i2c mux driver |
4 | * |
5 | * Copyright (C) 2016-2020 Mellanox Technologies |
6 | */ |
7 | |
8 | #include <linux/device.h> |
9 | #include <linux/i2c.h> |
10 | #include <linux/i2c-mux.h> |
11 | #include <linux/io.h> |
12 | #include <linux/init.h> |
13 | #include <linux/module.h> |
14 | #include <linux/platform_data/mlxcpld.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/slab.h> |
17 | |
18 | /* mlxcpld_mux - mux control structure: |
19 | * @last_val - last selected register value or -1 if mux deselected |
20 | * @client - I2C device client |
21 | * @pdata: platform data |
22 | */ |
23 | struct mlxcpld_mux { |
24 | int last_val; |
25 | struct i2c_client *client; |
26 | struct mlxcpld_mux_plat_data pdata; |
27 | }; |
28 | |
29 | /* MUX logic description. |
30 | * Driver can support different mux control logic, according to CPLD |
31 | * implementation. |
32 | * |
33 | * Connectivity schema. |
34 | * |
35 | * i2c-mlxcpld Digital Analog |
36 | * driver |
37 | * *--------* * -> mux1 (virt bus2) -> mux -> | |
38 | * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | |
39 | * | bridge | bus 1 *---------* | |
40 | * | logic |---------------------> * mux reg * | |
41 | * | in CPLD| *---------* | |
42 | * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | |
43 | * | driver | | |
44 | * | *---------------* | Devices |
45 | * | * CPLD (i2c bus)* select | |
46 | * | * registers for *--------* |
47 | * | * mux selection * deselect |
48 | * | *---------------* |
49 | * | | |
50 | * <--------> <-----------> |
51 | * i2c cntrl Board cntrl reg |
52 | * reg space space (mux select, |
53 | * IO, LED, WD, info) |
54 | * |
55 | */ |
56 | |
57 | /* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() |
58 | * for this as they will try to lock adapter a second time. |
59 | */ |
60 | static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, |
61 | struct mlxcpld_mux *mux, u32 val) |
62 | { |
63 | struct i2c_client *client = mux->client; |
64 | union i2c_smbus_data data; |
65 | struct i2c_msg msg; |
66 | u8 buf[3]; |
67 | |
68 | switch (mux->pdata.reg_size) { |
69 | case 1: |
70 | data.byte = val; |
71 | return __i2c_smbus_xfer(adapter: adap, addr: client->addr, flags: client->flags, |
72 | I2C_SMBUS_WRITE, command: mux->pdata.sel_reg_addr, |
73 | I2C_SMBUS_BYTE_DATA, data: &data); |
74 | case 2: |
75 | buf[0] = mux->pdata.sel_reg_addr >> 8; |
76 | buf[1] = mux->pdata.sel_reg_addr; |
77 | buf[2] = val; |
78 | msg.addr = client->addr; |
79 | msg.buf = buf; |
80 | msg.len = mux->pdata.reg_size + 1; |
81 | msg.flags = 0; |
82 | return __i2c_transfer(adap, msgs: &msg, num: 1); |
83 | default: |
84 | return -EINVAL; |
85 | } |
86 | } |
87 | |
88 | static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) |
89 | { |
90 | struct mlxcpld_mux *mux = i2c_mux_priv(muxc); |
91 | u32 regval = chan; |
92 | int err = 0; |
93 | |
94 | if (mux->pdata.reg_size == 1) |
95 | regval += 1; |
96 | |
97 | /* Only select the channel if its different from the last channel */ |
98 | if (mux->last_val != regval) { |
99 | err = mlxcpld_mux_reg_write(adap: muxc->parent, mux, val: regval); |
100 | mux->last_val = err < 0 ? -1 : regval; |
101 | } |
102 | |
103 | return err; |
104 | } |
105 | |
106 | static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) |
107 | { |
108 | struct mlxcpld_mux *mux = i2c_mux_priv(muxc); |
109 | |
110 | /* Deselect active channel */ |
111 | mux->last_val = -1; |
112 | |
113 | return mlxcpld_mux_reg_write(adap: muxc->parent, mux, val: 0); |
114 | } |
115 | |
116 | /* Probe/reomove functions */ |
117 | static int mlxcpld_mux_probe(struct platform_device *pdev) |
118 | { |
119 | struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(dev: &pdev->dev); |
120 | struct i2c_client *client = to_i2c_client(pdev->dev.parent); |
121 | struct i2c_mux_core *muxc; |
122 | struct mlxcpld_mux *data; |
123 | int num, err; |
124 | u32 func; |
125 | |
126 | if (!pdata) |
127 | return -EINVAL; |
128 | |
129 | switch (pdata->reg_size) { |
130 | case 1: |
131 | func = I2C_FUNC_SMBUS_WRITE_BYTE_DATA; |
132 | break; |
133 | case 2: |
134 | func = I2C_FUNC_I2C; |
135 | break; |
136 | default: |
137 | return -EINVAL; |
138 | } |
139 | |
140 | if (!i2c_check_functionality(adap: client->adapter, func)) |
141 | return -ENODEV; |
142 | |
143 | muxc = i2c_mux_alloc(parent: client->adapter, dev: &pdev->dev, max_adapters: pdata->num_adaps, |
144 | sizeof_priv: sizeof(*data), flags: 0, select: mlxcpld_mux_select_chan, |
145 | deselect: mlxcpld_mux_deselect); |
146 | if (!muxc) |
147 | return -ENOMEM; |
148 | |
149 | platform_set_drvdata(pdev, data: muxc); |
150 | data = i2c_mux_priv(muxc); |
151 | data->client = client; |
152 | memcpy(&data->pdata, pdata, sizeof(*pdata)); |
153 | data->last_val = -1; /* force the first selection */ |
154 | |
155 | /* Create an adapter for each channel. */ |
156 | for (num = 0; num < pdata->num_adaps; num++) { |
157 | err = i2c_mux_add_adapter(muxc, force_nr: 0, chan_id: pdata->chan_ids[num], class: 0); |
158 | if (err) |
159 | goto virt_reg_failed; |
160 | } |
161 | |
162 | /* Notify caller when all channels' adapters are created. */ |
163 | if (pdata->completion_notify) |
164 | pdata->completion_notify(pdata->handle, muxc->parent, muxc->adapter); |
165 | |
166 | return 0; |
167 | |
168 | virt_reg_failed: |
169 | i2c_mux_del_adapters(muxc); |
170 | return err; |
171 | } |
172 | |
173 | static void mlxcpld_mux_remove(struct platform_device *pdev) |
174 | { |
175 | struct i2c_mux_core *muxc = platform_get_drvdata(pdev); |
176 | |
177 | i2c_mux_del_adapters(muxc); |
178 | } |
179 | |
180 | static struct platform_driver mlxcpld_mux_driver = { |
181 | .driver = { |
182 | .name = "i2c-mux-mlxcpld" , |
183 | }, |
184 | .probe = mlxcpld_mux_probe, |
185 | .remove_new = mlxcpld_mux_remove, |
186 | }; |
187 | |
188 | module_platform_driver(mlxcpld_mux_driver); |
189 | |
190 | MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)" ); |
191 | MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver" ); |
192 | MODULE_LICENSE("Dual BSD/GPL" ); |
193 | MODULE_ALIAS("platform:i2c-mux-mlxcpld" ); |
194 | |