i2c: mux: mlxcpld: Prepare mux selection infrastructure for two-byte support
[linux-block.git] / drivers / i2c / muxes / i2c-mux-mlxcpld.c
CommitLineData
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 */
25struct 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 */
62static 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
73static 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
88static 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 99static 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
142virt_reg_failed:
143 i2c_mux_del_adapters(muxc);
144 return err;
145}
146
84af1b16 147static 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
155static 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 163module_platform_driver(mlxcpld_mux_driver);
c02b7bf5
VP
164
165MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)");
166MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver");
167MODULE_LICENSE("Dual BSD/GPL");
168MODULE_ALIAS("platform:i2c-mux-mlxcpld");