Commit | Line | Data |
---|---|---|
337bc68c | 1 | // SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0 |
c02b7bf5 | 2 | /* |
337bc68c | 3 | * Mellanox i2c mux driver |
c02b7bf5 | 4 | * |
337bc68c | 5 | * Copyright (C) 2016-2020 Mellanox Technologies |
c02b7bf5 VP |
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> | |
98d29c41 | 14 | #include <linux/platform_data/mlxcpld.h> |
c02b7bf5 VP |
15 | #include <linux/platform_device.h> |
16 | #include <linux/slab.h> | |
c02b7bf5 | 17 | |
c02b7bf5 | 18 | /* mlxcpld_mux - mux control structure: |
81566938 | 19 | * @last_val - last selected register value or -1 if mux deselected |
c02b7bf5 | 20 | * @client - I2C device client |
84af1b16 | 21 | * @pdata: platform data |
c02b7bf5 VP |
22 | */ |
23 | struct mlxcpld_mux { | |
81566938 | 24 | int last_val; |
c02b7bf5 | 25 | struct i2c_client *client; |
84af1b16 | 26 | struct mlxcpld_mux_plat_data pdata; |
c02b7bf5 VP |
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 | ||
c02b7bf5 VP |
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, | |
81566938 | 61 | struct mlxcpld_mux *mux, u32 val) |
c02b7bf5 | 62 | { |
84af1b16 | 63 | struct i2c_client *client = mux->client; |
c52a1c5f VP |
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(adap, client->addr, client->flags, | |
72 | I2C_SMBUS_WRITE, mux->pdata.sel_reg_addr, | |
73 | I2C_SMBUS_BYTE_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, &msg, 1); | |
83 | default: | |
84 | return -EINVAL; | |
85 | } | |
c02b7bf5 VP |
86 | } |
87 | ||
88 | static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) | |
89 | { | |
84af1b16 | 90 | struct mlxcpld_mux *mux = i2c_mux_priv(muxc); |
c52a1c5f | 91 | u32 regval = chan; |
c02b7bf5 VP |
92 | int err = 0; |
93 | ||
c52a1c5f VP |
94 | if (mux->pdata.reg_size == 1) |
95 | regval += 1; | |
96 | ||
c02b7bf5 | 97 | /* Only select the channel if its different from the last channel */ |
81566938 | 98 | if (mux->last_val != regval) { |
84af1b16 | 99 | err = mlxcpld_mux_reg_write(muxc->parent, mux, regval); |
81566938 | 100 | mux->last_val = err < 0 ? -1 : regval; |
c02b7bf5 VP |
101 | } |
102 | ||
103 | return err; | |
104 | } | |
105 | ||
106 | static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) | |
107 | { | |
84af1b16 | 108 | struct mlxcpld_mux *mux = i2c_mux_priv(muxc); |
c02b7bf5 VP |
109 | |
110 | /* Deselect active channel */ | |
81566938 | 111 | mux->last_val = -1; |
c02b7bf5 | 112 | |
81566938 | 113 | return mlxcpld_mux_reg_write(muxc->parent, mux, 0); |
c02b7bf5 VP |
114 | } |
115 | ||
116 | /* Probe/reomove functions */ | |
84af1b16 | 117 | static int mlxcpld_mux_probe(struct platform_device *pdev) |
c02b7bf5 | 118 | { |
84af1b16 VP |
119 | struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&pdev->dev); |
120 | struct i2c_client *client = to_i2c_client(pdev->dev.parent); | |
c02b7bf5 | 121 | struct i2c_mux_core *muxc; |
c02b7bf5 | 122 | struct mlxcpld_mux *data; |
cae52163 | 123 | int num, err; |
c52a1c5f | 124 | u32 func; |
c02b7bf5 VP |
125 | |
126 | if (!pdata) | |
127 | return -EINVAL; | |
128 | ||
c52a1c5f VP |
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(client->adapter, func)) | |
c02b7bf5 VP |
141 | return -ENODEV; |
142 | ||
699c0506 | 143 | muxc = i2c_mux_alloc(client->adapter, &pdev->dev, pdata->num_adaps, |
c02b7bf5 VP |
144 | sizeof(*data), 0, mlxcpld_mux_select_chan, |
145 | mlxcpld_mux_deselect); | |
146 | if (!muxc) | |
147 | return -ENOMEM; | |
148 | ||
84af1b16 | 149 | platform_set_drvdata(pdev, muxc); |
c02b7bf5 | 150 | data = i2c_mux_priv(muxc); |
c02b7bf5 | 151 | data->client = client; |
84af1b16 | 152 | memcpy(&data->pdata, pdata, sizeof(*pdata)); |
81566938 | 153 | data->last_val = -1; /* force the first selection */ |
c02b7bf5 VP |
154 | |
155 | /* Create an adapter for each channel. */ | |
699c0506 | 156 | for (num = 0; num < pdata->num_adaps; num++) { |
cae52163 | 157 | err = i2c_mux_add_adapter(muxc, 0, pdata->chan_ids[num], 0); |
c02b7bf5 VP |
158 | if (err) |
159 | goto virt_reg_failed; | |
160 | } | |
161 | ||
a39bd92e VP |
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 | ||
c02b7bf5 VP |
166 | return 0; |
167 | ||
168 | virt_reg_failed: | |
169 | i2c_mux_del_adapters(muxc); | |
170 | return err; | |
171 | } | |
172 | ||
e190a0c3 | 173 | static void mlxcpld_mux_remove(struct platform_device *pdev) |
c02b7bf5 | 174 | { |
84af1b16 | 175 | struct i2c_mux_core *muxc = platform_get_drvdata(pdev); |
c02b7bf5 VP |
176 | |
177 | i2c_mux_del_adapters(muxc); | |
c02b7bf5 VP |
178 | } |
179 | ||
84af1b16 VP |
180 | static struct platform_driver mlxcpld_mux_driver = { |
181 | .driver = { | |
182 | .name = "i2c-mux-mlxcpld", | |
c02b7bf5 | 183 | }, |
84af1b16 | 184 | .probe = mlxcpld_mux_probe, |
e190a0c3 | 185 | .remove_new = mlxcpld_mux_remove, |
c02b7bf5 VP |
186 | }; |
187 | ||
84af1b16 | 188 | module_platform_driver(mlxcpld_mux_driver); |
c02b7bf5 | 189 | |
6a57a219 | 190 | MODULE_AUTHOR("Michael Shych <michaels@mellanox.com>"); |
c02b7bf5 VP |
191 | MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); |
192 | MODULE_LICENSE("Dual BSD/GPL"); | |
193 | MODULE_ALIAS("platform:i2c-mux-mlxcpld"); |