1 /* 2 * drivers/i2c/muxes/i2c-mux-mlxcpld.c 3 * Copyright (c) 2016 Mellanox Technologies. All rights reserved. 4 * Copyright (c) 2016 Michael Shych <michaels@mellanox.com> 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the names of the copyright holders nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * Alternatively, this software may be distributed under the terms of the 19 * GNU General Public License ("GPL") version 2 as published by the Free 20 * Software Foundation. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 * POSSIBILITY OF SUCH DAMAGE. 33 */ 34 35 #include <linux/device.h> 36 #include <linux/i2c.h> 37 #include <linux/i2c-mux.h> 38 #include <linux/io.h> 39 #include <linux/init.h> 40 #include <linux/module.h> 41 #include <linux/platform_device.h> 42 #include <linux/slab.h> 43 #include <linux/version.h> 44 #include <linux/i2c/mlxcpld.h> 45 46 #define CPLD_MUX_MAX_NCHANS 8 47 48 /* mlxcpld_mux - mux control structure: 49 * @last_chan - last register value 50 * @client - I2C device client 51 */ 52 struct mlxcpld_mux { 53 u8 last_chan; 54 struct i2c_client *client; 55 }; 56 57 /* MUX logic description. 58 * Driver can support different mux control logic, according to CPLD 59 * implementation. 60 * 61 * Connectivity schema. 62 * 63 * i2c-mlxcpld Digital Analog 64 * driver 65 * *--------* * -> mux1 (virt bus2) -> mux -> | 66 * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | 67 * | bridge | bus 1 *---------* | 68 * | logic |---------------------> * mux reg * | 69 * | in CPLD| *---------* | 70 * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | 71 * | driver | | 72 * | *---------------* | Devices 73 * | * CPLD (i2c bus)* select | 74 * | * registers for *--------* 75 * | * mux selection * deselect 76 * | *---------------* 77 * | | 78 * <--------> <-----------> 79 * i2c cntrl Board cntrl reg 80 * reg space space (mux select, 81 * IO, LED, WD, info) 82 * 83 */ 84 85 static const struct i2c_device_id mlxcpld_mux_id[] = { 86 { "mlxcpld_mux_module", 0 }, 87 { } 88 }; 89 MODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); 90 91 /* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() 92 * for this as they will try to lock adapter a second time. 93 */ 94 static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, 95 struct i2c_client *client, u8 val) 96 { 97 struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 98 int ret = -ENODEV; 99 100 if (adap->algo->master_xfer) { 101 struct i2c_msg msg; 102 u8 msgbuf[] = {pdata->sel_reg_addr, val}; 103 104 msg.addr = client->addr; 105 msg.flags = 0; 106 msg.len = 2; 107 msg.buf = msgbuf; 108 ret = __i2c_transfer(adap, &msg, 1); 109 110 if (ret >= 0 && ret != 1) 111 ret = -EREMOTEIO; 112 } else if (adap->algo->smbus_xfer) { 113 union i2c_smbus_data data; 114 115 data.byte = val; 116 ret = adap->algo->smbus_xfer(adap, client->addr, 117 client->flags, I2C_SMBUS_WRITE, 118 pdata->sel_reg_addr, 119 I2C_SMBUS_BYTE_DATA, &data); 120 } 121 122 return ret; 123 } 124 125 static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) 126 { 127 struct mlxcpld_mux *data = i2c_mux_priv(muxc); 128 struct i2c_client *client = data->client; 129 u8 regval = chan + 1; 130 int err = 0; 131 132 /* Only select the channel if its different from the last channel */ 133 if (data->last_chan != regval) { 134 err = mlxcpld_mux_reg_write(muxc->parent, client, regval); 135 data->last_chan = err < 0 ? 0 : regval; 136 } 137 138 return err; 139 } 140 141 static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) 142 { 143 struct mlxcpld_mux *data = i2c_mux_priv(muxc); 144 struct i2c_client *client = data->client; 145 146 /* Deselect active channel */ 147 data->last_chan = 0; 148 149 return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); 150 } 151 152 /* Probe/reomove functions */ 153 static int mlxcpld_mux_probe(struct i2c_client *client, 154 const struct i2c_device_id *id) 155 { 156 struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); 157 struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 158 struct i2c_mux_core *muxc; 159 int num, force; 160 struct mlxcpld_mux *data; 161 int err; 162 163 if (!pdata) 164 return -EINVAL; 165 166 if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) 167 return -ENODEV; 168 169 muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, 170 sizeof(*data), 0, mlxcpld_mux_select_chan, 171 mlxcpld_mux_deselect); 172 if (!muxc) 173 return -ENOMEM; 174 175 data = i2c_mux_priv(muxc); 176 i2c_set_clientdata(client, muxc); 177 data->client = client; 178 data->last_chan = 0; /* force the first selection */ 179 180 /* Create an adapter for each channel. */ 181 for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { 182 if (num >= pdata->num_adaps) 183 /* discard unconfigured channels */ 184 break; 185 186 force = pdata->adap_ids[num]; 187 188 err = i2c_mux_add_adapter(muxc, force, num, 0); 189 if (err) 190 goto virt_reg_failed; 191 } 192 193 return 0; 194 195 virt_reg_failed: 196 i2c_mux_del_adapters(muxc); 197 return err; 198 } 199 200 static int mlxcpld_mux_remove(struct i2c_client *client) 201 { 202 struct i2c_mux_core *muxc = i2c_get_clientdata(client); 203 204 i2c_mux_del_adapters(muxc); 205 return 0; 206 } 207 208 static struct i2c_driver mlxcpld_mux_driver = { 209 .driver = { 210 .name = "mlxcpld-mux", 211 }, 212 .probe = mlxcpld_mux_probe, 213 .remove = mlxcpld_mux_remove, 214 .id_table = mlxcpld_mux_id, 215 }; 216 217 module_i2c_driver(mlxcpld_mux_driver); 218 219 MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); 220 MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); 221 MODULE_LICENSE("Dual BSD/GPL"); 222 MODULE_ALIAS("platform:i2c-mux-mlxcpld"); 223