| /* |
| * drivers/i2c/muxes/i2c-mux-mlxcpld.c |
| * Copyright (c) 2016 Mellanox Technologies. All rights reserved. |
| * Copyright (c) 2016 Michael Shych <michaels@mellanox.com> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the names of the copyright holders nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * Alternatively, this software may be distributed under the terms of the |
| * GNU General Public License ("GPL") version 2 as published by the Free |
| * Software Foundation. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include <linux/device.h> |
| #include <linux/i2c.h> |
| #include <linux/i2c-mux.h> |
| #include <linux/io.h> |
| #include <linux/init.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/slab.h> |
| #include <linux/i2c/mlxcpld.h> |
| |
| #define CPLD_MUX_MAX_NCHANS 8 |
| |
| /* mlxcpld_mux - mux control structure: |
| * @last_chan - last register value |
| * @client - I2C device client |
| */ |
| struct mlxcpld_mux { |
| u8 last_chan; |
| struct i2c_client *client; |
| }; |
| |
| /* MUX logic description. |
| * Driver can support different mux control logic, according to CPLD |
| * implementation. |
| * |
| * Connectivity schema. |
| * |
| * i2c-mlxcpld Digital Analog |
| * driver |
| * *--------* * -> mux1 (virt bus2) -> mux -> | |
| * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | |
| * | bridge | bus 1 *---------* | |
| * | logic |---------------------> * mux reg * | |
| * | in CPLD| *---------* | |
| * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | |
| * | driver | | |
| * | *---------------* | Devices |
| * | * CPLD (i2c bus)* select | |
| * | * registers for *--------* |
| * | * mux selection * deselect |
| * | *---------------* |
| * | | |
| * <--------> <-----------> |
| * i2c cntrl Board cntrl reg |
| * reg space space (mux select, |
| * IO, LED, WD, info) |
| * |
| */ |
| |
| static const struct i2c_device_id mlxcpld_mux_id[] = { |
| { "mlxcpld_mux_module", 0 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); |
| |
| /* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() |
| * for this as they will try to lock adapter a second time. |
| */ |
| static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, |
| struct i2c_client *client, u8 val) |
| { |
| struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); |
| int ret = -ENODEV; |
| |
| if (adap->algo->master_xfer) { |
| struct i2c_msg msg; |
| u8 msgbuf[] = {pdata->sel_reg_addr, val}; |
| |
| msg.addr = client->addr; |
| msg.flags = 0; |
| msg.len = 2; |
| msg.buf = msgbuf; |
| ret = __i2c_transfer(adap, &msg, 1); |
| |
| if (ret >= 0 && ret != 1) |
| ret = -EREMOTEIO; |
| } else if (adap->algo->smbus_xfer) { |
| union i2c_smbus_data data; |
| |
| data.byte = val; |
| ret = adap->algo->smbus_xfer(adap, client->addr, |
| client->flags, I2C_SMBUS_WRITE, |
| pdata->sel_reg_addr, |
| I2C_SMBUS_BYTE_DATA, &data); |
| } |
| |
| return ret; |
| } |
| |
| static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) |
| { |
| struct mlxcpld_mux *data = i2c_mux_priv(muxc); |
| struct i2c_client *client = data->client; |
| u8 regval = chan + 1; |
| int err = 0; |
| |
| /* Only select the channel if its different from the last channel */ |
| if (data->last_chan != regval) { |
| err = mlxcpld_mux_reg_write(muxc->parent, client, regval); |
| data->last_chan = err < 0 ? 0 : regval; |
| } |
| |
| return err; |
| } |
| |
| static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) |
| { |
| struct mlxcpld_mux *data = i2c_mux_priv(muxc); |
| struct i2c_client *client = data->client; |
| |
| /* Deselect active channel */ |
| data->last_chan = 0; |
| |
| return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); |
| } |
| |
| /* Probe/reomove functions */ |
| static int mlxcpld_mux_probe(struct i2c_client *client, |
| const struct i2c_device_id *id) |
| { |
| struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); |
| struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); |
| struct i2c_mux_core *muxc; |
| int num, force; |
| struct mlxcpld_mux *data; |
| int err; |
| |
| if (!pdata) |
| return -EINVAL; |
| |
| if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) |
| return -ENODEV; |
| |
| muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, |
| sizeof(*data), 0, mlxcpld_mux_select_chan, |
| mlxcpld_mux_deselect); |
| if (!muxc) |
| return -ENOMEM; |
| |
| data = i2c_mux_priv(muxc); |
| i2c_set_clientdata(client, muxc); |
| data->client = client; |
| data->last_chan = 0; /* force the first selection */ |
| |
| /* Create an adapter for each channel. */ |
| for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { |
| if (num >= pdata->num_adaps) |
| /* discard unconfigured channels */ |
| break; |
| |
| force = pdata->adap_ids[num]; |
| |
| err = i2c_mux_add_adapter(muxc, force, num, 0); |
| if (err) |
| goto virt_reg_failed; |
| } |
| |
| return 0; |
| |
| virt_reg_failed: |
| i2c_mux_del_adapters(muxc); |
| return err; |
| } |
| |
| static int mlxcpld_mux_remove(struct i2c_client *client) |
| { |
| struct i2c_mux_core *muxc = i2c_get_clientdata(client); |
| |
| i2c_mux_del_adapters(muxc); |
| return 0; |
| } |
| |
| static struct i2c_driver mlxcpld_mux_driver = { |
| .driver = { |
| .name = "mlxcpld-mux", |
| }, |
| .probe = mlxcpld_mux_probe, |
| .remove = mlxcpld_mux_remove, |
| .id_table = mlxcpld_mux_id, |
| }; |
| |
| module_i2c_driver(mlxcpld_mux_driver); |
| |
| MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); |
| MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); |
| MODULE_LICENSE("Dual BSD/GPL"); |
| MODULE_ALIAS("platform:i2c-mux-mlxcpld"); |