18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * I2C multiplexer using GPIO API 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Peter Korsgaard <peter.korsgaard@barco.com> 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/i2c.h> 98c2ecf20Sopenharmony_ci#include <linux/i2c-mux.h> 108c2ecf20Sopenharmony_ci#include <linux/platform_data/i2c-mux-gpio.h> 118c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/slab.h> 148c2ecf20Sopenharmony_ci#include <linux/bits.h> 158c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h> 168c2ecf20Sopenharmony_ci/* FIXME: stop poking around inside gpiolib */ 178c2ecf20Sopenharmony_ci#include "../../gpio/gpiolib.h" 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct gpiomux { 208c2ecf20Sopenharmony_ci struct i2c_mux_gpio_platform_data data; 218c2ecf20Sopenharmony_ci int ngpios; 228c2ecf20Sopenharmony_ci struct gpio_desc **gpios; 238c2ecf20Sopenharmony_ci}; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_cistatic void i2c_mux_gpio_set(const struct gpiomux *mux, unsigned val) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci DECLARE_BITMAP(values, BITS_PER_TYPE(val)); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci values[0] = val; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci gpiod_set_array_value_cansleep(mux->ngpios, mux->gpios, NULL, values); 328c2ecf20Sopenharmony_ci} 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic int i2c_mux_gpio_select(struct i2c_mux_core *muxc, u32 chan) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci struct gpiomux *mux = i2c_mux_priv(muxc); 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci i2c_mux_gpio_set(mux, chan); 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci return 0; 418c2ecf20Sopenharmony_ci} 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic int i2c_mux_gpio_deselect(struct i2c_mux_core *muxc, u32 chan) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct gpiomux *mux = i2c_mux_priv(muxc); 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci i2c_mux_gpio_set(mux, mux->data.idle); 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci return 0; 508c2ecf20Sopenharmony_ci} 518c2ecf20Sopenharmony_ci 528c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 538c2ecf20Sopenharmony_cistatic int i2c_mux_gpio_probe_dt(struct gpiomux *mux, 548c2ecf20Sopenharmony_ci struct platform_device *pdev) 558c2ecf20Sopenharmony_ci{ 568c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 578c2ecf20Sopenharmony_ci struct device_node *adapter_np, *child; 588c2ecf20Sopenharmony_ci struct i2c_adapter *adapter; 598c2ecf20Sopenharmony_ci unsigned *values; 608c2ecf20Sopenharmony_ci int i = 0; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (!np) 638c2ecf20Sopenharmony_ci return -ENODEV; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci adapter_np = of_parse_phandle(np, "i2c-parent", 0); 668c2ecf20Sopenharmony_ci if (!adapter_np) { 678c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); 688c2ecf20Sopenharmony_ci return -ENODEV; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci adapter = of_find_i2c_adapter_by_node(adapter_np); 718c2ecf20Sopenharmony_ci of_node_put(adapter_np); 728c2ecf20Sopenharmony_ci if (!adapter) 738c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci mux->data.parent = i2c_adapter_id(adapter); 768c2ecf20Sopenharmony_ci put_device(&adapter->dev); 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci mux->data.n_values = of_get_child_count(np); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci values = devm_kcalloc(&pdev->dev, 818c2ecf20Sopenharmony_ci mux->data.n_values, sizeof(*mux->data.values), 828c2ecf20Sopenharmony_ci GFP_KERNEL); 838c2ecf20Sopenharmony_ci if (!values) { 848c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot allocate values array"); 858c2ecf20Sopenharmony_ci return -ENOMEM; 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci for_each_child_of_node(np, child) { 898c2ecf20Sopenharmony_ci of_property_read_u32(child, "reg", values + i); 908c2ecf20Sopenharmony_ci i++; 918c2ecf20Sopenharmony_ci } 928c2ecf20Sopenharmony_ci mux->data.values = values; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci if (of_property_read_u32(np, "idle-state", &mux->data.idle)) 958c2ecf20Sopenharmony_ci mux->data.idle = I2C_MUX_GPIO_NO_IDLE; 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci#else 1008c2ecf20Sopenharmony_cistatic int i2c_mux_gpio_probe_dt(struct gpiomux *mux, 1018c2ecf20Sopenharmony_ci struct platform_device *pdev) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci#endif 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic int i2c_mux_gpio_probe(struct platform_device *pdev) 1088c2ecf20Sopenharmony_ci{ 1098c2ecf20Sopenharmony_ci struct i2c_mux_core *muxc; 1108c2ecf20Sopenharmony_ci struct gpiomux *mux; 1118c2ecf20Sopenharmony_ci struct i2c_adapter *parent; 1128c2ecf20Sopenharmony_ci struct i2c_adapter *root; 1138c2ecf20Sopenharmony_ci unsigned initial_state; 1148c2ecf20Sopenharmony_ci int i, ngpios, ret; 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_ci mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); 1178c2ecf20Sopenharmony_ci if (!mux) 1188c2ecf20Sopenharmony_ci return -ENOMEM; 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci if (!dev_get_platdata(&pdev->dev)) { 1218c2ecf20Sopenharmony_ci ret = i2c_mux_gpio_probe_dt(mux, pdev); 1228c2ecf20Sopenharmony_ci if (ret < 0) 1238c2ecf20Sopenharmony_ci return ret; 1248c2ecf20Sopenharmony_ci } else { 1258c2ecf20Sopenharmony_ci memcpy(&mux->data, dev_get_platdata(&pdev->dev), 1268c2ecf20Sopenharmony_ci sizeof(mux->data)); 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci ngpios = gpiod_count(&pdev->dev, "mux"); 1308c2ecf20Sopenharmony_ci if (ngpios <= 0) { 1318c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "no valid gpios provided\n"); 1328c2ecf20Sopenharmony_ci return ngpios ?: -EINVAL; 1338c2ecf20Sopenharmony_ci } 1348c2ecf20Sopenharmony_ci mux->ngpios = ngpios; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci parent = i2c_get_adapter(mux->data.parent); 1378c2ecf20Sopenharmony_ci if (!parent) 1388c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, 1418c2ecf20Sopenharmony_ci ngpios * sizeof(*mux->gpios), 0, 1428c2ecf20Sopenharmony_ci i2c_mux_gpio_select, NULL); 1438c2ecf20Sopenharmony_ci if (!muxc) { 1448c2ecf20Sopenharmony_ci ret = -ENOMEM; 1458c2ecf20Sopenharmony_ci goto alloc_failed; 1468c2ecf20Sopenharmony_ci } 1478c2ecf20Sopenharmony_ci mux->gpios = muxc->priv; 1488c2ecf20Sopenharmony_ci muxc->priv = mux; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, muxc); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci root = i2c_root_adapter(&parent->dev); 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci muxc->mux_locked = true; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci if (mux->data.idle != I2C_MUX_GPIO_NO_IDLE) { 1578c2ecf20Sopenharmony_ci initial_state = mux->data.idle; 1588c2ecf20Sopenharmony_ci muxc->deselect = i2c_mux_gpio_deselect; 1598c2ecf20Sopenharmony_ci } else { 1608c2ecf20Sopenharmony_ci initial_state = mux->data.values[0]; 1618c2ecf20Sopenharmony_ci } 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci for (i = 0; i < ngpios; i++) { 1648c2ecf20Sopenharmony_ci struct device *gpio_dev; 1658c2ecf20Sopenharmony_ci struct gpio_desc *gpiod; 1668c2ecf20Sopenharmony_ci enum gpiod_flags flag; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (initial_state & BIT(i)) 1698c2ecf20Sopenharmony_ci flag = GPIOD_OUT_HIGH; 1708c2ecf20Sopenharmony_ci else 1718c2ecf20Sopenharmony_ci flag = GPIOD_OUT_LOW; 1728c2ecf20Sopenharmony_ci gpiod = devm_gpiod_get_index(&pdev->dev, "mux", i, flag); 1738c2ecf20Sopenharmony_ci if (IS_ERR(gpiod)) { 1748c2ecf20Sopenharmony_ci ret = PTR_ERR(gpiod); 1758c2ecf20Sopenharmony_ci goto alloc_failed; 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci mux->gpios[i] = gpiod; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci if (!muxc->mux_locked) 1818c2ecf20Sopenharmony_ci continue; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci /* FIXME: find a proper way to access the GPIO device */ 1848c2ecf20Sopenharmony_ci gpio_dev = &gpiod->gdev->dev; 1858c2ecf20Sopenharmony_ci muxc->mux_locked = i2c_root_adapter(gpio_dev) == root; 1868c2ecf20Sopenharmony_ci } 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci if (muxc->mux_locked) 1898c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "mux-locked i2c mux\n"); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci for (i = 0; i < mux->data.n_values; i++) { 1928c2ecf20Sopenharmony_ci u32 nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; 1938c2ecf20Sopenharmony_ci unsigned int class = mux->data.classes ? mux->data.classes[i] : 0; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class); 1968c2ecf20Sopenharmony_ci if (ret) 1978c2ecf20Sopenharmony_ci goto add_adapter_failed; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "%d port mux on %s adapter\n", 2018c2ecf20Sopenharmony_ci mux->data.n_values, parent->name); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci return 0; 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ciadd_adapter_failed: 2068c2ecf20Sopenharmony_ci i2c_mux_del_adapters(muxc); 2078c2ecf20Sopenharmony_cialloc_failed: 2088c2ecf20Sopenharmony_ci i2c_put_adapter(parent); 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci return ret; 2118c2ecf20Sopenharmony_ci} 2128c2ecf20Sopenharmony_ci 2138c2ecf20Sopenharmony_cistatic int i2c_mux_gpio_remove(struct platform_device *pdev) 2148c2ecf20Sopenharmony_ci{ 2158c2ecf20Sopenharmony_ci struct i2c_mux_core *muxc = platform_get_drvdata(pdev); 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci i2c_mux_del_adapters(muxc); 2188c2ecf20Sopenharmony_ci i2c_put_adapter(muxc->parent); 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci return 0; 2218c2ecf20Sopenharmony_ci} 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_cistatic const struct of_device_id i2c_mux_gpio_of_match[] = { 2248c2ecf20Sopenharmony_ci { .compatible = "i2c-mux-gpio", }, 2258c2ecf20Sopenharmony_ci {}, 2268c2ecf20Sopenharmony_ci}; 2278c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, i2c_mux_gpio_of_match); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_cistatic struct platform_driver i2c_mux_gpio_driver = { 2308c2ecf20Sopenharmony_ci .probe = i2c_mux_gpio_probe, 2318c2ecf20Sopenharmony_ci .remove = i2c_mux_gpio_remove, 2328c2ecf20Sopenharmony_ci .driver = { 2338c2ecf20Sopenharmony_ci .name = "i2c-mux-gpio", 2348c2ecf20Sopenharmony_ci .of_match_table = i2c_mux_gpio_of_match, 2358c2ecf20Sopenharmony_ci }, 2368c2ecf20Sopenharmony_ci}; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_cimodule_platform_driver(i2c_mux_gpio_driver); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO-based I2C multiplexer driver"); 2418c2ecf20Sopenharmony_ciMODULE_AUTHOR("Peter Korsgaard <peter.korsgaard@barco.com>"); 2428c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2438c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:i2c-mux-gpio"); 244