18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * I2C multiplexer using a single register 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2015 Freescale Semiconductor 68c2ecf20Sopenharmony_ci * York Sun <yorksun@freescale.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/i2c.h> 108c2ecf20Sopenharmony_ci#include <linux/i2c-mux.h> 118c2ecf20Sopenharmony_ci#include <linux/init.h> 128c2ecf20Sopenharmony_ci#include <linux/io.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/of_address.h> 158c2ecf20Sopenharmony_ci#include <linux/platform_data/i2c-mux-reg.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/slab.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_cistruct regmux { 208c2ecf20Sopenharmony_ci struct i2c_mux_reg_platform_data data; 218c2ecf20Sopenharmony_ci}; 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_cistatic int i2c_mux_reg_set(const struct regmux *mux, unsigned int chan_id) 248c2ecf20Sopenharmony_ci{ 258c2ecf20Sopenharmony_ci if (!mux->data.reg) 268c2ecf20Sopenharmony_ci return -EINVAL; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci /* 298c2ecf20Sopenharmony_ci * Write to the register, followed by a read to ensure the write is 308c2ecf20Sopenharmony_ci * completed on a "posted" bus, for example PCI or write buffers. 318c2ecf20Sopenharmony_ci * The endianness of reading doesn't matter and the return data 328c2ecf20Sopenharmony_ci * is not used. 338c2ecf20Sopenharmony_ci */ 348c2ecf20Sopenharmony_ci switch (mux->data.reg_size) { 358c2ecf20Sopenharmony_ci case 4: 368c2ecf20Sopenharmony_ci if (mux->data.little_endian) 378c2ecf20Sopenharmony_ci iowrite32(chan_id, mux->data.reg); 388c2ecf20Sopenharmony_ci else 398c2ecf20Sopenharmony_ci iowrite32be(chan_id, mux->data.reg); 408c2ecf20Sopenharmony_ci if (!mux->data.write_only) 418c2ecf20Sopenharmony_ci ioread32(mux->data.reg); 428c2ecf20Sopenharmony_ci break; 438c2ecf20Sopenharmony_ci case 2: 448c2ecf20Sopenharmony_ci if (mux->data.little_endian) 458c2ecf20Sopenharmony_ci iowrite16(chan_id, mux->data.reg); 468c2ecf20Sopenharmony_ci else 478c2ecf20Sopenharmony_ci iowrite16be(chan_id, mux->data.reg); 488c2ecf20Sopenharmony_ci if (!mux->data.write_only) 498c2ecf20Sopenharmony_ci ioread16(mux->data.reg); 508c2ecf20Sopenharmony_ci break; 518c2ecf20Sopenharmony_ci case 1: 528c2ecf20Sopenharmony_ci iowrite8(chan_id, mux->data.reg); 538c2ecf20Sopenharmony_ci if (!mux->data.write_only) 548c2ecf20Sopenharmony_ci ioread8(mux->data.reg); 558c2ecf20Sopenharmony_ci break; 568c2ecf20Sopenharmony_ci } 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci return 0; 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int i2c_mux_reg_select(struct i2c_mux_core *muxc, u32 chan) 628c2ecf20Sopenharmony_ci{ 638c2ecf20Sopenharmony_ci struct regmux *mux = i2c_mux_priv(muxc); 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci return i2c_mux_reg_set(mux, chan); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int i2c_mux_reg_deselect(struct i2c_mux_core *muxc, u32 chan) 698c2ecf20Sopenharmony_ci{ 708c2ecf20Sopenharmony_ci struct regmux *mux = i2c_mux_priv(muxc); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci if (mux->data.idle_in_use) 738c2ecf20Sopenharmony_ci return i2c_mux_reg_set(mux, mux->data.idle); 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci return 0; 768c2ecf20Sopenharmony_ci} 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci#ifdef CONFIG_OF 798c2ecf20Sopenharmony_cistatic int i2c_mux_reg_probe_dt(struct regmux *mux, 808c2ecf20Sopenharmony_ci struct platform_device *pdev) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 838c2ecf20Sopenharmony_ci struct device_node *adapter_np, *child; 848c2ecf20Sopenharmony_ci struct i2c_adapter *adapter; 858c2ecf20Sopenharmony_ci struct resource res; 868c2ecf20Sopenharmony_ci unsigned *values; 878c2ecf20Sopenharmony_ci int i = 0; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (!np) 908c2ecf20Sopenharmony_ci return -ENODEV; 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci adapter_np = of_parse_phandle(np, "i2c-parent", 0); 938c2ecf20Sopenharmony_ci if (!adapter_np) { 948c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Cannot parse i2c-parent\n"); 958c2ecf20Sopenharmony_ci return -ENODEV; 968c2ecf20Sopenharmony_ci } 978c2ecf20Sopenharmony_ci adapter = of_find_i2c_adapter_by_node(adapter_np); 988c2ecf20Sopenharmony_ci of_node_put(adapter_np); 998c2ecf20Sopenharmony_ci if (!adapter) 1008c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci mux->data.parent = i2c_adapter_id(adapter); 1038c2ecf20Sopenharmony_ci put_device(&adapter->dev); 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci mux->data.n_values = of_get_child_count(np); 1068c2ecf20Sopenharmony_ci if (of_property_read_bool(np, "little-endian")) { 1078c2ecf20Sopenharmony_ci mux->data.little_endian = true; 1088c2ecf20Sopenharmony_ci } else if (of_property_read_bool(np, "big-endian")) { 1098c2ecf20Sopenharmony_ci mux->data.little_endian = false; 1108c2ecf20Sopenharmony_ci } else { 1118c2ecf20Sopenharmony_ci#if defined(__BYTE_ORDER) ? __BYTE_ORDER == __LITTLE_ENDIAN : \ 1128c2ecf20Sopenharmony_ci defined(__LITTLE_ENDIAN) 1138c2ecf20Sopenharmony_ci mux->data.little_endian = true; 1148c2ecf20Sopenharmony_ci#elif defined(__BYTE_ORDER) ? __BYTE_ORDER == __BIG_ENDIAN : \ 1158c2ecf20Sopenharmony_ci defined(__BIG_ENDIAN) 1168c2ecf20Sopenharmony_ci mux->data.little_endian = false; 1178c2ecf20Sopenharmony_ci#else 1188c2ecf20Sopenharmony_ci#error Endianness not defined? 1198c2ecf20Sopenharmony_ci#endif 1208c2ecf20Sopenharmony_ci } 1218c2ecf20Sopenharmony_ci mux->data.write_only = of_property_read_bool(np, "write-only"); 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci values = devm_kcalloc(&pdev->dev, 1248c2ecf20Sopenharmony_ci mux->data.n_values, sizeof(*mux->data.values), 1258c2ecf20Sopenharmony_ci GFP_KERNEL); 1268c2ecf20Sopenharmony_ci if (!values) 1278c2ecf20Sopenharmony_ci return -ENOMEM; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci for_each_child_of_node(np, child) { 1308c2ecf20Sopenharmony_ci of_property_read_u32(child, "reg", values + i); 1318c2ecf20Sopenharmony_ci i++; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci mux->data.values = values; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (!of_property_read_u32(np, "idle-state", &mux->data.idle)) 1368c2ecf20Sopenharmony_ci mux->data.idle_in_use = true; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci /* map address from "reg" if exists */ 1398c2ecf20Sopenharmony_ci if (of_address_to_resource(np, 0, &res) == 0) { 1408c2ecf20Sopenharmony_ci mux->data.reg_size = resource_size(&res); 1418c2ecf20Sopenharmony_ci mux->data.reg = devm_ioremap_resource(&pdev->dev, &res); 1428c2ecf20Sopenharmony_ci if (IS_ERR(mux->data.reg)) 1438c2ecf20Sopenharmony_ci return PTR_ERR(mux->data.reg); 1448c2ecf20Sopenharmony_ci } 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return 0; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci#else 1498c2ecf20Sopenharmony_cistatic int i2c_mux_reg_probe_dt(struct regmux *mux, 1508c2ecf20Sopenharmony_ci struct platform_device *pdev) 1518c2ecf20Sopenharmony_ci{ 1528c2ecf20Sopenharmony_ci return 0; 1538c2ecf20Sopenharmony_ci} 1548c2ecf20Sopenharmony_ci#endif 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic int i2c_mux_reg_probe(struct platform_device *pdev) 1578c2ecf20Sopenharmony_ci{ 1588c2ecf20Sopenharmony_ci struct i2c_mux_core *muxc; 1598c2ecf20Sopenharmony_ci struct regmux *mux; 1608c2ecf20Sopenharmony_ci struct i2c_adapter *parent; 1618c2ecf20Sopenharmony_ci struct resource *res; 1628c2ecf20Sopenharmony_ci unsigned int class; 1638c2ecf20Sopenharmony_ci int i, ret, nr; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL); 1668c2ecf20Sopenharmony_ci if (!mux) 1678c2ecf20Sopenharmony_ci return -ENOMEM; 1688c2ecf20Sopenharmony_ci 1698c2ecf20Sopenharmony_ci if (dev_get_platdata(&pdev->dev)) { 1708c2ecf20Sopenharmony_ci memcpy(&mux->data, dev_get_platdata(&pdev->dev), 1718c2ecf20Sopenharmony_ci sizeof(mux->data)); 1728c2ecf20Sopenharmony_ci } else { 1738c2ecf20Sopenharmony_ci ret = i2c_mux_reg_probe_dt(mux, pdev); 1748c2ecf20Sopenharmony_ci if (ret < 0) 1758c2ecf20Sopenharmony_ci return dev_err_probe(&pdev->dev, ret, 1768c2ecf20Sopenharmony_ci "Error parsing device tree"); 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci parent = i2c_get_adapter(mux->data.parent); 1808c2ecf20Sopenharmony_ci if (!parent) 1818c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci if (!mux->data.reg) { 1848c2ecf20Sopenharmony_ci dev_info(&pdev->dev, 1858c2ecf20Sopenharmony_ci "Register not set, using platform resource\n"); 1868c2ecf20Sopenharmony_ci mux->data.reg = devm_platform_get_and_ioremap_resource(pdev, 0, &res); 1878c2ecf20Sopenharmony_ci if (IS_ERR(mux->data.reg)) { 1888c2ecf20Sopenharmony_ci ret = PTR_ERR(mux->data.reg); 1898c2ecf20Sopenharmony_ci goto err_put_parent; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci mux->data.reg_size = resource_size(res); 1928c2ecf20Sopenharmony_ci } 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci if (mux->data.reg_size != 4 && mux->data.reg_size != 2 && 1958c2ecf20Sopenharmony_ci mux->data.reg_size != 1) { 1968c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Invalid register size\n"); 1978c2ecf20Sopenharmony_ci ret = -EINVAL; 1988c2ecf20Sopenharmony_ci goto err_put_parent; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values, 0, 0, 2028c2ecf20Sopenharmony_ci i2c_mux_reg_select, NULL); 2038c2ecf20Sopenharmony_ci if (!muxc) { 2048c2ecf20Sopenharmony_ci ret = -ENOMEM; 2058c2ecf20Sopenharmony_ci goto err_put_parent; 2068c2ecf20Sopenharmony_ci } 2078c2ecf20Sopenharmony_ci muxc->priv = mux; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, muxc); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ci if (mux->data.idle_in_use) 2128c2ecf20Sopenharmony_ci muxc->deselect = i2c_mux_reg_deselect; 2138c2ecf20Sopenharmony_ci 2148c2ecf20Sopenharmony_ci for (i = 0; i < mux->data.n_values; i++) { 2158c2ecf20Sopenharmony_ci nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0; 2168c2ecf20Sopenharmony_ci class = mux->data.classes ? mux->data.classes[i] : 0; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class); 2198c2ecf20Sopenharmony_ci if (ret) 2208c2ecf20Sopenharmony_ci goto err_del_mux_adapters; 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "%d port mux on %s adapter\n", 2248c2ecf20Sopenharmony_ci mux->data.n_values, muxc->parent->name); 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci return 0; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_cierr_del_mux_adapters: 2298c2ecf20Sopenharmony_ci i2c_mux_del_adapters(muxc); 2308c2ecf20Sopenharmony_cierr_put_parent: 2318c2ecf20Sopenharmony_ci i2c_put_adapter(parent); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci return ret; 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cistatic int i2c_mux_reg_remove(struct platform_device *pdev) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci struct i2c_mux_core *muxc = platform_get_drvdata(pdev); 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci i2c_mux_del_adapters(muxc); 2418c2ecf20Sopenharmony_ci i2c_put_adapter(muxc->parent); 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci return 0; 2448c2ecf20Sopenharmony_ci} 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic const struct of_device_id i2c_mux_reg_of_match[] = { 2478c2ecf20Sopenharmony_ci { .compatible = "i2c-mux-reg", }, 2488c2ecf20Sopenharmony_ci {}, 2498c2ecf20Sopenharmony_ci}; 2508c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, i2c_mux_reg_of_match); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic struct platform_driver i2c_mux_reg_driver = { 2538c2ecf20Sopenharmony_ci .probe = i2c_mux_reg_probe, 2548c2ecf20Sopenharmony_ci .remove = i2c_mux_reg_remove, 2558c2ecf20Sopenharmony_ci .driver = { 2568c2ecf20Sopenharmony_ci .name = "i2c-mux-reg", 2578c2ecf20Sopenharmony_ci .of_match_table = of_match_ptr(i2c_mux_reg_of_match), 2588c2ecf20Sopenharmony_ci }, 2598c2ecf20Sopenharmony_ci}; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cimodule_platform_driver(i2c_mux_reg_driver); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Register-based I2C multiplexer driver"); 2648c2ecf20Sopenharmony_ciMODULE_AUTHOR("York Sun <yorksun@freescale.com>"); 2658c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2668c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:i2c-mux-reg"); 267