18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * drivers/i2c/muxes/i2c-mux-mlxcpld.c 38c2ecf20Sopenharmony_ci * Copyright (c) 2016 Mellanox Technologies. All rights reserved. 48c2ecf20Sopenharmony_ci * Copyright (c) 2016 Michael Shych <michaels@mellanox.com> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * Redistribution and use in source and binary forms, with or without 78c2ecf20Sopenharmony_ci * modification, are permitted provided that the following conditions are met: 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci * 1. Redistributions of source code must retain the above copyright 108c2ecf20Sopenharmony_ci * notice, this list of conditions and the following disclaimer. 118c2ecf20Sopenharmony_ci * 2. Redistributions in binary form must reproduce the above copyright 128c2ecf20Sopenharmony_ci * notice, this list of conditions and the following disclaimer in the 138c2ecf20Sopenharmony_ci * documentation and/or other materials provided with the distribution. 148c2ecf20Sopenharmony_ci * 3. Neither the names of the copyright holders nor the names of its 158c2ecf20Sopenharmony_ci * contributors may be used to endorse or promote products derived from 168c2ecf20Sopenharmony_ci * this software without specific prior written permission. 178c2ecf20Sopenharmony_ci * 188c2ecf20Sopenharmony_ci * Alternatively, this software may be distributed under the terms of the 198c2ecf20Sopenharmony_ci * GNU General Public License ("GPL") version 2 as published by the Free 208c2ecf20Sopenharmony_ci * Software Foundation. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 238c2ecf20Sopenharmony_ci * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 248c2ecf20Sopenharmony_ci * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 258c2ecf20Sopenharmony_ci * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 268c2ecf20Sopenharmony_ci * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 278c2ecf20Sopenharmony_ci * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 288c2ecf20Sopenharmony_ci * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 298c2ecf20Sopenharmony_ci * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 308c2ecf20Sopenharmony_ci * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 318c2ecf20Sopenharmony_ci * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 328c2ecf20Sopenharmony_ci * POSSIBILITY OF SUCH DAMAGE. 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci#include <linux/device.h> 368c2ecf20Sopenharmony_ci#include <linux/i2c.h> 378c2ecf20Sopenharmony_ci#include <linux/i2c-mux.h> 388c2ecf20Sopenharmony_ci#include <linux/io.h> 398c2ecf20Sopenharmony_ci#include <linux/init.h> 408c2ecf20Sopenharmony_ci#include <linux/module.h> 418c2ecf20Sopenharmony_ci#include <linux/platform_data/x86/mlxcpld.h> 428c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 438c2ecf20Sopenharmony_ci#include <linux/slab.h> 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci#define CPLD_MUX_MAX_NCHANS 8 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci/* mlxcpld_mux - mux control structure: 488c2ecf20Sopenharmony_ci * @last_chan - last register value 498c2ecf20Sopenharmony_ci * @client - I2C device client 508c2ecf20Sopenharmony_ci */ 518c2ecf20Sopenharmony_cistruct mlxcpld_mux { 528c2ecf20Sopenharmony_ci u8 last_chan; 538c2ecf20Sopenharmony_ci struct i2c_client *client; 548c2ecf20Sopenharmony_ci}; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci/* MUX logic description. 578c2ecf20Sopenharmony_ci * Driver can support different mux control logic, according to CPLD 588c2ecf20Sopenharmony_ci * implementation. 598c2ecf20Sopenharmony_ci * 608c2ecf20Sopenharmony_ci * Connectivity schema. 618c2ecf20Sopenharmony_ci * 628c2ecf20Sopenharmony_ci * i2c-mlxcpld Digital Analog 638c2ecf20Sopenharmony_ci * driver 648c2ecf20Sopenharmony_ci * *--------* * -> mux1 (virt bus2) -> mux -> | 658c2ecf20Sopenharmony_ci * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | 668c2ecf20Sopenharmony_ci * | bridge | bus 1 *---------* | 678c2ecf20Sopenharmony_ci * | logic |---------------------> * mux reg * | 688c2ecf20Sopenharmony_ci * | in CPLD| *---------* | 698c2ecf20Sopenharmony_ci * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | 708c2ecf20Sopenharmony_ci * | driver | | 718c2ecf20Sopenharmony_ci * | *---------------* | Devices 728c2ecf20Sopenharmony_ci * | * CPLD (i2c bus)* select | 738c2ecf20Sopenharmony_ci * | * registers for *--------* 748c2ecf20Sopenharmony_ci * | * mux selection * deselect 758c2ecf20Sopenharmony_ci * | *---------------* 768c2ecf20Sopenharmony_ci * | | 778c2ecf20Sopenharmony_ci * <--------> <-----------> 788c2ecf20Sopenharmony_ci * i2c cntrl Board cntrl reg 798c2ecf20Sopenharmony_ci * reg space space (mux select, 808c2ecf20Sopenharmony_ci * IO, LED, WD, info) 818c2ecf20Sopenharmony_ci * 828c2ecf20Sopenharmony_ci */ 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic const struct i2c_device_id mlxcpld_mux_id[] = { 858c2ecf20Sopenharmony_ci { "mlxcpld_mux_module", 0 }, 868c2ecf20Sopenharmony_ci { } 878c2ecf20Sopenharmony_ci}; 888c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci/* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() 918c2ecf20Sopenharmony_ci * for this as they will try to lock adapter a second time. 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_cistatic int mlxcpld_mux_reg_write(struct i2c_adapter *adap, 948c2ecf20Sopenharmony_ci struct i2c_client *client, u8 val) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 978c2ecf20Sopenharmony_ci union i2c_smbus_data data = { .byte = val }; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci return __i2c_smbus_xfer(adap, client->addr, client->flags, 1008c2ecf20Sopenharmony_ci I2C_SMBUS_WRITE, pdata->sel_reg_addr, 1018c2ecf20Sopenharmony_ci I2C_SMBUS_BYTE_DATA, &data); 1028c2ecf20Sopenharmony_ci} 1038c2ecf20Sopenharmony_ci 1048c2ecf20Sopenharmony_cistatic int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci struct mlxcpld_mux *data = i2c_mux_priv(muxc); 1078c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 1088c2ecf20Sopenharmony_ci u8 regval = chan + 1; 1098c2ecf20Sopenharmony_ci int err = 0; 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci /* Only select the channel if its different from the last channel */ 1128c2ecf20Sopenharmony_ci if (data->last_chan != regval) { 1138c2ecf20Sopenharmony_ci err = mlxcpld_mux_reg_write(muxc->parent, client, regval); 1148c2ecf20Sopenharmony_ci data->last_chan = err < 0 ? 0 : regval; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci return err; 1188c2ecf20Sopenharmony_ci} 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_cistatic int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) 1218c2ecf20Sopenharmony_ci{ 1228c2ecf20Sopenharmony_ci struct mlxcpld_mux *data = i2c_mux_priv(muxc); 1238c2ecf20Sopenharmony_ci struct i2c_client *client = data->client; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci /* Deselect active channel */ 1268c2ecf20Sopenharmony_ci data->last_chan = 0; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); 1298c2ecf20Sopenharmony_ci} 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci/* Probe/reomove functions */ 1328c2ecf20Sopenharmony_cistatic int mlxcpld_mux_probe(struct i2c_client *client, 1338c2ecf20Sopenharmony_ci const struct i2c_device_id *id) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct i2c_adapter *adap = client->adapter; 1368c2ecf20Sopenharmony_ci struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); 1378c2ecf20Sopenharmony_ci struct i2c_mux_core *muxc; 1388c2ecf20Sopenharmony_ci int num, force; 1398c2ecf20Sopenharmony_ci struct mlxcpld_mux *data; 1408c2ecf20Sopenharmony_ci int err; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci if (!pdata) 1438c2ecf20Sopenharmony_ci return -EINVAL; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) 1468c2ecf20Sopenharmony_ci return -ENODEV; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, 1498c2ecf20Sopenharmony_ci sizeof(*data), 0, mlxcpld_mux_select_chan, 1508c2ecf20Sopenharmony_ci mlxcpld_mux_deselect); 1518c2ecf20Sopenharmony_ci if (!muxc) 1528c2ecf20Sopenharmony_ci return -ENOMEM; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci data = i2c_mux_priv(muxc); 1558c2ecf20Sopenharmony_ci i2c_set_clientdata(client, muxc); 1568c2ecf20Sopenharmony_ci data->client = client; 1578c2ecf20Sopenharmony_ci data->last_chan = 0; /* force the first selection */ 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci /* Create an adapter for each channel. */ 1608c2ecf20Sopenharmony_ci for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { 1618c2ecf20Sopenharmony_ci if (num >= pdata->num_adaps) 1628c2ecf20Sopenharmony_ci /* discard unconfigured channels */ 1638c2ecf20Sopenharmony_ci break; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci force = pdata->adap_ids[num]; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci err = i2c_mux_add_adapter(muxc, force, num, 0); 1688c2ecf20Sopenharmony_ci if (err) 1698c2ecf20Sopenharmony_ci goto virt_reg_failed; 1708c2ecf20Sopenharmony_ci } 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci return 0; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_civirt_reg_failed: 1758c2ecf20Sopenharmony_ci i2c_mux_del_adapters(muxc); 1768c2ecf20Sopenharmony_ci return err; 1778c2ecf20Sopenharmony_ci} 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_cistatic int mlxcpld_mux_remove(struct i2c_client *client) 1808c2ecf20Sopenharmony_ci{ 1818c2ecf20Sopenharmony_ci struct i2c_mux_core *muxc = i2c_get_clientdata(client); 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci i2c_mux_del_adapters(muxc); 1848c2ecf20Sopenharmony_ci return 0; 1858c2ecf20Sopenharmony_ci} 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_cistatic struct i2c_driver mlxcpld_mux_driver = { 1888c2ecf20Sopenharmony_ci .driver = { 1898c2ecf20Sopenharmony_ci .name = "mlxcpld-mux", 1908c2ecf20Sopenharmony_ci }, 1918c2ecf20Sopenharmony_ci .probe = mlxcpld_mux_probe, 1928c2ecf20Sopenharmony_ci .remove = mlxcpld_mux_remove, 1938c2ecf20Sopenharmony_ci .id_table = mlxcpld_mux_id, 1948c2ecf20Sopenharmony_ci}; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_cimodule_i2c_driver(mlxcpld_mux_driver); 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ciMODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); 1998c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); 2008c2ecf20Sopenharmony_ciMODULE_LICENSE("Dual BSD/GPL"); 2018c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:i2c-mux-mlxcpld"); 202