diff options
Diffstat (limited to 'drivers/i2c/muxes')
-rw-r--r-- | drivers/i2c/muxes/Kconfig | 122 | ||||
-rw-r--r-- | drivers/i2c/muxes/Makefile | 18 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-arb-gpio-challenge.c | 206 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-demux-pinctrl.c | 326 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-gpio.c | 243 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-gpmux.c | 167 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-ltc4306.c | 320 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-mlxcpld.c | 201 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-pca9541.c | 350 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-pca954x.c | 567 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-pinctrl.c | 198 | ||||
-rw-r--r-- | drivers/i2c/muxes/i2c-mux-reg.c | 266 |
12 files changed, 2984 insertions, 0 deletions
diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig new file mode 100644 index 000000000..1708b1a82 --- /dev/null +++ b/drivers/i2c/muxes/Kconfig @@ -0,0 +1,122 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Multiplexer I2C chip drivers configuration +# + +menu "Multiplexer I2C Chip support" + depends on I2C_MUX + +config I2C_ARB_GPIO_CHALLENGE + tristate "GPIO-based I2C arbitration" + depends on GPIOLIB || COMPILE_TEST + depends on OF || COMPILE_TEST + help + If you say yes to this option, support will be included for an + I2C multimaster arbitration scheme using GPIOs and a challenge & + response mechanism where masters have to claim the bus by asserting + a GPIO. + + This driver can also be built as a module. If so, the module + will be called i2c-arb-gpio-challenge. + +config I2C_MUX_GPIO + tristate "GPIO-based I2C multiplexer" + depends on GPIOLIB + help + If you say yes to this option, support will be included for a + GPIO based I2C multiplexer. This driver provides access to + I2C busses connected through a MUX, which is controlled + through GPIO pins. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-gpio. + +config I2C_MUX_GPMUX + tristate "General Purpose I2C multiplexer" + select MULTIPLEXER + depends on OF || COMPILE_TEST + help + If you say yes to this option, support will be included for a + general purpose I2C multiplexer. This driver provides access to + I2C busses connected through a MUX, which in turn is controlled + by a MUX-controller from the MUX subsystem. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-gpmux. + +config I2C_MUX_LTC4306 + tristate "LTC LTC4306/5 I2C multiplexer" + select GPIOLIB + select REGMAP_I2C + help + If you say yes here you get support for the Analog Devices + LTC4306 or LTC4305 I2C mux/switch devices. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-ltc4306. + +config I2C_MUX_PCA9541 + tristate "NXP PCA9541 I2C Master Selector" + help + If you say yes here you get support for the NXP PCA9541 + I2C Master Selector. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-pca9541. + +config I2C_MUX_PCA954x + tristate "NXP PCA954x and PCA984x I2C Mux/switches" + depends on GPIOLIB || COMPILE_TEST + help + If you say yes here you get support for the NXP PCA954x + and PCA984x I2C mux/switch devices. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-pca954x. + +config I2C_MUX_PINCTRL + tristate "pinctrl-based I2C multiplexer" + depends on PINCTRL + depends on OF || COMPILE_TEST + help + If you say yes to this option, support will be included for an I2C + multiplexer that uses the pinctrl subsystem, i.e. pin multiplexing. + This is useful for SoCs whose I2C module's signals can be routed to + different sets of pins at run-time. + + This driver can also be built as a module. If so, the module will be + called i2c-mux-pinctrl. + +config I2C_MUX_REG + tristate "Register-based I2C multiplexer" + depends on HAS_IOMEM + help + If you say yes to this option, support will be included for a + register based I2C multiplexer. This driver provides access to + I2C busses connected through a MUX, which is controlled + by a single register. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-reg. + +config I2C_DEMUX_PINCTRL + tristate "pinctrl-based I2C demultiplexer" + depends on PINCTRL && OF + select OF_DYNAMIC + help + If you say yes to this option, support will be included for an I2C + demultiplexer that uses the pinctrl subsystem. This is useful if you + want to change the I2C master at run-time depending on features. + +config I2C_MUX_MLXCPLD + tristate "Mellanox CPLD based I2C multiplexer" + help + If you say yes to this option, support will be included for a + CPLD based I2C multiplexer. This driver provides access to + I2C busses connected through a MUX, which is controlled + by a CPLD register. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-mlxcpld. + +endmenu diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile new file mode 100644 index 000000000..6d9d865e8 --- /dev/null +++ b/drivers/i2c/muxes/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for multiplexer I2C chip drivers. + +obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o + +obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o + +obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o +obj-$(CONFIG_I2C_MUX_GPMUX) += i2c-mux-gpmux.o +obj-$(CONFIG_I2C_MUX_LTC4306) += i2c-mux-ltc4306.o +obj-$(CONFIG_I2C_MUX_MLXCPLD) += i2c-mux-mlxcpld.o +obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o +obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o +obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o +obj-$(CONFIG_I2C_MUX_REG) += i2c-mux-reg.o + +ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG diff --git a/drivers/i2c/muxes/i2c-arb-gpio-challenge.c b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c new file mode 100644 index 000000000..6dc88902c --- /dev/null +++ b/drivers/i2c/muxes/i2c-arb-gpio-challenge.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * GPIO-based I2C Arbitration Using a Challenge & Response Mechanism + * + * Copyright (C) 2012 Google, Inc + */ + +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + + +/** + * struct i2c_arbitrator_data - Driver data for I2C arbitrator + * + * @our_gpio: GPIO descriptor we'll use to claim. + * @their_gpio: GPIO descriptor that the other side will use to claim. + * @slew_delay_us: microseconds to wait for a GPIO to go high. + * @wait_retry_us: we'll attempt another claim after this many microseconds. + * @wait_free_us: we'll give up after this many microseconds. + */ + +struct i2c_arbitrator_data { + struct gpio_desc *our_gpio; + struct gpio_desc *their_gpio; + unsigned int slew_delay_us; + unsigned int wait_retry_us; + unsigned int wait_free_us; +}; + + +/** + * i2c_arbitrator_select - claim the I2C bus + * + * Use the GPIO-based signalling protocol; return -EBUSY if we fail. + */ +static int i2c_arbitrator_select(struct i2c_mux_core *muxc, u32 chan) +{ + const struct i2c_arbitrator_data *arb = i2c_mux_priv(muxc); + unsigned long stop_retry, stop_time; + + /* Start a round of trying to claim the bus */ + stop_time = jiffies + usecs_to_jiffies(arb->wait_free_us) + 1; + do { + /* Indicate that we want to claim the bus */ + gpiod_set_value(arb->our_gpio, 1); + udelay(arb->slew_delay_us); + + /* Wait for the other master to release it */ + stop_retry = jiffies + usecs_to_jiffies(arb->wait_retry_us) + 1; + while (time_before(jiffies, stop_retry)) { + int gpio_val = gpiod_get_value(arb->their_gpio); + + if (!gpio_val) { + /* We got it, so return */ + return 0; + } + + usleep_range(50, 200); + } + + /* It didn't release, so give up, wait, and try again */ + gpiod_set_value(arb->our_gpio, 0); + + usleep_range(arb->wait_retry_us, arb->wait_retry_us * 2); + } while (time_before(jiffies, stop_time)); + + /* Give up, release our claim */ + gpiod_set_value(arb->our_gpio, 0); + udelay(arb->slew_delay_us); + dev_err(muxc->dev, "Could not claim bus, timeout\n"); + return -EBUSY; +} + +/** + * i2c_arbitrator_deselect - release the I2C bus + * + * Release the I2C bus using the GPIO-based signalling protocol. + */ +static int i2c_arbitrator_deselect(struct i2c_mux_core *muxc, u32 chan) +{ + const struct i2c_arbitrator_data *arb = i2c_mux_priv(muxc); + + /* Release the bus and wait for the other master to notice */ + gpiod_set_value(arb->our_gpio, 0); + udelay(arb->slew_delay_us); + + return 0; +} + +static int i2c_arbitrator_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *parent_np; + struct i2c_mux_core *muxc; + struct i2c_arbitrator_data *arb; + struct gpio_desc *dummy; + int ret; + + /* We only support probing from device tree; no platform_data */ + if (!np) { + dev_err(dev, "Cannot find device tree node\n"); + return -ENODEV; + } + if (dev_get_platdata(dev)) { + dev_err(dev, "Platform data is not supported\n"); + return -EINVAL; + } + + muxc = i2c_mux_alloc(NULL, dev, 1, sizeof(*arb), I2C_MUX_ARBITRATOR, + i2c_arbitrator_select, i2c_arbitrator_deselect); + if (!muxc) + return -ENOMEM; + arb = i2c_mux_priv(muxc); + + platform_set_drvdata(pdev, muxc); + + /* Request GPIOs, our GPIO as unclaimed to begin with */ + arb->our_gpio = devm_gpiod_get(dev, "our-claim", GPIOD_OUT_LOW); + if (IS_ERR(arb->our_gpio)) { + dev_err(dev, "could not get \"our-claim\" GPIO (%ld)\n", + PTR_ERR(arb->our_gpio)); + return PTR_ERR(arb->our_gpio); + } + + arb->their_gpio = devm_gpiod_get(dev, "their-claim", GPIOD_IN); + if (IS_ERR(arb->their_gpio)) { + dev_err(dev, "could not get \"their-claim\" GPIO (%ld)\n", + PTR_ERR(arb->their_gpio)); + return PTR_ERR(arb->their_gpio); + } + + /* At the moment we only support a single two master (us + 1 other) */ + dummy = devm_gpiod_get_index(dev, "their-claim", 1, GPIOD_IN); + if (!IS_ERR(dummy)) { + dev_err(dev, "Only one other master is supported\n"); + return -EINVAL; + } else if (PTR_ERR(dummy) == -EPROBE_DEFER) { + return -EPROBE_DEFER; + } + + /* Arbitration parameters */ + if (of_property_read_u32(np, "slew-delay-us", &arb->slew_delay_us)) + arb->slew_delay_us = 10; + if (of_property_read_u32(np, "wait-retry-us", &arb->wait_retry_us)) + arb->wait_retry_us = 3000; + if (of_property_read_u32(np, "wait-free-us", &arb->wait_free_us)) + arb->wait_free_us = 50000; + + /* Find our parent */ + parent_np = of_parse_phandle(np, "i2c-parent", 0); + if (!parent_np) { + dev_err(dev, "Cannot parse i2c-parent\n"); + return -EINVAL; + } + muxc->parent = of_get_i2c_adapter_by_node(parent_np); + of_node_put(parent_np); + if (!muxc->parent) { + dev_err(dev, "Cannot find parent bus\n"); + return -EPROBE_DEFER; + } + + /* Actually add the mux adapter */ + ret = i2c_mux_add_adapter(muxc, 0, 0, 0); + if (ret) + i2c_put_adapter(muxc->parent); + + return ret; +} + +static int i2c_arbitrator_remove(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); + return 0; +} + +static const struct of_device_id i2c_arbitrator_of_match[] = { + { .compatible = "i2c-arb-gpio-challenge", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_arbitrator_of_match); + +static struct platform_driver i2c_arbitrator_driver = { + .probe = i2c_arbitrator_probe, + .remove = i2c_arbitrator_remove, + .driver = { + .name = "i2c-arb-gpio-challenge", + .of_match_table = i2c_arbitrator_of_match, + }, +}; + +module_platform_driver(i2c_arbitrator_driver); + +MODULE_DESCRIPTION("GPIO-based I2C Arbitration"); +MODULE_AUTHOR("Doug Anderson <dianders@chromium.org>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-arb-gpio-challenge"); diff --git a/drivers/i2c/muxes/i2c-demux-pinctrl.c b/drivers/i2c/muxes/i2c-demux-pinctrl.c new file mode 100644 index 000000000..45a3f7e7b --- /dev/null +++ b/drivers/i2c/muxes/i2c-demux-pinctrl.c @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Pinctrl based I2C DeMultiplexer + * + * Copyright (C) 2015-16 by Wolfram Sang, Sang Engineering <wsa@sang-engineering.com> + * Copyright (C) 2015-16 by Renesas Electronics Corporation + * + * See the bindings doc for DTS setup and the sysfs doc for usage information. + * (look for filenames containing 'i2c-demux-pinctrl' in Documentation/) + */ + +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/sysfs.h> + +struct i2c_demux_pinctrl_chan { + struct device_node *parent_np; + struct i2c_adapter *parent_adap; + struct of_changeset chgset; +}; + +struct i2c_demux_pinctrl_priv { + int cur_chan; + int num_chan; + struct device *dev; + const char *bus_name; + struct i2c_adapter cur_adap; + struct i2c_algorithm algo; + struct i2c_demux_pinctrl_chan chan[]; +}; + +static int i2c_demux_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct i2c_demux_pinctrl_priv *priv = adap->algo_data; + struct i2c_adapter *parent = priv->chan[priv->cur_chan].parent_adap; + + return __i2c_transfer(parent, msgs, num); +} + +static u32 i2c_demux_functionality(struct i2c_adapter *adap) +{ + struct i2c_demux_pinctrl_priv *priv = adap->algo_data; + struct i2c_adapter *parent = priv->chan[priv->cur_chan].parent_adap; + + return parent->algo->functionality(parent); +} + +static int i2c_demux_activate_master(struct i2c_demux_pinctrl_priv *priv, u32 new_chan) +{ + struct i2c_adapter *adap; + struct pinctrl *p; + int ret; + + ret = of_changeset_apply(&priv->chan[new_chan].chgset); + if (ret) + goto err; + + adap = of_get_i2c_adapter_by_node(priv->chan[new_chan].parent_np); + if (!adap) { + ret = -ENODEV; + goto err_with_revert; + } + + /* + * Check if there are pinctrl states at all. Note: we cant' use + * devm_pinctrl_get_select() because we need to distinguish between + * the -ENODEV from devm_pinctrl_get() and pinctrl_lookup_state(). + */ + p = devm_pinctrl_get(adap->dev.parent); + if (IS_ERR(p)) { + ret = PTR_ERR(p); + /* continue if just no pinctrl states (e.g. i2c-gpio), otherwise exit */ + if (ret != -ENODEV) + goto err_with_put; + } else { + /* there are states. check and use them */ + struct pinctrl_state *s = pinctrl_lookup_state(p, priv->bus_name); + + if (IS_ERR(s)) { + ret = PTR_ERR(s); + goto err_with_put; + } + ret = pinctrl_select_state(p, s); + if (ret < 0) + goto err_with_put; + } + + priv->chan[new_chan].parent_adap = adap; + priv->cur_chan = new_chan; + + /* Now fill out current adapter structure. cur_chan must be up to date */ + priv->algo.master_xfer = i2c_demux_master_xfer; + if (adap->algo->master_xfer_atomic) + priv->algo.master_xfer_atomic = i2c_demux_master_xfer; + priv->algo.functionality = i2c_demux_functionality; + + snprintf(priv->cur_adap.name, sizeof(priv->cur_adap.name), + "i2c-demux (master i2c-%d)", i2c_adapter_id(adap)); + priv->cur_adap.owner = THIS_MODULE; + priv->cur_adap.algo = &priv->algo; + priv->cur_adap.algo_data = priv; + priv->cur_adap.dev.parent = &adap->dev; + priv->cur_adap.class = adap->class; + priv->cur_adap.retries = adap->retries; + priv->cur_adap.timeout = adap->timeout; + priv->cur_adap.quirks = adap->quirks; + priv->cur_adap.dev.of_node = priv->dev->of_node; + ret = i2c_add_adapter(&priv->cur_adap); + if (ret < 0) + goto err_with_put; + + return 0; + + err_with_put: + i2c_put_adapter(adap); + err_with_revert: + of_changeset_revert(&priv->chan[new_chan].chgset); + err: + dev_err(priv->dev, "failed to setup demux-adapter %d (%d)\n", new_chan, ret); + priv->cur_chan = -EINVAL; + return ret; +} + +static int i2c_demux_deactivate_master(struct i2c_demux_pinctrl_priv *priv) +{ + int ret, cur = priv->cur_chan; + + if (cur < 0) + return 0; + + i2c_del_adapter(&priv->cur_adap); + i2c_put_adapter(priv->chan[cur].parent_adap); + + ret = of_changeset_revert(&priv->chan[cur].chgset); + + priv->chan[cur].parent_adap = NULL; + priv->cur_chan = -EINVAL; + + return ret; +} + +static int i2c_demux_change_master(struct i2c_demux_pinctrl_priv *priv, u32 new_chan) +{ + int ret; + + if (new_chan == priv->cur_chan) + return 0; + + ret = i2c_demux_deactivate_master(priv); + if (ret) + return ret; + + return i2c_demux_activate_master(priv, new_chan); +} + +static ssize_t available_masters_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_demux_pinctrl_priv *priv = dev_get_drvdata(dev); + int count = 0, i; + + for (i = 0; i < priv->num_chan && count < PAGE_SIZE; i++) + count += scnprintf(buf + count, PAGE_SIZE - count, "%d:%pOF%c", + i, priv->chan[i].parent_np, + i == priv->num_chan - 1 ? '\n' : ' '); + + return count; +} +static DEVICE_ATTR_RO(available_masters); + +static ssize_t current_master_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_demux_pinctrl_priv *priv = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", priv->cur_chan); +} + +static ssize_t current_master_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_demux_pinctrl_priv *priv = dev_get_drvdata(dev); + unsigned int val; + int ret; + + ret = kstrtouint(buf, 0, &val); + if (ret < 0) + return ret; + + if (val >= priv->num_chan) + return -EINVAL; + + ret = i2c_demux_change_master(priv, val); + + return ret < 0 ? ret : count; +} +static DEVICE_ATTR_RW(current_master); + +static int i2c_demux_pinctrl_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct i2c_demux_pinctrl_priv *priv; + struct property *props; + int num_chan, i, j, err; + + num_chan = of_count_phandle_with_args(np, "i2c-parent", NULL); + if (num_chan < 2) { + dev_err(&pdev->dev, "Need at least two I2C masters to switch\n"); + return -EINVAL; + } + + priv = devm_kzalloc(&pdev->dev, struct_size(priv, chan, num_chan), + GFP_KERNEL); + + props = devm_kcalloc(&pdev->dev, num_chan, sizeof(*props), GFP_KERNEL); + + if (!priv || !props) + return -ENOMEM; + + err = of_property_read_string(np, "i2c-bus-name", &priv->bus_name); + if (err) + return err; + + for (i = 0; i < num_chan; i++) { + struct device_node *adap_np; + + adap_np = of_parse_phandle(np, "i2c-parent", i); + if (!adap_np) { + dev_err(&pdev->dev, "can't get phandle for parent %d\n", i); + err = -ENOENT; + goto err_rollback; + } + priv->chan[i].parent_np = adap_np; + + props[i].name = devm_kstrdup(&pdev->dev, "status", GFP_KERNEL); + props[i].value = devm_kstrdup(&pdev->dev, "ok", GFP_KERNEL); + if (!props[i].name || !props[i].value) { + err = -ENOMEM; + goto err_rollback; + } + props[i].length = 3; + + of_changeset_init(&priv->chan[i].chgset); + of_changeset_update_property(&priv->chan[i].chgset, adap_np, &props[i]); + } + + priv->num_chan = num_chan; + priv->dev = &pdev->dev; + + platform_set_drvdata(pdev, priv); + + pm_runtime_no_callbacks(&pdev->dev); + + /* switch to first parent as active master */ + i2c_demux_activate_master(priv, 0); + + err = device_create_file(&pdev->dev, &dev_attr_available_masters); + if (err) + goto err_rollback_activation; + + err = device_create_file(&pdev->dev, &dev_attr_current_master); + if (err) + goto err_rollback_available; + + return 0; + +err_rollback_available: + device_remove_file(&pdev->dev, &dev_attr_available_masters); +err_rollback_activation: + i2c_demux_deactivate_master(priv); +err_rollback: + for (j = 0; j < i; j++) { + of_node_put(priv->chan[j].parent_np); + of_changeset_destroy(&priv->chan[j].chgset); + } + + return err; +} + +static int i2c_demux_pinctrl_remove(struct platform_device *pdev) +{ + struct i2c_demux_pinctrl_priv *priv = platform_get_drvdata(pdev); + int i; + + device_remove_file(&pdev->dev, &dev_attr_current_master); + device_remove_file(&pdev->dev, &dev_attr_available_masters); + + i2c_demux_deactivate_master(priv); + + for (i = 0; i < priv->num_chan; i++) { + of_node_put(priv->chan[i].parent_np); + of_changeset_destroy(&priv->chan[i].chgset); + } + + return 0; +} + +static const struct of_device_id i2c_demux_pinctrl_of_match[] = { + { .compatible = "i2c-demux-pinctrl", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_demux_pinctrl_of_match); + +static struct platform_driver i2c_demux_pinctrl_driver = { + .driver = { + .name = "i2c-demux-pinctrl", + .of_match_table = i2c_demux_pinctrl_of_match, + }, + .probe = i2c_demux_pinctrl_probe, + .remove = i2c_demux_pinctrl_remove, +}; +module_platform_driver(i2c_demux_pinctrl_driver); + +MODULE_DESCRIPTION("pinctrl-based I2C demux driver"); +MODULE_AUTHOR("Wolfram Sang <wsa@sang-engineering.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-demux-pinctrl"); diff --git a/drivers/i2c/muxes/i2c-mux-gpio.c b/drivers/i2c/muxes/i2c-mux-gpio.c new file mode 100644 index 000000000..4effe563e --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-gpio.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C multiplexer using GPIO API + * + * Peter Korsgaard <peter.korsgaard@barco.com> + */ + +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/platform_data/i2c-mux-gpio.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/bits.h> +#include <linux/gpio/consumer.h> +/* FIXME: stop poking around inside gpiolib */ +#include "../../gpio/gpiolib.h" + +struct gpiomux { + struct i2c_mux_gpio_platform_data data; + int ngpios; + struct gpio_desc **gpios; +}; + +static void i2c_mux_gpio_set(const struct gpiomux *mux, unsigned val) +{ + DECLARE_BITMAP(values, BITS_PER_TYPE(val)); + + values[0] = val; + + gpiod_set_array_value_cansleep(mux->ngpios, mux->gpios, NULL, values); +} + +static int i2c_mux_gpio_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct gpiomux *mux = i2c_mux_priv(muxc); + + i2c_mux_gpio_set(mux, chan); + + return 0; +} + +static int i2c_mux_gpio_deselect(struct i2c_mux_core *muxc, u32 chan) +{ + struct gpiomux *mux = i2c_mux_priv(muxc); + + i2c_mux_gpio_set(mux, mux->data.idle); + + return 0; +} + +#ifdef CONFIG_OF +static int i2c_mux_gpio_probe_dt(struct gpiomux *mux, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *adapter_np, *child; + struct i2c_adapter *adapter; + unsigned *values; + int i = 0; + + if (!np) + return -ENODEV; + + adapter_np = of_parse_phandle(np, "i2c-parent", 0); + if (!adapter_np) { + dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); + return -ENODEV; + } + adapter = of_find_i2c_adapter_by_node(adapter_np); + of_node_put(adapter_np); + if (!adapter) + return -EPROBE_DEFER; + + mux->data.parent = i2c_adapter_id(adapter); + put_device(&adapter->dev); + + mux->data.n_values = of_get_child_count(np); + + values = devm_kcalloc(&pdev->dev, + mux->data.n_values, sizeof(*mux->data.values), + GFP_KERNEL); + if (!values) { + dev_err(&pdev->dev, "Cannot allocate values array"); + return -ENOMEM; + } + + for_each_child_of_node(np, child) { + of_property_read_u32(child, "reg", values + i); + i++; + } + mux->data.values = values; + + if (of_property_read_u32(np, "idle-state", &mux->data.idle)) + mux->data.idle = I2C_MUX_GPIO_NO_IDLE; + + return 0; +} +#else +static int i2c_mux_gpio_probe_dt(struct gpiomux *mux, + struct platform_device *pdev) +{ + return 0; +} +#endif + +static int i2c_mux_gpio_probe(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc; + struct gpiomux *mux; + struct i2c_adapter *parent; + struct i2c_adapter *root; + unsigned initial_state; + int i, ngpios, ret; + + mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + if (!dev_get_platdata(&pdev->dev)) { + ret = i2c_mux_gpio_probe_dt(mux, pdev); + if (ret < 0) + return ret; + } else { + memcpy(&mux->data, dev_get_platdata(&pdev->dev), + sizeof(mux->data)); + } + + ngpios = gpiod_count(&pdev->dev, "mux"); + if (ngpios <= 0) { + dev_err(&pdev->dev, "no valid gpios provided\n"); + return ngpios ?: -EINVAL; + } + mux->ngpios = ngpios; + + parent = i2c_get_adapter(mux->data.parent); + if (!parent) + return -EPROBE_DEFER; + + muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, + ngpios * sizeof(*mux->gpios), 0, + i2c_mux_gpio_select, NULL); + if (!muxc) { + ret = -ENOMEM; + goto alloc_failed; + } + mux->gpios = muxc->priv; + muxc->priv = mux; + + platform_set_drvdata(pdev, muxc); + + root = i2c_root_adapter(&parent->dev); + + muxc->mux_locked = true; + + if (mux->data.idle != I2C_MUX_GPIO_NO_IDLE) { + initial_state = mux->data.idle; + muxc->deselect = i2c_mux_gpio_deselect; + } else { + initial_state = mux->data.values[0]; + } + + for (i = 0; i < ngpios; i++) { + struct device *gpio_dev; + struct gpio_desc *gpiod; + enum gpiod_flags flag; + + if (initial_state & BIT(i)) + flag = GPIOD_OUT_HIGH; + else + flag = GPIOD_OUT_LOW; + gpiod = devm_gpiod_get_index(&pdev->dev, "mux", i, flag); + if (IS_ERR(gpiod)) { + ret = PTR_ERR(gpiod); + goto alloc_failed; + } + + mux->gpios[i] = gpiod; + + if (!muxc->mux_locked) + continue; + + /* FIXME: find a proper way to access the GPIO device */ + gpio_dev = &gpiod->gdev->dev; + muxc->mux_locked = i2c_root_adapter(gpio_dev) == root; + } + + if (muxc->mux_locked) + dev_info(&pdev->dev, "mux-locked i2c mux\n"); + + for (i = 0; i < mux->data.n_values; i++) { + u32 nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; + unsigned int class = mux->data.classes ? mux->data.classes[i] : 0; + + ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class); + if (ret) + goto add_adapter_failed; + } + + dev_info(&pdev->dev, "%d port mux on %s adapter\n", + mux->data.n_values, parent->name); + + return 0; + +add_adapter_failed: + i2c_mux_del_adapters(muxc); +alloc_failed: + i2c_put_adapter(parent); + + return ret; +} + +static int i2c_mux_gpio_remove(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); + + return 0; +} + +static const struct of_device_id i2c_mux_gpio_of_match[] = { + { .compatible = "i2c-mux-gpio", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_mux_gpio_of_match); + +static struct platform_driver i2c_mux_gpio_driver = { + .probe = i2c_mux_gpio_probe, + .remove = i2c_mux_gpio_remove, + .driver = { + .name = "i2c-mux-gpio", + .of_match_table = i2c_mux_gpio_of_match, + }, +}; + +module_platform_driver(i2c_mux_gpio_driver); + +MODULE_DESCRIPTION("GPIO-based I2C multiplexer driver"); +MODULE_AUTHOR("Peter Korsgaard <peter.korsgaard@barco.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:i2c-mux-gpio"); diff --git a/drivers/i2c/muxes/i2c-mux-gpmux.c b/drivers/i2c/muxes/i2c-mux-gpmux.c new file mode 100644 index 000000000..0ebc12575 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-gpmux.c @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * General Purpose I2C multiplexer + * + * Copyright (C) 2017 Axentia Technologies AB + * + * Author: Peter Rosin <peda@axentia.se> + */ + +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/module.h> +#include <linux/mux/consumer.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> + +struct mux { + struct mux_control *control; + + bool do_not_deselect; +}; + +static int i2c_mux_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct mux *mux = i2c_mux_priv(muxc); + int ret; + + ret = mux_control_select(mux->control, chan); + mux->do_not_deselect = ret < 0; + + return ret; +} + +static int i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan) +{ + struct mux *mux = i2c_mux_priv(muxc); + + if (mux->do_not_deselect) + return 0; + + return mux_control_deselect(mux->control); +} + +static struct i2c_adapter *mux_parent_adapter(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct device_node *parent_np; + struct i2c_adapter *parent; + + parent_np = of_parse_phandle(np, "i2c-parent", 0); + if (!parent_np) { + dev_err(dev, "Cannot parse i2c-parent\n"); + return ERR_PTR(-ENODEV); + } + parent = of_get_i2c_adapter_by_node(parent_np); + of_node_put(parent_np); + if (!parent) + return ERR_PTR(-EPROBE_DEFER); + + return parent; +} + +static const struct of_device_id i2c_mux_of_match[] = { + { .compatible = "i2c-mux", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_mux_of_match); + +static int i2c_mux_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct device_node *child; + struct i2c_mux_core *muxc; + struct mux *mux; + struct i2c_adapter *parent; + int children; + int ret; + + if (!np) + return -ENODEV; + + mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + mux->control = devm_mux_control_get(dev, NULL); + if (IS_ERR(mux->control)) + return dev_err_probe(dev, PTR_ERR(mux->control), + "failed to get control-mux\n"); + + parent = mux_parent_adapter(dev); + if (IS_ERR(parent)) + return dev_err_probe(dev, PTR_ERR(parent), + "failed to get i2c-parent adapter\n"); + + children = of_get_child_count(np); + + muxc = i2c_mux_alloc(parent, dev, children, 0, 0, + i2c_mux_select, i2c_mux_deselect); + if (!muxc) { + ret = -ENOMEM; + goto err_parent; + } + muxc->priv = mux; + + platform_set_drvdata(pdev, muxc); + + muxc->mux_locked = of_property_read_bool(np, "mux-locked"); + + for_each_child_of_node(np, child) { + u32 chan; + + ret = of_property_read_u32(child, "reg", &chan); + if (ret < 0) { + dev_err(dev, "no reg property for node '%pOFn'\n", + child); + goto err_children; + } + + if (chan >= mux_control_states(mux->control)) { + dev_err(dev, "invalid reg %u\n", chan); + ret = -EINVAL; + goto err_children; + } + + ret = i2c_mux_add_adapter(muxc, 0, chan, 0); + if (ret) + goto err_children; + } + + dev_info(dev, "%d-port mux on %s adapter\n", children, parent->name); + + return 0; + +err_children: + of_node_put(child); + i2c_mux_del_adapters(muxc); +err_parent: + i2c_put_adapter(parent); + + return ret; +} + +static int i2c_mux_remove(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); + + return 0; +} + +static struct platform_driver i2c_mux_driver = { + .probe = i2c_mux_probe, + .remove = i2c_mux_remove, + .driver = { + .name = "i2c-mux-gpmux", + .of_match_table = i2c_mux_of_match, + }, +}; +module_platform_driver(i2c_mux_driver); + +MODULE_DESCRIPTION("General Purpose I2C multiplexer driver"); +MODULE_AUTHOR("Peter Rosin <peda@axentia.se>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/muxes/i2c-mux-ltc4306.c b/drivers/i2c/muxes/i2c-mux-ltc4306.c new file mode 100644 index 000000000..704f1e50f --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-ltc4306.c @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Linear Technology LTC4306 and LTC4305 I2C multiplexer/switch + * + * Copyright (C) 2017 Analog Devices Inc. + * + * Based on: i2c-mux-pca954x.c + * + * Datasheet: http://cds.linear.com/docs/en/datasheet/4306.pdf + */ + +#include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> +#include <linux/i2c-mux.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/property.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define LTC4305_MAX_NCHANS 2 +#define LTC4306_MAX_NCHANS 4 + +#define LTC_REG_STATUS 0x0 +#define LTC_REG_CONFIG 0x1 +#define LTC_REG_MODE 0x2 +#define LTC_REG_SWITCH 0x3 + +#define LTC_DOWNSTREAM_ACCL_EN BIT(6) +#define LTC_UPSTREAM_ACCL_EN BIT(7) + +#define LTC_GPIO_ALL_INPUT 0xC0 +#define LTC_SWITCH_MASK 0xF0 + +enum ltc_type { + ltc_4305, + ltc_4306, +}; + +struct chip_desc { + u8 nchans; + u8 num_gpios; +}; + +struct ltc4306 { + struct regmap *regmap; + struct gpio_chip gpiochip; + const struct chip_desc *chip; +}; + +static const struct chip_desc chips[] = { + [ltc_4305] = { + .nchans = LTC4305_MAX_NCHANS, + }, + [ltc_4306] = { + .nchans = LTC4306_MAX_NCHANS, + .num_gpios = 2, + }, +}; + +static bool ltc4306_is_volatile_reg(struct device *dev, unsigned int reg) +{ + return (reg == LTC_REG_CONFIG) ? true : false; +} + +static const struct regmap_config ltc4306_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = LTC_REG_SWITCH, + .volatile_reg = ltc4306_is_volatile_reg, + .cache_type = REGCACHE_FLAT, +}; + +static int ltc4306_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, LTC_REG_CONFIG, &val); + if (ret < 0) + return ret; + + return !!(val & BIT(1 - offset)); +} + +static void ltc4306_gpio_set(struct gpio_chip *chip, unsigned int offset, + int value) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + regmap_update_bits(data->regmap, LTC_REG_CONFIG, BIT(5 - offset), + value ? BIT(5 - offset) : 0); +} + +static int ltc4306_gpio_get_direction(struct gpio_chip *chip, + unsigned int offset) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + unsigned int val; + int ret; + + ret = regmap_read(data->regmap, LTC_REG_MODE, &val); + if (ret < 0) + return ret; + + return !!(val & BIT(7 - offset)); +} + +static int ltc4306_gpio_direction_input(struct gpio_chip *chip, + unsigned int offset) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + return regmap_update_bits(data->regmap, LTC_REG_MODE, + BIT(7 - offset), BIT(7 - offset)); +} + +static int ltc4306_gpio_direction_output(struct gpio_chip *chip, + unsigned int offset, int value) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + + ltc4306_gpio_set(chip, offset, value); + return regmap_update_bits(data->regmap, LTC_REG_MODE, + BIT(7 - offset), 0); +} + +static int ltc4306_gpio_set_config(struct gpio_chip *chip, + unsigned int offset, unsigned long config) +{ + struct ltc4306 *data = gpiochip_get_data(chip); + unsigned int val; + + switch (pinconf_to_config_param(config)) { + case PIN_CONFIG_DRIVE_OPEN_DRAIN: + val = 0; + break; + case PIN_CONFIG_DRIVE_PUSH_PULL: + val = BIT(4 - offset); + break; + default: + return -ENOTSUPP; + } + + return regmap_update_bits(data->regmap, LTC_REG_MODE, + BIT(4 - offset), val); +} + +static int ltc4306_gpio_init(struct ltc4306 *data) +{ + struct device *dev = regmap_get_device(data->regmap); + + if (!data->chip->num_gpios) + return 0; + + data->gpiochip.label = dev_name(dev); + data->gpiochip.base = -1; + data->gpiochip.ngpio = data->chip->num_gpios; + data->gpiochip.parent = dev; + data->gpiochip.can_sleep = true; + data->gpiochip.get_direction = ltc4306_gpio_get_direction; + data->gpiochip.direction_input = ltc4306_gpio_direction_input; + data->gpiochip.direction_output = ltc4306_gpio_direction_output; + data->gpiochip.get = ltc4306_gpio_get; + data->gpiochip.set = ltc4306_gpio_set; + data->gpiochip.set_config = ltc4306_gpio_set_config; + data->gpiochip.owner = THIS_MODULE; + + /* gpiolib assumes all GPIOs default input */ + regmap_write(data->regmap, LTC_REG_MODE, LTC_GPIO_ALL_INPUT); + + return devm_gpiochip_add_data(dev, &data->gpiochip, data); +} + +static int ltc4306_select_mux(struct i2c_mux_core *muxc, u32 chan) +{ + struct ltc4306 *data = i2c_mux_priv(muxc); + + return regmap_update_bits(data->regmap, LTC_REG_SWITCH, + LTC_SWITCH_MASK, BIT(7 - chan)); +} + +static int ltc4306_deselect_mux(struct i2c_mux_core *muxc, u32 chan) +{ + struct ltc4306 *data = i2c_mux_priv(muxc); + + return regmap_update_bits(data->regmap, LTC_REG_SWITCH, + LTC_SWITCH_MASK, 0); +} + +static const struct i2c_device_id ltc4306_id[] = { + { "ltc4305", ltc_4305 }, + { "ltc4306", ltc_4306 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4306_id); + +static const struct of_device_id ltc4306_of_match[] = { + { .compatible = "lltc,ltc4305", .data = &chips[ltc_4305] }, + { .compatible = "lltc,ltc4306", .data = &chips[ltc_4306] }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc4306_of_match); + +static int ltc4306_probe(struct i2c_client *client) +{ + struct i2c_adapter *adap = client->adapter; + const struct chip_desc *chip; + struct i2c_mux_core *muxc; + struct ltc4306 *data; + struct gpio_desc *gpio; + bool idle_disc; + unsigned int val = 0; + int num, ret; + + chip = of_device_get_match_data(&client->dev); + + if (!chip) + chip = &chips[i2c_match_id(ltc4306_id, client)->driver_data]; + + idle_disc = device_property_read_bool(&client->dev, + "i2c-mux-idle-disconnect"); + + muxc = i2c_mux_alloc(adap, &client->dev, + chip->nchans, sizeof(*data), + I2C_MUX_LOCKED, ltc4306_select_mux, + idle_disc ? ltc4306_deselect_mux : NULL); + if (!muxc) + return -ENOMEM; + data = i2c_mux_priv(muxc); + data->chip = chip; + + i2c_set_clientdata(client, muxc); + + data->regmap = devm_regmap_init_i2c(client, <c4306_regmap_config); + if (IS_ERR(data->regmap)) { + ret = PTR_ERR(data->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + ret); + return ret; + } + + /* Reset and enable the mux if an enable GPIO is specified. */ + gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + udelay(1); + gpiod_set_value(gpio, 1); + } + + /* + * Write the mux register at addr to verify + * that the mux is in fact present. This also + * initializes the mux to disconnected state. + */ + if (regmap_write(data->regmap, LTC_REG_SWITCH, 0) < 0) { + dev_warn(&client->dev, "probe failed\n"); + return -ENODEV; + } + + if (device_property_read_bool(&client->dev, + "ltc,downstream-accelerators-enable")) + val |= LTC_DOWNSTREAM_ACCL_EN; + + if (device_property_read_bool(&client->dev, + "ltc,upstream-accelerators-enable")) + val |= LTC_UPSTREAM_ACCL_EN; + + if (regmap_write(data->regmap, LTC_REG_CONFIG, val) < 0) + return -ENODEV; + + ret = ltc4306_gpio_init(data); + if (ret < 0) + return ret; + + /* Now create an adapter for each channel */ + for (num = 0; num < chip->nchans; num++) { + ret = i2c_mux_add_adapter(muxc, 0, num, 0); + if (ret) { + i2c_mux_del_adapters(muxc); + return ret; + } + } + + dev_info(&client->dev, + "registered %d multiplexed busses for I2C switch %s\n", + num, client->name); + + return 0; +} + +static int ltc4306_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 ltc4306_driver = { + .driver = { + .name = "ltc4306", + .of_match_table = of_match_ptr(ltc4306_of_match), + }, + .probe_new = ltc4306_probe, + .remove = ltc4306_remove, + .id_table = ltc4306_id, +}; + +module_i2c_driver(ltc4306_driver); + +MODULE_AUTHOR("Michael Hennerich <michael.hennerich@analog.com>"); +MODULE_DESCRIPTION("Linear Technology LTC4306, LTC4305 I2C mux/switch driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/muxes/i2c-mux-mlxcpld.c b/drivers/i2c/muxes/i2c-mux-mlxcpld.c new file mode 100644 index 000000000..5ed55ca4f --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-mlxcpld.c @@ -0,0 +1,201 @@ +/* + * 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_data/x86/mlxcpld.h> +#include <linux/platform_device.h> +#include <linux/slab.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); + union i2c_smbus_data data = { .byte = val }; + + return __i2c_smbus_xfer(adap, client->addr, client->flags, + I2C_SMBUS_WRITE, pdata->sel_reg_addr, + I2C_SMBUS_BYTE_DATA, &data); +} + +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 = client->adapter; + 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"); diff --git a/drivers/i2c/muxes/i2c-mux-pca9541.c b/drivers/i2c/muxes/i2c-mux-pca9541.c new file mode 100644 index 000000000..6daec8d3d --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-pca9541.c @@ -0,0 +1,350 @@ +/* + * I2C multiplexer driver for PCA9541 bus master selector + * + * Copyright (c) 2010 Ericsson AB. + * + * Author: Guenter Roeck <linux@roeck-us.net> + * + * Derived from: + * pca954x.c + * + * Copyright (c) 2008-2009 Rodolfo Giometti <giometti@linux.it> + * Copyright (c) 2008-2009 Eurotech S.p.A. <info@eurotech.it> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/bitops.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/slab.h> + +/* + * The PCA9541 is a bus master selector. It supports two I2C masters connected + * to a single slave bus. + * + * Before each bus transaction, a master has to acquire bus ownership. After the + * transaction is complete, bus ownership has to be released. This fits well + * into the I2C multiplexer framework, which provides select and release + * functions for this purpose. For this reason, this driver is modeled as + * single-channel I2C bus multiplexer. + * + * This driver assumes that the two bus masters are controlled by two different + * hosts. If a single host controls both masters, platform code has to ensure + * that only one of the masters is instantiated at any given time. + */ + +#define PCA9541_CONTROL 0x01 +#define PCA9541_ISTAT 0x02 + +#define PCA9541_CTL_MYBUS BIT(0) +#define PCA9541_CTL_NMYBUS BIT(1) +#define PCA9541_CTL_BUSON BIT(2) +#define PCA9541_CTL_NBUSON BIT(3) +#define PCA9541_CTL_BUSINIT BIT(4) +#define PCA9541_CTL_TESTON BIT(6) +#define PCA9541_CTL_NTESTON BIT(7) + +#define PCA9541_ISTAT_INTIN BIT(0) +#define PCA9541_ISTAT_BUSINIT BIT(1) +#define PCA9541_ISTAT_BUSOK BIT(2) +#define PCA9541_ISTAT_BUSLOST BIT(3) +#define PCA9541_ISTAT_MYTEST BIT(6) +#define PCA9541_ISTAT_NMYTEST BIT(7) + +#define BUSON (PCA9541_CTL_BUSON | PCA9541_CTL_NBUSON) +#define MYBUS (PCA9541_CTL_MYBUS | PCA9541_CTL_NMYBUS) +#define mybus(x) (!((x) & MYBUS) || ((x) & MYBUS) == MYBUS) +#define busoff(x) (!((x) & BUSON) || ((x) & BUSON) == BUSON) + +/* arbitration timeouts, in jiffies */ +#define ARB_TIMEOUT (HZ / 8) /* 125 ms until forcing bus ownership */ +#define ARB2_TIMEOUT (HZ / 4) /* 250 ms until acquisition failure */ + +/* arbitration retry delays, in us */ +#define SELECT_DELAY_SHORT 50 +#define SELECT_DELAY_LONG 1000 + +struct pca9541 { + struct i2c_client *client; + unsigned long select_timeout; + unsigned long arb_timeout; +}; + +static const struct i2c_device_id pca9541_id[] = { + {"pca9541", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pca9541_id); + +#ifdef CONFIG_OF +static const struct of_device_id pca9541_of_match[] = { + { .compatible = "nxp,pca9541" }, + {} +}; +MODULE_DEVICE_TABLE(of, pca9541_of_match); +#endif + +/* + * Write to chip register. Don't use i2c_transfer()/i2c_smbus_xfer() + * as they will try to lock the adapter a second time. + */ +static int pca9541_reg_write(struct i2c_client *client, u8 command, u8 val) +{ + struct i2c_adapter *adap = client->adapter; + union i2c_smbus_data data = { .byte = val }; + + return __i2c_smbus_xfer(adap, client->addr, client->flags, + I2C_SMBUS_WRITE, command, + I2C_SMBUS_BYTE_DATA, &data); +} + +/* + * Read from chip register. Don't use i2c_transfer()/i2c_smbus_xfer() + * as they will try to lock adapter a second time. + */ +static int pca9541_reg_read(struct i2c_client *client, u8 command) +{ + struct i2c_adapter *adap = client->adapter; + union i2c_smbus_data data; + int ret; + + ret = __i2c_smbus_xfer(adap, client->addr, client->flags, + I2C_SMBUS_READ, command, + I2C_SMBUS_BYTE_DATA, &data); + + return ret ?: data.byte; +} + +/* + * Arbitration management functions + */ + +/* Release bus. Also reset NTESTON and BUSINIT if it was set. */ +static void pca9541_release_bus(struct i2c_client *client) +{ + int reg; + + reg = pca9541_reg_read(client, PCA9541_CONTROL); + if (reg >= 0 && !busoff(reg) && mybus(reg)) + pca9541_reg_write(client, PCA9541_CONTROL, + (reg & PCA9541_CTL_NBUSON) >> 1); +} + +/* + * Arbitration is defined as a two-step process. A bus master can only activate + * the slave bus if it owns it; otherwise it has to request ownership first. + * This multi-step process ensures that access contention is resolved + * gracefully. + * + * Bus Ownership Other master Action + * state requested access + * ---------------------------------------------------- + * off - yes wait for arbitration timeout or + * for other master to drop request + * off no no take ownership + * off yes no turn on bus + * on yes - done + * on no - wait for arbitration timeout or + * for other master to release bus + * + * The main contention point occurs if the slave bus is off and both masters + * request ownership at the same time. In this case, one master will turn on + * the slave bus, believing that it owns it. The other master will request + * bus ownership. Result is that the bus is turned on, and master which did + * _not_ own the slave bus before ends up owning it. + */ + +/* Control commands per PCA9541 datasheet */ +static const u8 pca9541_control[16] = { + 4, 0, 1, 5, 4, 4, 5, 5, 0, 0, 1, 1, 0, 4, 5, 1 +}; + +/* + * Channel arbitration + * + * Return values: + * <0: error + * 0 : bus not acquired + * 1 : bus acquired + */ +static int pca9541_arbitrate(struct i2c_client *client) +{ + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct pca9541 *data = i2c_mux_priv(muxc); + int reg; + + reg = pca9541_reg_read(client, PCA9541_CONTROL); + if (reg < 0) + return reg; + + if (busoff(reg)) { + int istat; + /* + * Bus is off. Request ownership or turn it on unless + * other master requested ownership. + */ + istat = pca9541_reg_read(client, PCA9541_ISTAT); + if (!(istat & PCA9541_ISTAT_NMYTEST) + || time_is_before_eq_jiffies(data->arb_timeout)) { + /* + * Other master did not request ownership, + * or arbitration timeout expired. Take the bus. + */ + pca9541_reg_write(client, + PCA9541_CONTROL, + pca9541_control[reg & 0x0f] + | PCA9541_CTL_NTESTON); + data->select_timeout = SELECT_DELAY_SHORT; + } else { + /* + * Other master requested ownership. + * Set extra long timeout to give it time to acquire it. + */ + data->select_timeout = SELECT_DELAY_LONG * 2; + } + } else if (mybus(reg)) { + /* + * Bus is on, and we own it. We are done with acquisition. + * Reset NTESTON and BUSINIT, then return success. + */ + if (reg & (PCA9541_CTL_NTESTON | PCA9541_CTL_BUSINIT)) + pca9541_reg_write(client, + PCA9541_CONTROL, + reg & ~(PCA9541_CTL_NTESTON + | PCA9541_CTL_BUSINIT)); + return 1; + } else { + /* + * Other master owns the bus. + * If arbitration timeout has expired, force ownership. + * Otherwise request it. + */ + data->select_timeout = SELECT_DELAY_LONG; + if (time_is_before_eq_jiffies(data->arb_timeout)) { + /* Time is up, take the bus and reset it. */ + pca9541_reg_write(client, + PCA9541_CONTROL, + pca9541_control[reg & 0x0f] + | PCA9541_CTL_BUSINIT + | PCA9541_CTL_NTESTON); + } else { + /* Request bus ownership if needed */ + if (!(reg & PCA9541_CTL_NTESTON)) + pca9541_reg_write(client, + PCA9541_CONTROL, + reg | PCA9541_CTL_NTESTON); + } + } + return 0; +} + +static int pca9541_select_chan(struct i2c_mux_core *muxc, u32 chan) +{ + struct pca9541 *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + int ret; + unsigned long timeout = jiffies + ARB2_TIMEOUT; + /* give up after this time */ + + data->arb_timeout = jiffies + ARB_TIMEOUT; + /* force bus ownership after this time */ + + do { + ret = pca9541_arbitrate(client); + if (ret) + return ret < 0 ? ret : 0; + + if (data->select_timeout == SELECT_DELAY_SHORT) + udelay(data->select_timeout); + else + msleep(data->select_timeout / 1000); + } while (time_is_after_eq_jiffies(timeout)); + + return -ETIMEDOUT; +} + +static int pca9541_release_chan(struct i2c_mux_core *muxc, u32 chan) +{ + struct pca9541 *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + + pca9541_release_bus(client); + return 0; +} + +/* + * I2C init/probing/exit functions + */ +static int pca9541_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adap = client->adapter; + struct i2c_mux_core *muxc; + struct pca9541 *data; + int ret; + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* + * I2C accesses are unprotected here. + * We have to lock the I2C segment before releasing the bus. + */ + i2c_lock_bus(adap, I2C_LOCK_SEGMENT); + pca9541_release_bus(client); + i2c_unlock_bus(adap, I2C_LOCK_SEGMENT); + + /* Create mux adapter */ + + muxc = i2c_mux_alloc(adap, &client->dev, 1, sizeof(*data), + I2C_MUX_ARBITRATOR, + pca9541_select_chan, pca9541_release_chan); + if (!muxc) + return -ENOMEM; + + data = i2c_mux_priv(muxc); + data->client = client; + + i2c_set_clientdata(client, muxc); + + ret = i2c_mux_add_adapter(muxc, 0, 0, 0); + if (ret) + return ret; + + dev_info(&client->dev, "registered master selector for I2C %s\n", + client->name); + + return 0; +} + +static int pca9541_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 pca9541_driver = { + .driver = { + .name = "pca9541", + .of_match_table = of_match_ptr(pca9541_of_match), + }, + .probe = pca9541_probe, + .remove = pca9541_remove, + .id_table = pca9541_id, +}; + +module_i2c_driver(pca9541_driver); + +MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); +MODULE_DESCRIPTION("PCA9541 I2C master selector driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c new file mode 100644 index 000000000..4ad665757 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-pca954x.c @@ -0,0 +1,567 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C multiplexer + * + * Copyright (c) 2008-2009 Rodolfo Giometti <giometti@linux.it> + * Copyright (c) 2008-2009 Eurotech S.p.A. <info@eurotech.it> + * + * This module supports the PCA954x and PCA984x series of I2C multiplexer/switch + * chips made by NXP Semiconductors. + * This includes the: + * PCA9540, PCA9542, PCA9543, PCA9544, PCA9545, PCA9546, PCA9547, + * PCA9548, PCA9846, PCA9847, PCA9848 and PCA9849. + * + * These chips are all controlled via the I2C bus itself, and all have a + * single 8-bit register. The upstream "parent" bus fans out to two, + * four, or eight downstream busses or channels; which of these + * are selected is determined by the chip type and register contents. A + * mux can select only one sub-bus at a time; a switch can select any + * combination simultaneously. + * + * Based on: + * pca954x.c from Kumar Gala <galak@kernel.crashing.org> + * Copyright (C) 2006 + * + * Based on: + * pca954x.c from Ken Harrenstien + * Copyright (C) 2004 Google, Inc. (Ken Harrenstien) + * + * Based on: + * i2c-virtual_cb.c from Brian Kuschak <bkuschak@yahoo.com> + * and + * pca9540.c from Jean Delvare <jdelvare@suse.de>. + */ + +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/module.h> +#include <linux/pm.h> +#include <linux/property.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <dt-bindings/mux/mux.h> + +#define PCA954X_MAX_NCHANS 8 + +#define PCA954X_IRQ_OFFSET 4 + +enum pca_type { + pca_9540, + pca_9542, + pca_9543, + pca_9544, + pca_9545, + pca_9546, + pca_9547, + pca_9548, + pca_9846, + pca_9847, + pca_9848, + pca_9849, +}; + +struct chip_desc { + u8 nchans; + u8 enable; /* used for muxes only */ + u8 has_irq; + enum muxtype { + pca954x_ismux = 0, + pca954x_isswi + } muxtype; + struct i2c_device_identity id; +}; + +struct pca954x { + const struct chip_desc *chip; + + u8 last_chan; /* last register value */ + /* MUX_IDLE_AS_IS, MUX_IDLE_DISCONNECT or >= 0 for channel */ + s32 idle_state; + + struct i2c_client *client; + + struct irq_domain *irq; + unsigned int irq_mask; + raw_spinlock_t lock; +}; + +/* Provide specs for the PCA954x types we know about */ +static const struct chip_desc chips[] = { + [pca_9540] = { + .nchans = 2, + .enable = 0x4, + .muxtype = pca954x_ismux, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9542] = { + .nchans = 2, + .enable = 0x4, + .has_irq = 1, + .muxtype = pca954x_ismux, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9543] = { + .nchans = 2, + .has_irq = 1, + .muxtype = pca954x_isswi, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9544] = { + .nchans = 4, + .enable = 0x4, + .has_irq = 1, + .muxtype = pca954x_ismux, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9545] = { + .nchans = 4, + .has_irq = 1, + .muxtype = pca954x_isswi, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9546] = { + .nchans = 4, + .muxtype = pca954x_isswi, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9547] = { + .nchans = 8, + .enable = 0x8, + .muxtype = pca954x_ismux, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9548] = { + .nchans = 8, + .muxtype = pca954x_isswi, + .id = { .manufacturer_id = I2C_DEVICE_ID_NONE }, + }, + [pca_9846] = { + .nchans = 4, + .muxtype = pca954x_isswi, + .id = { + .manufacturer_id = I2C_DEVICE_ID_NXP_SEMICONDUCTORS, + .part_id = 0x10b, + }, + }, + [pca_9847] = { + .nchans = 8, + .enable = 0x8, + .muxtype = pca954x_ismux, + .id = { + .manufacturer_id = I2C_DEVICE_ID_NXP_SEMICONDUCTORS, + .part_id = 0x108, + }, + }, + [pca_9848] = { + .nchans = 8, + .muxtype = pca954x_isswi, + .id = { + .manufacturer_id = I2C_DEVICE_ID_NXP_SEMICONDUCTORS, + .part_id = 0x10a, + }, + }, + [pca_9849] = { + .nchans = 4, + .enable = 0x4, + .muxtype = pca954x_ismux, + .id = { + .manufacturer_id = I2C_DEVICE_ID_NXP_SEMICONDUCTORS, + .part_id = 0x109, + }, + }, +}; + +static const struct i2c_device_id pca954x_id[] = { + { "pca9540", pca_9540 }, + { "pca9542", pca_9542 }, + { "pca9543", pca_9543 }, + { "pca9544", pca_9544 }, + { "pca9545", pca_9545 }, + { "pca9546", pca_9546 }, + { "pca9547", pca_9547 }, + { "pca9548", pca_9548 }, + { "pca9846", pca_9846 }, + { "pca9847", pca_9847 }, + { "pca9848", pca_9848 }, + { "pca9849", pca_9849 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca954x_id); + +static const struct of_device_id pca954x_of_match[] = { + { .compatible = "nxp,pca9540", .data = &chips[pca_9540] }, + { .compatible = "nxp,pca9542", .data = &chips[pca_9542] }, + { .compatible = "nxp,pca9543", .data = &chips[pca_9543] }, + { .compatible = "nxp,pca9544", .data = &chips[pca_9544] }, + { .compatible = "nxp,pca9545", .data = &chips[pca_9545] }, + { .compatible = "nxp,pca9546", .data = &chips[pca_9546] }, + { .compatible = "nxp,pca9547", .data = &chips[pca_9547] }, + { .compatible = "nxp,pca9548", .data = &chips[pca_9548] }, + { .compatible = "nxp,pca9846", .data = &chips[pca_9846] }, + { .compatible = "nxp,pca9847", .data = &chips[pca_9847] }, + { .compatible = "nxp,pca9848", .data = &chips[pca_9848] }, + { .compatible = "nxp,pca9849", .data = &chips[pca_9849] }, + {} +}; +MODULE_DEVICE_TABLE(of, pca954x_of_match); + +/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer() + for this as they will try to lock adapter a second time */ +static int pca954x_reg_write(struct i2c_adapter *adap, + struct i2c_client *client, u8 val) +{ + union i2c_smbus_data dummy; + + return __i2c_smbus_xfer(adap, client->addr, client->flags, + I2C_SMBUS_WRITE, val, + I2C_SMBUS_BYTE, &dummy); +} + +static u8 pca954x_regval(struct pca954x *data, u8 chan) +{ + /* We make switches look like muxes, not sure how to be smarter. */ + if (data->chip->muxtype == pca954x_ismux) + return chan | data->chip->enable; + else + return 1 << chan; +} + +static int pca954x_select_chan(struct i2c_mux_core *muxc, u32 chan) +{ + struct pca954x *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + u8 regval; + int ret = 0; + + regval = pca954x_regval(data, chan); + /* Only select the channel if its different from the last channel */ + if (data->last_chan != regval) { + ret = pca954x_reg_write(muxc->parent, client, regval); + data->last_chan = ret < 0 ? 0 : regval; + } + + return ret; +} + +static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan) +{ + struct pca954x *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + s32 idle_state; + + idle_state = READ_ONCE(data->idle_state); + if (idle_state >= 0) + /* Set the mux back to a predetermined channel */ + return pca954x_select_chan(muxc, idle_state); + + if (idle_state == MUX_IDLE_DISCONNECT) { + /* Deselect active channel */ + data->last_chan = 0; + return pca954x_reg_write(muxc->parent, client, + data->last_chan); + } + + /* otherwise leave as-is */ + + return 0; +} + +static ssize_t idle_state_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct pca954x *data = i2c_mux_priv(muxc); + + return sprintf(buf, "%d\n", READ_ONCE(data->idle_state)); +} + +static ssize_t idle_state_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct pca954x *data = i2c_mux_priv(muxc); + int val; + int ret; + + ret = kstrtoint(buf, 0, &val); + if (ret < 0) + return ret; + + if (val != MUX_IDLE_AS_IS && val != MUX_IDLE_DISCONNECT && + (val < 0 || val >= data->chip->nchans)) + return -EINVAL; + + i2c_lock_bus(muxc->parent, I2C_LOCK_SEGMENT); + + WRITE_ONCE(data->idle_state, val); + /* + * Set the mux into a state consistent with the new + * idle_state. + */ + if (data->last_chan || val != MUX_IDLE_DISCONNECT) + ret = pca954x_deselect_mux(muxc, 0); + + i2c_unlock_bus(muxc->parent, I2C_LOCK_SEGMENT); + + return ret < 0 ? ret : count; +} + +static DEVICE_ATTR_RW(idle_state); + +static irqreturn_t pca954x_irq_handler(int irq, void *dev_id) +{ + struct pca954x *data = dev_id; + unsigned long pending; + int ret, i; + + ret = i2c_smbus_read_byte(data->client); + if (ret < 0) + return IRQ_NONE; + + pending = (ret >> PCA954X_IRQ_OFFSET) & (BIT(data->chip->nchans) - 1); + for_each_set_bit(i, &pending, data->chip->nchans) + handle_nested_irq(irq_linear_revmap(data->irq, i)); + + return IRQ_RETVAL(pending); +} + +static int pca954x_irq_set_type(struct irq_data *idata, unsigned int type) +{ + if ((type & IRQ_TYPE_SENSE_MASK) != IRQ_TYPE_LEVEL_LOW) + return -EINVAL; + return 0; +} + +static struct irq_chip pca954x_irq_chip = { + .name = "i2c-mux-pca954x", + .irq_set_type = pca954x_irq_set_type, +}; + +static int pca954x_irq_setup(struct i2c_mux_core *muxc) +{ + struct pca954x *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + int c, irq; + + if (!data->chip->has_irq || client->irq <= 0) + return 0; + + raw_spin_lock_init(&data->lock); + + data->irq = irq_domain_add_linear(client->dev.of_node, + data->chip->nchans, + &irq_domain_simple_ops, data); + if (!data->irq) + return -ENODEV; + + for (c = 0; c < data->chip->nchans; c++) { + irq = irq_create_mapping(data->irq, c); + if (!irq) { + dev_err(&client->dev, "failed irq create map\n"); + return -EINVAL; + } + irq_set_chip_data(irq, data); + irq_set_chip_and_handler(irq, &pca954x_irq_chip, + handle_simple_irq); + } + + return 0; +} + +static void pca954x_cleanup(struct i2c_mux_core *muxc) +{ + struct pca954x *data = i2c_mux_priv(muxc); + int c, irq; + + if (data->irq) { + for (c = 0; c < data->chip->nchans; c++) { + irq = irq_find_mapping(data->irq, c); + irq_dispose_mapping(irq); + } + irq_domain_remove(data->irq); + } + i2c_mux_del_adapters(muxc); +} + +static int pca954x_init(struct i2c_client *client, struct pca954x *data) +{ + int ret; + + if (data->idle_state >= 0) + data->last_chan = pca954x_regval(data, data->idle_state); + else + data->last_chan = 0; /* Disconnect multiplexer */ + + ret = i2c_smbus_write_byte(client, data->last_chan); + if (ret < 0) + data->last_chan = 0; + + return ret; +} + +/* + * I2C init/probing/exit functions + */ +static int pca954x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adap = client->adapter; + struct device *dev = &client->dev; + struct gpio_desc *gpio; + struct i2c_mux_core *muxc; + struct pca954x *data; + int num; + int ret; + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_BYTE)) + return -ENODEV; + + muxc = i2c_mux_alloc(adap, dev, PCA954X_MAX_NCHANS, sizeof(*data), 0, + pca954x_select_chan, pca954x_deselect_mux); + if (!muxc) + return -ENOMEM; + data = i2c_mux_priv(muxc); + + i2c_set_clientdata(client, muxc); + data->client = client; + + /* Reset the mux if a reset GPIO is specified. */ + gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + if (gpio) { + udelay(1); + gpiod_set_value_cansleep(gpio, 0); + /* Give the chip some time to recover. */ + udelay(1); + } + + data->chip = device_get_match_data(dev); + if (!data->chip) + data->chip = &chips[id->driver_data]; + + if (data->chip->id.manufacturer_id != I2C_DEVICE_ID_NONE) { + struct i2c_device_identity id; + + ret = i2c_get_device_id(client, &id); + if (ret && ret != -EOPNOTSUPP) + return ret; + + if (!ret && + (id.manufacturer_id != data->chip->id.manufacturer_id || + id.part_id != data->chip->id.part_id)) { + dev_warn(dev, "unexpected device id %03x-%03x-%x\n", + id.manufacturer_id, id.part_id, + id.die_revision); + return -ENODEV; + } + } + + data->idle_state = MUX_IDLE_AS_IS; + if (device_property_read_u32(dev, "idle-state", &data->idle_state)) { + if (device_property_read_bool(dev, "i2c-mux-idle-disconnect")) + data->idle_state = MUX_IDLE_DISCONNECT; + } + + /* + * Write the mux register at addr to verify + * that the mux is in fact present. This also + * initializes the mux to a channel + * or disconnected state. + */ + ret = pca954x_init(client, data); + if (ret < 0) { + dev_warn(dev, "probe failed\n"); + return -ENODEV; + } + + ret = pca954x_irq_setup(muxc); + if (ret) + goto fail_cleanup; + + /* Now create an adapter for each channel */ + for (num = 0; num < data->chip->nchans; num++) { + ret = i2c_mux_add_adapter(muxc, 0, num, 0); + if (ret) + goto fail_cleanup; + } + + if (data->irq) { + ret = devm_request_threaded_irq(dev, data->client->irq, + NULL, pca954x_irq_handler, + IRQF_ONESHOT | IRQF_SHARED, + "pca954x", data); + if (ret) + goto fail_cleanup; + } + + /* + * The attr probably isn't going to be needed in most cases, + * so don't fail completely on error. + */ + device_create_file(dev, &dev_attr_idle_state); + + dev_info(dev, "registered %d multiplexed busses for I2C %s %s\n", + num, data->chip->muxtype == pca954x_ismux + ? "mux" : "switch", client->name); + + return 0; + +fail_cleanup: + pca954x_cleanup(muxc); + return ret; +} + +static int pca954x_remove(struct i2c_client *client) +{ + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + + device_remove_file(&client->dev, &dev_attr_idle_state); + + pca954x_cleanup(muxc); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pca954x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + struct pca954x *data = i2c_mux_priv(muxc); + int ret; + + ret = pca954x_init(client, data); + if (ret < 0) + dev_err(&client->dev, "failed to verify mux presence\n"); + + return ret; +} +#endif + +static SIMPLE_DEV_PM_OPS(pca954x_pm, NULL, pca954x_resume); + +static struct i2c_driver pca954x_driver = { + .driver = { + .name = "pca954x", + .pm = &pca954x_pm, + .of_match_table = pca954x_of_match, + }, + .probe = pca954x_probe, + .remove = pca954x_remove, + .id_table = pca954x_id, +}; + +module_i2c_driver(pca954x_driver); + +MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); +MODULE_DESCRIPTION("PCA954x I2C mux/switch driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/muxes/i2c-mux-pinctrl.c b/drivers/i2c/muxes/i2c-mux-pinctrl.c new file mode 100644 index 000000000..fc991cf00 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-pinctrl.c @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C multiplexer using pinctrl API + * + * Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved. + */ + +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/module.h> +#include <linux/pinctrl/consumer.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/of.h> +#include "../../pinctrl/core.h" + +struct i2c_mux_pinctrl { + struct pinctrl *pinctrl; + struct pinctrl_state *states[]; +}; + +static int i2c_mux_pinctrl_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct i2c_mux_pinctrl *mux = i2c_mux_priv(muxc); + + return pinctrl_select_state(mux->pinctrl, mux->states[chan]); +} + +static int i2c_mux_pinctrl_deselect(struct i2c_mux_core *muxc, u32 chan) +{ + return i2c_mux_pinctrl_select(muxc, muxc->num_adapters); +} + +static struct i2c_adapter *i2c_mux_pinctrl_root_adapter( + struct pinctrl_state *state) +{ + struct i2c_adapter *root = NULL; + struct pinctrl_setting *setting; + struct i2c_adapter *pin_root; + + list_for_each_entry(setting, &state->settings, node) { + pin_root = i2c_root_adapter(setting->pctldev->dev); + if (!pin_root) + return NULL; + if (!root) + root = pin_root; + else if (root != pin_root) + return NULL; + } + + return root; +} + +static struct i2c_adapter *i2c_mux_pinctrl_parent_adapter(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct device_node *parent_np; + struct i2c_adapter *parent; + + parent_np = of_parse_phandle(np, "i2c-parent", 0); + if (!parent_np) { + dev_err(dev, "Cannot parse i2c-parent\n"); + return ERR_PTR(-ENODEV); + } + parent = of_get_i2c_adapter_by_node(parent_np); + of_node_put(parent_np); + if (!parent) + return ERR_PTR(-EPROBE_DEFER); + + return parent; +} + +static int i2c_mux_pinctrl_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct i2c_mux_core *muxc; + struct i2c_mux_pinctrl *mux; + struct i2c_adapter *parent; + struct i2c_adapter *root; + int num_names, i, ret; + const char *name; + + num_names = of_property_count_strings(np, "pinctrl-names"); + if (num_names < 0) { + dev_err(dev, "Cannot parse pinctrl-names: %d\n", + num_names); + return num_names; + } + + parent = i2c_mux_pinctrl_parent_adapter(dev); + if (IS_ERR(parent)) + return PTR_ERR(parent); + + muxc = i2c_mux_alloc(parent, dev, num_names, + struct_size(mux, states, num_names), + 0, i2c_mux_pinctrl_select, NULL); + if (!muxc) { + ret = -ENOMEM; + goto err_put_parent; + } + mux = i2c_mux_priv(muxc); + + platform_set_drvdata(pdev, muxc); + + mux->pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(mux->pinctrl)) { + ret = PTR_ERR(mux->pinctrl); + dev_err(dev, "Cannot get pinctrl: %d\n", ret); + goto err_put_parent; + } + + for (i = 0; i < num_names; i++) { + ret = of_property_read_string_index(np, "pinctrl-names", i, + &name); + if (ret < 0) { + dev_err(dev, "Cannot parse pinctrl-names: %d\n", ret); + goto err_put_parent; + } + + mux->states[i] = pinctrl_lookup_state(mux->pinctrl, name); + if (IS_ERR(mux->states[i])) { + ret = PTR_ERR(mux->states[i]); + dev_err(dev, "Cannot look up pinctrl state %s: %d\n", + name, ret); + goto err_put_parent; + } + + if (strcmp(name, "idle")) + continue; + + if (i != num_names - 1) { + dev_err(dev, "idle state must be last\n"); + ret = -EINVAL; + goto err_put_parent; + } + muxc->deselect = i2c_mux_pinctrl_deselect; + } + + root = i2c_root_adapter(&muxc->parent->dev); + + muxc->mux_locked = true; + for (i = 0; i < num_names; i++) { + if (root != i2c_mux_pinctrl_root_adapter(mux->states[i])) { + muxc->mux_locked = false; + break; + } + } + if (muxc->mux_locked) + dev_info(dev, "mux-locked i2c mux\n"); + + /* Do not add any adapter for the idle state (if it's there at all). */ + for (i = 0; i < num_names - !!muxc->deselect; i++) { + ret = i2c_mux_add_adapter(muxc, 0, i, 0); + if (ret) + goto err_del_adapter; + } + + return 0; + +err_del_adapter: + i2c_mux_del_adapters(muxc); +err_put_parent: + i2c_put_adapter(parent); + + return ret; +} + +static int i2c_mux_pinctrl_remove(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); + + return 0; +} + +static const struct of_device_id i2c_mux_pinctrl_of_match[] = { + { .compatible = "i2c-mux-pinctrl", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_mux_pinctrl_of_match); + +static struct platform_driver i2c_mux_pinctrl_driver = { + .driver = { + .name = "i2c-mux-pinctrl", + .of_match_table = of_match_ptr(i2c_mux_pinctrl_of_match), + }, + .probe = i2c_mux_pinctrl_probe, + .remove = i2c_mux_pinctrl_remove, +}; +module_platform_driver(i2c_mux_pinctrl_driver); + +MODULE_DESCRIPTION("pinctrl-based I2C multiplexer driver"); +MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-mux-pinctrl"); diff --git a/drivers/i2c/muxes/i2c-mux-reg.c b/drivers/i2c/muxes/i2c-mux-reg.c new file mode 100644 index 000000000..30a6de169 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-reg.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * I2C multiplexer using a single register + * + * Copyright 2015 Freescale Semiconductor + * York Sun <yorksun@freescale.com> + */ + +#include <linux/i2c.h> +#include <linux/i2c-mux.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_data/i2c-mux-reg.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct regmux { + struct i2c_mux_reg_platform_data data; +}; + +static int i2c_mux_reg_set(const struct regmux *mux, unsigned int chan_id) +{ + if (!mux->data.reg) + return -EINVAL; + + /* + * Write to the register, followed by a read to ensure the write is + * completed on a "posted" bus, for example PCI or write buffers. + * The endianness of reading doesn't matter and the return data + * is not used. + */ + switch (mux->data.reg_size) { + case 4: + if (mux->data.little_endian) + iowrite32(chan_id, mux->data.reg); + else + iowrite32be(chan_id, mux->data.reg); + if (!mux->data.write_only) + ioread32(mux->data.reg); + break; + case 2: + if (mux->data.little_endian) + iowrite16(chan_id, mux->data.reg); + else + iowrite16be(chan_id, mux->data.reg); + if (!mux->data.write_only) + ioread16(mux->data.reg); + break; + case 1: + iowrite8(chan_id, mux->data.reg); + if (!mux->data.write_only) + ioread8(mux->data.reg); + break; + } + + return 0; +} + +static int i2c_mux_reg_select(struct i2c_mux_core *muxc, u32 chan) +{ + struct regmux *mux = i2c_mux_priv(muxc); + + return i2c_mux_reg_set(mux, chan); +} + +static int i2c_mux_reg_deselect(struct i2c_mux_core *muxc, u32 chan) +{ + struct regmux *mux = i2c_mux_priv(muxc); + + if (mux->data.idle_in_use) + return i2c_mux_reg_set(mux, mux->data.idle); + + return 0; +} + +#ifdef CONFIG_OF +static int i2c_mux_reg_probe_dt(struct regmux *mux, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct device_node *adapter_np, *child; + struct i2c_adapter *adapter; + struct resource res; + unsigned *values; + int i = 0; + + if (!np) + return -ENODEV; + + adapter_np = of_parse_phandle(np, "i2c-parent", 0); + if (!adapter_np) { + dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); + return -ENODEV; + } + adapter = of_find_i2c_adapter_by_node(adapter_np); + of_node_put(adapter_np); + if (!adapter) + return -EPROBE_DEFER; + + mux->data.parent = i2c_adapter_id(adapter); + put_device(&adapter->dev); + + mux->data.n_values = of_get_child_count(np); + if (of_property_read_bool(np, "little-endian")) { + mux->data.little_endian = true; + } else if (of_property_read_bool(np, "big-endian")) { + mux->data.little_endian = false; + } else { +#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : \ + defined(__LITTLE_ENDIAN) + mux->data.little_endian = true; +#elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : \ + defined(__BIG_ENDIAN) + mux->data.little_endian = false; +#else +#error Endianness not defined? +#endif + } + mux->data.write_only = of_property_read_bool(np, "write-only"); + + values = devm_kcalloc(&pdev->dev, + mux->data.n_values, sizeof(*mux->data.values), + GFP_KERNEL); + if (!values) + return -ENOMEM; + + for_each_child_of_node(np, child) { + of_property_read_u32(child, "reg", values + i); + i++; + } + mux->data.values = values; + + if (!of_property_read_u32(np, "idle-state", &mux->data.idle)) + mux->data.idle_in_use = true; + + /* map address from "reg" if exists */ + if (of_address_to_resource(np, 0, &res) == 0) { + mux->data.reg_size = resource_size(&res); + mux->data.reg = devm_ioremap_resource(&pdev->dev, &res); + if (IS_ERR(mux->data.reg)) + return PTR_ERR(mux->data.reg); + } + + return 0; +} +#else +static int i2c_mux_reg_probe_dt(struct regmux *mux, + struct platform_device *pdev) +{ + return 0; +} +#endif + +static int i2c_mux_reg_probe(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc; + struct regmux *mux; + struct i2c_adapter *parent; + struct resource *res; + unsigned int class; + int i, ret, nr; + + mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); + if (!mux) + return -ENOMEM; + + if (dev_get_platdata(&pdev->dev)) { + memcpy(&mux->data, dev_get_platdata(&pdev->dev), + sizeof(mux->data)); + } else { + ret = i2c_mux_reg_probe_dt(mux, pdev); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, + "Error parsing device tree"); + } + + parent = i2c_get_adapter(mux->data.parent); + if (!parent) + return -EPROBE_DEFER; + + if (!mux->data.reg) { + dev_info(&pdev->dev, + "Register not set, using platform resource\n"); + mux->data.reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(mux->data.reg)) { + ret = PTR_ERR(mux->data.reg); + goto err_put_parent; + } + mux->data.reg_size = resource_size(res); + } + + if (mux->data.reg_size != 4 && mux->data.reg_size != 2 && + mux->data.reg_size != 1) { + dev_err(&pdev->dev, "Invalid register size\n"); + ret = -EINVAL; + goto err_put_parent; + } + + muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, 0, 0, + i2c_mux_reg_select, NULL); + if (!muxc) { + ret = -ENOMEM; + goto err_put_parent; + } + muxc->priv = mux; + + platform_set_drvdata(pdev, muxc); + + if (mux->data.idle_in_use) + muxc->deselect = i2c_mux_reg_deselect; + + for (i = 0; i < mux->data.n_values; i++) { + nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; + class = mux->data.classes ? mux->data.classes[i] : 0; + + ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class); + if (ret) + goto err_del_mux_adapters; + } + + dev_dbg(&pdev->dev, "%d port mux on %s adapter\n", + mux->data.n_values, muxc->parent->name); + + return 0; + +err_del_mux_adapters: + i2c_mux_del_adapters(muxc); +err_put_parent: + i2c_put_adapter(parent); + + return ret; +} + +static int i2c_mux_reg_remove(struct platform_device *pdev) +{ + struct i2c_mux_core *muxc = platform_get_drvdata(pdev); + + i2c_mux_del_adapters(muxc); + i2c_put_adapter(muxc->parent); + + return 0; +} + +static const struct of_device_id i2c_mux_reg_of_match[] = { + { .compatible = "i2c-mux-reg", }, + {}, +}; +MODULE_DEVICE_TABLE(of, i2c_mux_reg_of_match); + +static struct platform_driver i2c_mux_reg_driver = { + .probe = i2c_mux_reg_probe, + .remove = i2c_mux_reg_remove, + .driver = { + .name = "i2c-mux-reg", + .of_match_table = of_match_ptr(i2c_mux_reg_of_match), + }, +}; + +module_platform_driver(i2c_mux_reg_driver); + +MODULE_DESCRIPTION("Register-based I2C multiplexer driver"); +MODULE_AUTHOR("York Sun <yorksun@freescale.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:i2c-mux-reg"); |