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_data/x86/mlxcpld.h> 42 #include <linux/platform_device.h> 43 #include <linux/slab.h> 44 45 #define CPLD_MUX_MAX_NCHANS 8 46 47 /* mlxcpld_mux - mux control structure: 48 * @last_chan - last register value 49 * @client - I2C device client 50 */ 51 struct mlxcpld_mux { 52 u8 last_chan; 53 struct i2c_client *client; 54 }; 55 56 /* MUX logic description. 57 * Driver can support different mux control logic, according to CPLD 58 * implementation. 59 * 60 * Connectivity schema. 61 * 62 * i2c-mlxcpld Digital Analog 63 * driver 64 * *--------* * -> mux1 (virt bus2) -> mux -> | 65 * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | 66 * | bridge | bus 1 *---------* | 67 * | logic |---------------------> * mux reg * | 68 * | in CPLD| *---------* | 69 * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | 70 * | driver | | 71 * | *---------------* | Devices 72 * | * CPLD (i2c bus)* select | 73 * | * registers for *--------* 74 * | * mux selection * deselect 75 * | *---------------* 76 * | | 77 * <--------> <-----------> 78 * i2c cntrl Board cntrl reg 79 * reg space space (mux select, 80 * IO, LED, WD, info) 81 * 82 */ 83 84 static const struct i2c_device_id mlxcpld_mux_id[] = { 85 { "mlxcpld_mux_module", 0 }, 86 { } 87 }; 88 MODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); 89 90 /* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() 91 * for this as they will try to lock adapter a second time. 92 */ 93 static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, 94 struct i2c_client *client, u8 val) 95 { 96 struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 97 int ret = -ENODEV; 98 99 if (adap->algo->master_xfer) { 100 struct i2c_msg msg; 101 u8 msgbuf[] = {pdata->sel_reg_addr, val}; 102 103 msg.addr = client->addr; 104 msg.flags = 0; 105 msg.len = 2; 106 msg.buf = msgbuf; 107 ret = __i2c_transfer(adap, &msg, 1); 108 109 if (ret >= 0 && ret != 1) 110 ret = -EREMOTEIO; 111 } else if (adap->algo->smbus_xfer) { 112 union i2c_smbus_data data; 113 114 data.byte = val; 115 ret = adap->algo->smbus_xfer(adap, client->addr, 116 client->flags, I2C_SMBUS_WRITE, 117 pdata->sel_reg_addr, 118 I2C_SMBUS_BYTE_DATA, &data); 119 } 120 121 return ret; 122 } 123 124 static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) 125 { 126 struct mlxcpld_mux *data = i2c_mux_priv(muxc); 127 struct i2c_client *client = data->client; 128 u8 regval = chan + 1; 129 int err = 0; 130 131 /* Only select the channel if its different from the last channel */ 132 if (data->last_chan != regval) { 133 err = mlxcpld_mux_reg_write(muxc->parent, client, regval); 134 data->last_chan = err < 0 ? 0 : regval; 135 } 136 137 return err; 138 } 139 140 static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) 141 { 142 struct mlxcpld_mux *data = i2c_mux_priv(muxc); 143 struct i2c_client *client = data->client; 144 145 /* Deselect active channel */ 146 data->last_chan = 0; 147 148 return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); 149 } 150 151 /* Probe/reomove functions */ 152 static int mlxcpld_mux_probe(struct i2c_client *client, 153 const struct i2c_device_id *id) 154 { 155 struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); 156 struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 157 struct i2c_mux_core *muxc; 158 int num, force; 159 struct mlxcpld_mux *data; 160 int err; 161 162 if (!pdata) 163 return -EINVAL; 164 165 if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) 166 return -ENODEV; 167 168 muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, 169 sizeof(*data), 0, mlxcpld_mux_select_chan, 170 mlxcpld_mux_deselect); 171 if (!muxc) 172 return -ENOMEM; 173 174 data = i2c_mux_priv(muxc); 175 i2c_set_clientdata(client, muxc); 176 data->client = client; 177 data->last_chan = 0; /* force the first selection */ 178 179 /* Create an adapter for each channel. */ 180 for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { 181 if (num >= pdata->num_adaps) 182 /* discard unconfigured channels */ 183 break; 184 185 force = pdata->adap_ids[num]; 186 187 err = i2c_mux_add_adapter(muxc, force, num, 0); 188 if (err) 189 goto virt_reg_failed; 190 } 191 192 return 0; 193 194 virt_reg_failed: 195 i2c_mux_del_adapters(muxc); 196 return err; 197 } 198 199 static int mlxcpld_mux_remove(struct i2c_client *client) 200 { 201 struct i2c_mux_core *muxc = i2c_get_clientdata(client); 202 203 i2c_mux_del_adapters(muxc); 204 return 0; 205 } 206 207 static struct i2c_driver mlxcpld_mux_driver = { 208 .driver = { 209 .name = "mlxcpld-mux", 210 }, 211 .probe = mlxcpld_mux_probe, 212 .remove = mlxcpld_mux_remove, 213 .id_table = mlxcpld_mux_id, 214 }; 215 216 module_i2c_driver(mlxcpld_mux_driver); 217 218 MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); 219 MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); 220 MODULE_LICENSE("Dual BSD/GPL"); 221 MODULE_ALIAS("platform:i2c-mux-mlxcpld"); 222