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 */ 51struct 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 84static const struct i2c_device_id mlxcpld_mux_id[] = { 85 { "mlxcpld_mux_module", 0 }, 86 { } 87}; 88MODULE_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 */ 93static 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 union i2c_smbus_data data = { .byte = val }; 98 99 return __i2c_smbus_xfer(adap, client->addr, client->flags, 100 I2C_SMBUS_WRITE, pdata->sel_reg_addr, 101 I2C_SMBUS_BYTE_DATA, &data); 102} 103 104static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) 105{ 106 struct mlxcpld_mux *data = i2c_mux_priv(muxc); 107 struct i2c_client *client = data->client; 108 u8 regval = chan + 1; 109 int err = 0; 110 111 /* Only select the channel if its different from the last channel */ 112 if (data->last_chan != regval) { 113 err = mlxcpld_mux_reg_write(muxc->parent, client, regval); 114 data->last_chan = err < 0 ? 0 : regval; 115 } 116 117 return err; 118} 119 120static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) 121{ 122 struct mlxcpld_mux *data = i2c_mux_priv(muxc); 123 struct i2c_client *client = data->client; 124 125 /* Deselect active channel */ 126 data->last_chan = 0; 127 128 return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); 129} 130 131/* Probe/reomove functions */ 132static int mlxcpld_mux_probe(struct i2c_client *client, 133 const struct i2c_device_id *id) 134{ 135 struct i2c_adapter *adap = client->adapter; 136 struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 137 struct i2c_mux_core *muxc; 138 int num, force; 139 struct mlxcpld_mux *data; 140 int err; 141 142 if (!pdata) 143 return -EINVAL; 144 145 if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) 146 return -ENODEV; 147 148 muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, 149 sizeof(*data), 0, mlxcpld_mux_select_chan, 150 mlxcpld_mux_deselect); 151 if (!muxc) 152 return -ENOMEM; 153 154 data = i2c_mux_priv(muxc); 155 i2c_set_clientdata(client, muxc); 156 data->client = client; 157 data->last_chan = 0; /* force the first selection */ 158 159 /* Create an adapter for each channel. */ 160 for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { 161 if (num >= pdata->num_adaps) 162 /* discard unconfigured channels */ 163 break; 164 165 force = pdata->adap_ids[num]; 166 167 err = i2c_mux_add_adapter(muxc, force, num, 0); 168 if (err) 169 goto virt_reg_failed; 170 } 171 172 return 0; 173 174virt_reg_failed: 175 i2c_mux_del_adapters(muxc); 176 return err; 177} 178 179static int mlxcpld_mux_remove(struct i2c_client *client) 180{ 181 struct i2c_mux_core *muxc = i2c_get_clientdata(client); 182 183 i2c_mux_del_adapters(muxc); 184 return 0; 185} 186 187static struct i2c_driver mlxcpld_mux_driver = { 188 .driver = { 189 .name = "mlxcpld-mux", 190 }, 191 .probe = mlxcpld_mux_probe, 192 .remove = mlxcpld_mux_remove, 193 .id_table = mlxcpld_mux_id, 194}; 195 196module_i2c_driver(mlxcpld_mux_driver); 197 198MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); 199MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); 200MODULE_LICENSE("Dual BSD/GPL"); 201MODULE_ALIAS("platform:i2c-mux-mlxcpld"); 202