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 VP |
17 | |
18 | #define CPLD_MUX_MAX_NCHANS 8 | |
19 | ||
20 | /* mlxcpld_mux - mux control structure: | |
81566938 | 21 | * @last_val - last selected register value or -1 if mux deselected |
c02b7bf5 | 22 | * @client - I2C device client |
84af1b16 | 23 | * @pdata: platform data |
c02b7bf5 VP |
24 | */ |
25 | struct mlxcpld_mux { | |
81566938 | 26 | int last_val; |
c02b7bf5 | 27 | struct i2c_client *client; |
84af1b16 | 28 | struct mlxcpld_mux_plat_data pdata; |
c02b7bf5 VP |
29 | }; |
30 | ||
31 | /* MUX logic description. | |
32 | * Driver can support different mux control logic, according to CPLD | |
33 | * implementation. | |
34 | * | |
35 | * Connectivity schema. | |
36 | * | |
37 | * i2c-mlxcpld Digital Analog | |
38 | * driver | |
39 | * *--------* * -> mux1 (virt bus2) -> mux -> | | |
40 | * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | | |
41 | * | bridge | bus 1 *---------* | | |
42 | * | logic |---------------------> * mux reg * | | |
43 | * | in CPLD| *---------* | | |
44 | * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | | |
45 | * | driver | | | |
46 | * | *---------------* | Devices | |
47 | * | * CPLD (i2c bus)* select | | |
48 | * | * registers for *--------* | |
49 | * | * mux selection * deselect | |
50 | * | *---------------* | |
51 | * | | | |
52 | * <--------> <-----------> | |
53 | * i2c cntrl Board cntrl reg | |
54 | * reg space space (mux select, | |
55 | * IO, LED, WD, info) | |
56 | * | |
57 | */ | |
58 | ||
c02b7bf5 VP |
59 | /* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() |
60 | * for this as they will try to lock adapter a second time. | |
61 | */ | |
62 | static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, | |
81566938 | 63 | struct mlxcpld_mux *mux, u32 val) |
c02b7bf5 | 64 | { |
84af1b16 | 65 | struct i2c_client *client = mux->client; |
d7a0aef2 | 66 | union i2c_smbus_data data = { .byte = val }; |
c02b7bf5 | 67 | |
d7a0aef2 | 68 | return __i2c_smbus_xfer(adap, client->addr, client->flags, |
84af1b16 | 69 | I2C_SMBUS_WRITE, mux->pdata.sel_reg_addr, |
d7a0aef2 | 70 | I2C_SMBUS_BYTE_DATA, &data); |
c02b7bf5 VP |
71 | } |
72 | ||
73 | static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) | |
74 | { | |
84af1b16 | 75 | struct mlxcpld_mux *mux = i2c_mux_priv(muxc); |
81566938 | 76 | u32 regval = chan + 1; |
c02b7bf5 VP |
77 | int err = 0; |
78 | ||
79 | /* Only select the channel if its different from the last channel */ | |
81566938 | 80 | if (mux->last_val != regval) { |
84af1b16 | 81 | err = mlxcpld_mux_reg_write(muxc->parent, mux, regval); |
81566938 | 82 | mux->last_val = err < 0 ? -1 : regval; |
c02b7bf5 VP |
83 | } |
84 | ||
85 | return err; | |
86 | } | |
87 | ||
88 | static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) | |
89 | { | |
84af1b16 | 90 | struct mlxcpld_mux *mux = i2c_mux_priv(muxc); |
c02b7bf5 VP |
91 | |
92 | /* Deselect active channel */ | |
81566938 | 93 | mux->last_val = -1; |
c02b7bf5 | 94 | |
81566938 | 95 | return mlxcpld_mux_reg_write(muxc->parent, mux, 0); |
c02b7bf5 VP |
96 | } |
97 | ||
98 | /* Probe/reomove functions */ | |
84af1b16 | 99 | static int mlxcpld_mux_probe(struct platform_device *pdev) |
c02b7bf5 | 100 | { |
84af1b16 VP |
101 | struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&pdev->dev); |
102 | struct i2c_client *client = to_i2c_client(pdev->dev.parent); | |
c02b7bf5 VP |
103 | struct i2c_mux_core *muxc; |
104 | int num, force; | |
105 | struct mlxcpld_mux *data; | |
106 | int err; | |
107 | ||
108 | if (!pdata) | |
109 | return -EINVAL; | |
110 | ||
84af1b16 VP |
111 | if (!i2c_check_functionality(client->adapter, |
112 | I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) | |
c02b7bf5 VP |
113 | return -ENODEV; |
114 | ||
84af1b16 | 115 | muxc = i2c_mux_alloc(client->adapter, &pdev->dev, CPLD_MUX_MAX_NCHANS, |
c02b7bf5 VP |
116 | sizeof(*data), 0, mlxcpld_mux_select_chan, |
117 | mlxcpld_mux_deselect); | |
118 | if (!muxc) | |
119 | return -ENOMEM; | |
120 | ||
84af1b16 | 121 | platform_set_drvdata(pdev, muxc); |
c02b7bf5 | 122 | data = i2c_mux_priv(muxc); |
c02b7bf5 | 123 | data->client = client; |
84af1b16 | 124 | memcpy(&data->pdata, pdata, sizeof(*pdata)); |
81566938 | 125 | data->last_val = -1; /* force the first selection */ |
c02b7bf5 VP |
126 | |
127 | /* Create an adapter for each channel. */ | |
128 | for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { | |
129 | if (num >= pdata->num_adaps) | |
130 | /* discard unconfigured channels */ | |
131 | break; | |
132 | ||
133 | force = pdata->adap_ids[num]; | |
134 | ||
135 | err = i2c_mux_add_adapter(muxc, force, num, 0); | |
136 | if (err) | |
137 | goto virt_reg_failed; | |
138 | } | |
139 | ||
140 | return 0; | |
141 | ||
142 | virt_reg_failed: | |
143 | i2c_mux_del_adapters(muxc); | |
144 | return err; | |
145 | } | |
146 | ||
84af1b16 | 147 | static int mlxcpld_mux_remove(struct platform_device *pdev) |
c02b7bf5 | 148 | { |
84af1b16 | 149 | struct i2c_mux_core *muxc = platform_get_drvdata(pdev); |
c02b7bf5 VP |
150 | |
151 | i2c_mux_del_adapters(muxc); | |
152 | return 0; | |
153 | } | |
154 | ||
84af1b16 VP |
155 | static struct platform_driver mlxcpld_mux_driver = { |
156 | .driver = { | |
157 | .name = "i2c-mux-mlxcpld", | |
c02b7bf5 | 158 | }, |
84af1b16 VP |
159 | .probe = mlxcpld_mux_probe, |
160 | .remove = mlxcpld_mux_remove, | |
c02b7bf5 VP |
161 | }; |
162 | ||
84af1b16 | 163 | module_platform_driver(mlxcpld_mux_driver); |
c02b7bf5 VP |
164 | |
165 | MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); | |
166 | MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); | |
167 | MODULE_LICENSE("Dual BSD/GPL"); | |
168 | MODULE_ALIAS("platform:i2c-mux-mlxcpld"); |