|  | // SPDX-License-Identifier: GPL-2.0-only | 
|  | /* | 
|  | * I2C multiplexer using GPIO API | 
|  | * | 
|  | * Peter Korsgaard <peter.korsgaard@barco.com> | 
|  | */ | 
|  |  | 
|  | #include <linux/bits.h> | 
|  | #include <linux/delay.h> | 
|  | #include <linux/gpio/consumer.h> | 
|  | #include <linux/gpio/driver.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/i2c-mux.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/overflow.h> | 
|  | #include <linux/platform_data/i2c-mux-gpio.h> | 
|  | #include <linux/platform_device.h> | 
|  | #include <linux/slab.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 int 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); | 
|  |  | 
|  | if (mux->data.settle_time) | 
|  | fsleep(mux->data.settle_time); | 
|  |  | 
|  | 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; | 
|  | } | 
|  |  | 
|  | static int i2c_mux_gpio_probe_fw(struct gpiomux *mux, | 
|  | struct platform_device *pdev) | 
|  | { | 
|  | struct device *dev = &pdev->dev; | 
|  | struct fwnode_handle *fwnode = dev_fwnode(dev); | 
|  | struct device_node *np = dev->of_node; | 
|  | struct device_node *adapter_np; | 
|  | struct i2c_adapter *adapter = NULL; | 
|  | struct fwnode_handle *child; | 
|  | unsigned int *values; | 
|  | int rc, i = 0; | 
|  |  | 
|  | if (is_of_node(fwnode)) { | 
|  | 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); | 
|  |  | 
|  | } else if (is_acpi_node(fwnode)) { | 
|  | /* | 
|  | * In ACPI land the mux should be a direct child of the i2c | 
|  | * bus it muxes. | 
|  | */ | 
|  | acpi_handle dev_handle = ACPI_HANDLE(dev->parent); | 
|  |  | 
|  | adapter = i2c_acpi_find_adapter_by_handle(dev_handle); | 
|  | } | 
|  |  | 
|  | if (!adapter) | 
|  | return -EPROBE_DEFER; | 
|  |  | 
|  | mux->data.parent = i2c_adapter_id(adapter); | 
|  | put_device(&adapter->dev); | 
|  |  | 
|  | mux->data.n_values = device_get_child_node_count(dev); | 
|  | values = devm_kcalloc(dev, | 
|  | mux->data.n_values, sizeof(*mux->data.values), | 
|  | GFP_KERNEL); | 
|  | if (!values) { | 
|  | dev_err(dev, "Cannot allocate values array"); | 
|  | return -ENOMEM; | 
|  | } | 
|  |  | 
|  | device_for_each_child_node(dev, child) { | 
|  | if (is_of_node(child)) { | 
|  | fwnode_property_read_u32(child, "reg", values + i); | 
|  | } else if (is_acpi_node(child)) { | 
|  | rc = acpi_get_local_address(ACPI_HANDLE_FWNODE(child), values + i); | 
|  | if (rc) { | 
|  | fwnode_handle_put(child); | 
|  | return dev_err_probe(dev, rc, "Cannot get address\n"); | 
|  | } | 
|  | } | 
|  |  | 
|  | i++; | 
|  | } | 
|  | mux->data.values = values; | 
|  |  | 
|  | if (device_property_read_u32(dev, "idle-state", &mux->data.idle)) | 
|  | mux->data.idle = I2C_MUX_GPIO_NO_IDLE; | 
|  |  | 
|  | device_property_read_u32(dev, "settle-time-us", &mux->data.settle_time); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | 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 int 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_fw(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, | 
|  | array_size(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 gpio_device *gdev; | 
|  | struct device *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; | 
|  |  | 
|  | gdev = gpiod_to_gpio_device(gpiod); | 
|  | dev = gpio_device_to_device(gdev); | 
|  | muxc->mux_locked = i2c_root_adapter(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; | 
|  |  | 
|  | ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i]); | 
|  | 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 void 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); | 
|  | } | 
|  |  | 
|  | 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"); |