From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-8.8 required=3.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,SPF_PASS,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id F1910C677F8 for ; Mon, 8 Oct 2018 21:12:19 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 9E212213A2 for ; Mon, 8 Oct 2018 21:12:19 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 9E212213A2 Authentication-Results: mail.kernel.org; dmarc=none (p=none dis=none) header.from=mleia.com Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727166AbeJIEZ5 (ORCPT ); Tue, 9 Oct 2018 00:25:57 -0400 Received: from mleia.com ([178.79.152.223]:35518 "EHLO mail.mleia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726896AbeJIEZ5 (ORCPT ); Tue, 9 Oct 2018 00:25:57 -0400 Received: from mail.mleia.com (localhost [127.0.0.1]) by mail.mleia.com (Postfix) with ESMTP id EA34741E2C4; Mon, 8 Oct 2018 22:12:12 +0100 (BST) From: Vladimir Zapolskiy To: Lee Jones , Linus Walleij , Rob Herring Cc: Marek Vasut , Laurent Pinchart , Wolfram Sang , devicetree@vger.kernel.org, linux-gpio@vger.kernel.org, linux-media@vger.kernel.org, linux-kernel@vger.kernel.org, Vladimir Zapolskiy Subject: [PATCH 5/7] mfd: ds90ux9xx: add I2C bridge/alias and link connection driver Date: Tue, 9 Oct 2018 00:12:03 +0300 Message-Id: <20181008211205.2900-6-vz@mleia.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20181008211205.2900-1-vz@mleia.com> References: <20181008211205.2900-1-vz@mleia.com> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-49551924 X-CRM114-CacheID: sfid-20181008_221212_987446_3B960AD5 X-CRM114-Status: GOOD ( 37.21 ) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org From: Vladimir Zapolskiy The change adds TI DS90Ux9xx I2C bridge/alias subdevice driver and FPD Link connection handling mechanism. Access to I2C devices connected to a remote de-/serializer is done in a transparent way, on established link detection event such devices are registered on an I2C bus, which serves a local de-/serializer IC. The development of the driver was a collaborative work, the contribution done by Balasubramani Vivekanandan includes: * original simplistic implementation of the driver, * support of implicitly specified devices in device tree, * support of multiple FPD links for TI DS90Ux9xx, * other kind of valuable review comments, clean-ups and fixes. Also Steve Longerbeam made the following changes: * clear address maps after linked device removal, * disable pass-through in disconnection, * qualify locked status with non-zero remote address. Signed-off-by: Vladimir Zapolskiy --- drivers/mfd/Kconfig | 8 + drivers/mfd/Makefile | 1 + drivers/mfd/ds90ux9xx-i2c-bridge.c | 764 +++++++++++++++++++++++++++++ 3 files changed, 773 insertions(+) create mode 100644 drivers/mfd/ds90ux9xx-i2c-bridge.c diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index a969fa123f64..d97f652046d9 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -1294,6 +1294,14 @@ config MFD_DS90UX9XX controller and so on are provided by separate drivers and should enabled individually. +config MFD_DS90UX9XX_I2C + tristate "TI DS90Ux9xx I2C bridge/alias driver" + default MFD_DS90UX9XX + depends on MFD_DS90UX9XX + help + Select this option to enable I2C bridge/alias and link connection + handling driver for the TI DS90Ux9xx FPD Link de-/serializer ICs. + config MFD_LP3943 tristate "TI/National Semiconductor LP3943 MFD Driver" depends on I2C diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index cc92bf5394b7..5414d0cc0898 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -225,6 +225,7 @@ obj-$(CONFIG_MFD_DLN2) += dln2.o obj-$(CONFIG_MFD_RT5033) += rt5033.o obj-$(CONFIG_MFD_SKY81452) += sky81452.o obj-$(CONFIG_MFD_DS90UX9XX) += ds90ux9xx-core.o +obj-$(CONFIG_MFD_DS90UX9XX_I2C) += ds90ux9xx-i2c-bridge.o intel-soc-pmic-objs := intel_soc_pmic_core.o intel_soc_pmic_crc.o obj-$(CONFIG_INTEL_SOC_PMIC) += intel-soc-pmic.o diff --git a/drivers/mfd/ds90ux9xx-i2c-bridge.c b/drivers/mfd/ds90ux9xx-i2c-bridge.c new file mode 100644 index 000000000000..f35af0f238c8 --- /dev/null +++ b/drivers/mfd/ds90ux9xx-i2c-bridge.c @@ -0,0 +1,764 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * TI DS90Ux9xx I2C bridge/alias controller driver + * + * Copyright (c) 2017-2018 Mentor Graphics Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Serializer Registers */ +#define SER_REG_REMOTE_ID 0x06 +#define SER_REG_I2C_CTRL 0x17 + +/* Deserializer Registers */ +#define DES_REG_I2C_CTRL 0x05 +#define DES_REG_REMOTE_ID 0x07 + +/* Common Register address */ +#define SER_DES_REG_DEVICE_ID 0x00 +#define DEVICE_ID_OVERRIDE BIT(0) + +#define SER_DES_REG_CONFIG 0x03 +#define SER_CONFIG_I2C_AUTO_ACK BIT(5) +#define CONFIG_I2C_PASS_THROUGH BIT(3) +#define DES_CONFIG_I2C_AUTO_ACK BIT(2) + +#define I2C_CTRL_PASS_ALL BIT(7) + +#define DS90UX9XX_MAX_LINKED_DEVICES 2 +#define DS90UX9XX_MAX_SLAVE_DEVICES 8 + +/* Chosen link connection timings */ +#define CONN_MIN_TIME_MSEC 400U +#define CONN_STEP_TIME_MSEC 50U +#define CONN_MAX_TIME_MSEC 1000U + +struct ds90ux9xx_i2c_data { + const u8 slave_reg[DS90UX9XX_MAX_SLAVE_DEVICES]; + const u8 alias_reg[DS90UX9XX_MAX_SLAVE_DEVICES]; + const unsigned int num_slaves; +}; + +static const struct ds90ux9xx_i2c_data ds90ux925_i2c = { + .slave_reg = { 0x07, }, + .alias_reg = { 0x08, }, + .num_slaves = 1, +}; + +static const struct ds90ux9xx_i2c_data ds90ux926_i2c = { + .slave_reg = { 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, }, + .alias_reg = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, }, + .num_slaves = 8, +}; + +static const struct ds90ux9xx_i2c_data ds90ux927_i2c = { + .slave_reg = { 0x07, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, }, + .alias_reg = { 0x08, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, }, + .num_slaves = 8, +}; + +struct ds90ux9xx_i2c_bridged { + struct device_node *np; + struct i2c_client *i2c; + u8 addr; +}; + +struct ds90ux9xx_i2c_map { + u8 real_addr; + u8 alias_addr; +}; + +struct ds90ux9xx_i2c_linked { + struct ds90ux9xx_i2c_bridged remote; + struct ds90ux9xx_i2c_bridged slave[DS90UX9XX_MAX_SLAVE_DEVICES]; + struct ds90ux9xx_i2c_map map[DS90UX9XX_MAX_SLAVE_DEVICES]; + unsigned int num_slaves; + unsigned int num_maps; + bool is_bridged; +}; + +struct ds90ux9xx_i2c { + struct device *dev; + struct regmap *regmap; + struct i2c_adapter *adapter; + const struct ds90ux9xx_i2c_data *i2c_data; + bool remote; + struct task_struct *conn_monitor; + struct ds90ux9xx_i2c_linked linked[DS90UX9XX_MAX_LINKED_DEVICES]; + unsigned int link; + bool pass_all; +}; + +static int ds90ux9xx_setup_i2c_pass_through(struct ds90ux9xx_i2c *i2c_bridge, + bool enable) +{ + int ret; + + ret = regmap_update_bits(i2c_bridge->regmap, SER_DES_REG_CONFIG, + CONFIG_I2C_PASS_THROUGH, + enable ? CONFIG_I2C_PASS_THROUGH : 0x0); + if (ret) + dev_err(i2c_bridge->dev, "Failed to setup pass through\n"); + + return ret; +} + +static int ds90ux9xx_setup_i2c_pass_all(struct ds90ux9xx_i2c *i2c_bridge, + bool enable) +{ + unsigned int reg; + int ret; + + if (ds90ux9xx_is_serializer(i2c_bridge->dev->parent)) + reg = SER_REG_I2C_CTRL; + else + reg = DES_REG_I2C_CTRL; + + ret = regmap_update_bits(i2c_bridge->regmap, reg, I2C_CTRL_PASS_ALL, + enable ? I2C_CTRL_PASS_ALL : 0x0); + if (ret) + dev_err(i2c_bridge->dev, "Failed to setup pass all mode\n"); + + return ret; +} + +static int ds90ux9xx_setup_auto_ack(struct ds90ux9xx_i2c *i2c_bridge, + bool enable) +{ + unsigned int val; + int ret; + + if (ds90ux9xx_is_serializer(i2c_bridge->dev->parent)) + val = SER_CONFIG_I2C_AUTO_ACK; + else + val = DES_CONFIG_I2C_AUTO_ACK; + + ret = regmap_update_bits(i2c_bridge->regmap, SER_DES_REG_CONFIG, val, + enable ? val : 0x0); + if (ret) + dev_err(i2c_bridge->dev, "Failed to setup auto ack mode\n"); + + return ret; +} + +static int ds90ux9xx_setup_address_mapping(struct ds90ux9xx_i2c *i2c_bridge, + unsigned int i, u8 slave, u8 alias) +{ + const struct ds90ux9xx_i2c_data *i2c_data = i2c_bridge->i2c_data; + int ret; + + if (i >= i2c_data->num_slaves) + return -EINVAL; + + ret = regmap_write(i2c_bridge->regmap, i2c_data->slave_reg[i], + slave << 1); + if (ret) + return ret; + + return regmap_write(i2c_bridge->regmap, i2c_data->alias_reg[i], + alias << 1); +} + +static void ds90ux9xx_clear_address_mappings(struct ds90ux9xx_i2c *i2c_bridge) +{ + unsigned int i; + + for (i = 0; i < i2c_bridge->i2c_data->num_slaves; i++) + ds90ux9xx_setup_address_mapping(i2c_bridge, i, 0, 0); +} + +static int ds90ux9xx_setup_address_mappings(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked) +{ + struct ds90ux9xx_i2c_map *map; + unsigned int i; + int ret; + + /* To avoid address collisions disable the remaining slave/aliases */ + for (i = 0; i < i2c_bridge->i2c_data->num_slaves; i++) { + map = &linked->map[i]; + + if (i < linked->num_maps) + dev_dbg(i2c_bridge->dev, "Mapping remote slave %#x to %#x\n", + map->real_addr, map->alias_addr); + + ret = ds90ux9xx_setup_address_mapping(i2c_bridge, i, + map->real_addr, + map->alias_addr); + if (ret) + return ret; + } + + return 0; +} + +static int ds90ux9xx_get_remote_addr(struct ds90ux9xx_i2c *i2c_bridge, u8 *addr) +{ + unsigned int reg, val; + int ret; + + if (ds90ux9xx_is_serializer(i2c_bridge->dev->parent)) + reg = SER_REG_REMOTE_ID; + else + reg = DES_REG_REMOTE_ID; + + ret = regmap_read(i2c_bridge->regmap, reg, &val); + if (ret) + dev_err(i2c_bridge->dev, "Failed to get remote addr: %d\n", + ret); + else + *addr = val >> 1; + + return ret; +} + +static int ds90ux9xx_set_remote_addr(struct ds90ux9xx_i2c *i2c_bridge, u8 addr) +{ + u8 remote_addr, data[2] = { SER_DES_REG_DEVICE_ID, + (addr << 1) | DEVICE_ID_OVERRIDE }; + struct i2c_msg msg = { + .addr = addr, + .flags = 0x00, + .len = 2, + .buf = data, + }; + int ret; + + ret = ds90ux9xx_get_remote_addr(i2c_bridge, &remote_addr); + if (ret) + return ret; + + if (remote_addr == addr) + return 0; + + ret = ds90ux9xx_setup_address_mapping(i2c_bridge, 0, remote_addr, addr); + if (ret) + return ret; + + ret = i2c_transfer(i2c_bridge->adapter, &msg, 1); + if (ret != 1) + return ret ? ret : -EIO; + + dev_dbg(i2c_bridge->dev, "New address set for remote %#x\n", addr); + + return 0; +} + +static void ds90ux9xx_remove_slave_devices(struct ds90ux9xx_i2c_linked *linked, + bool drop_reference) +{ + struct ds90ux9xx_i2c_bridged *slave; + unsigned int i; + + if (!linked->is_bridged) + return; + + for (i = 0; i < linked->num_slaves; i++) { + slave = &linked->slave[i]; + + if (slave->i2c) { + i2c_unregister_device(slave->i2c); + slave->i2c = NULL; + } + + if (drop_reference && slave->np) { + of_node_put(slave->np); + slave->np = NULL; + } + } +} + +static void ds90ux9xx_remove_linked_device(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked, + bool drop_reference) +{ + struct ds90ux9xx_i2c_bridged *remote = &linked->remote; + + ds90ux9xx_remove_slave_devices(linked, drop_reference); + + if (remote->i2c) { + if (linked->is_bridged) + i2c_unregister_device(remote->i2c); + else + put_device(&remote->i2c->dev); + + remote->i2c = NULL; + } + + if (drop_reference && remote->np) { + of_node_put(remote->np); + remote->np = NULL; + } + + ds90ux9xx_clear_address_mappings(i2c_bridge); +} + +static void ds90ux9xx_remove_linked_devices(struct ds90ux9xx_i2c *i2c_bridge) +{ + struct ds90ux9xx_i2c_linked *linked; + unsigned int i; + + for (i = 0; i < ds90ux9xx_num_fpd_links(i2c_bridge->dev->parent); i++) { + linked = &i2c_bridge->linked[i]; + ds90ux9xx_remove_linked_device(i2c_bridge, linked, true); + } +} + +static void ds90ux9xx_add_bridged_device(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_bridged *bridged) +{ + struct i2c_board_info info = {}; + int ret; + + dev_dbg(i2c_bridge->dev, "Add I2C device '%s'\n", bridged->np->name); + + info.addr = bridged->addr; + info.of_node = bridged->np; + + /* Non-critical, in case of the problem report it and fallback */ + ret = of_modalias_node(bridged->np, info.type, sizeof(info.type)); + if (ret) + dev_err(i2c_bridge->dev, "Cannot get module alias for '%s'\n", + bridged->np->full_name); + + bridged->i2c = i2c_new_device(i2c_bridge->adapter, &info); + if (!bridged->i2c) + dev_err(i2c_bridge->dev, "Cannot add new I2C device\n"); +} + +static int ds90ux9xx_configure_remote_devices(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked) +{ + struct ds90ux9xx_i2c_bridged *remote = &linked->remote, *slave; + unsigned int i; + int ret; + + ret = ds90ux9xx_setup_address_mappings(i2c_bridge, linked); + if (ret) + return ret; + + ds90ux9xx_add_bridged_device(i2c_bridge, remote); + if (!remote->i2c) + return -ENODEV; + + for (i = 0; i < linked->num_slaves; i++) { + slave = &linked->slave[i]; + + ds90ux9xx_add_bridged_device(i2c_bridge, slave); + if (!slave->i2c) { + ds90ux9xx_remove_linked_device(i2c_bridge, + linked, false); + return -ENODEV; + } + } + + return 0; +} + +static void ds90ux9xx_disconnect_remotes(struct ds90ux9xx_i2c *i2c_bridge) +{ + unsigned int link = i2c_bridge->link; + + dev_dbg(i2c_bridge->dev, "Link %d is disconnected\n", link); + + ds90ux9xx_remove_linked_device(i2c_bridge, + &i2c_bridge->linked[link], false); + + ds90ux9xx_setup_i2c_pass_through(i2c_bridge, false); +} + +static int ds90ux9xx_connect_remotes(struct ds90ux9xx_i2c *i2c_bridge, + unsigned int link) +{ + struct ds90ux9xx_i2c_linked *linked = &i2c_bridge->linked[link]; + struct ds90ux9xx_i2c_bridged *remote = &linked->remote; + int ret; + + dev_dbg(i2c_bridge->dev, "Link %d is connected\n", link); + + i2c_bridge->link = link; + + ret = ds90ux9xx_setup_i2c_pass_through(i2c_bridge, linked->is_bridged); + if (ret) + return ret; + + if (!linked->is_bridged) { + remote->i2c = of_find_i2c_device_by_node(remote->np); + return remote->i2c ? 0 : -ENODEV; + } + + ret = ds90ux9xx_set_remote_addr(i2c_bridge, remote->addr); + if (ret) + return ret; + + return ds90ux9xx_configure_remote_devices(i2c_bridge, linked); +} + +static int ds90ux9xx_conn_monitor(void *data) +{ + unsigned int link, sleep_time = CONN_MIN_TIME_MSEC; + struct ds90ux9xx_i2c *i2c_bridge = data; + struct ds90ux9xx_i2c_bridged *remote; + struct ds90ux9xx_i2c_linked *linked; + bool lock; + u8 addr; + int ret; + + while (!kthread_should_stop()) { + ret = ds90ux9xx_get_link_status(i2c_bridge->dev->parent, + &link, &lock); + if (ret) + goto sleep; + + linked = &i2c_bridge->linked[i2c_bridge->link]; + remote = &linked->remote; + + ret = ds90ux9xx_get_remote_addr(i2c_bridge, &addr); + if (ret < 0) + goto sleep; + + lock = lock && (addr != 0); + + if (lock) + sleep_time = CONN_MAX_TIME_MSEC; + else if (remote->i2c) + sleep_time = CONN_MIN_TIME_MSEC; + else + sleep_time = min_t(unsigned int, CONN_MAX_TIME_MSEC, + sleep_time + CONN_STEP_TIME_MSEC); + + if (remote->i2c && lock && i2c_bridge->link == link) { + if (!linked->is_bridged) + goto sleep; + + if (remote->addr == addr) + goto sleep; + } + + if (remote->i2c) + ds90ux9xx_disconnect_remotes(i2c_bridge); + + if (!remote->i2c && lock) { + ret = ds90ux9xx_connect_remotes(i2c_bridge, link); + if (ret < 0) + dev_err(i2c_bridge->dev, + "Can't establish connection\n"); + } +sleep: + msleep(sleep_time); + } + + return 0; +} + +static int ds90ux9xx_parse_address_mappings(struct ds90ux9xx_i2c *i2c_bridge) +{ + const struct ds90ux9xx_i2c_data *i2c_data = i2c_bridge->i2c_data; + struct ds90ux9xx_i2c_linked *remote; + u32 link, real_addr, alias_addr; + unsigned int size, i; + const __be32 *list; + + list = of_get_property(i2c_bridge->dev->of_node, "ti,i2c-bridge-maps", + &size); + if (!list) + return 0; + + if (!size || size % 12) { + dev_err(i2c_bridge->dev, "Failed to get valid alias maps\n"); + return -EINVAL; + } + + for (i = 0; i < size / 12; i++) { + link = be32_to_cpu(*list++); + real_addr = be32_to_cpu(*list++); + alias_addr = be32_to_cpu(*list++); + + if (link >= ds90ux9xx_num_fpd_links(i2c_bridge->dev->parent)) { + dev_info(i2c_bridge->dev, "Invalid link id %d\n", i); + continue; + } + + remote = &i2c_bridge->linked[link]; + if (remote->num_maps >= i2c_data->num_slaves) { + dev_info(i2c_bridge->dev, "Too many aliases\n"); + break; + } + + remote->map[remote->num_maps].real_addr = real_addr; + remote->map[remote->num_maps].alias_addr = alias_addr; + remote->num_maps++; + } + + return 0; +} + +static u8 ds90ux9xx_get_mapped_address(struct ds90ux9xx_i2c_linked *linked, + u8 remote_addr) +{ + unsigned int i; + + for (i = 0; i < linked->num_maps; i++) { + if (linked->map[i].real_addr == remote_addr) + return linked->map[i].alias_addr; + } + + /* Fallback to preset address, remote device may be inaccessible */ + return remote_addr; +} + +static int ds90ux9xx_parse_remote_slaves(struct ds90ux9xx_i2c *i2c_bridge, + struct ds90ux9xx_i2c_linked *linked) +{ + struct ds90ux9xx_i2c_bridged *slave; + struct device_node *child, *np; + u32 addr; + + np = of_get_child_by_name(linked->remote.np, "i2c-bridge"); + if (!np) { + dev_info(i2c_bridge->dev, "I2C bridge device node not found\n"); + return 0; + } + + if (of_get_child_count(np) > DS90UX9XX_MAX_SLAVE_DEVICES) { + dev_err(i2c_bridge->dev, "Too many aliased I2C devices\n"); + of_node_put(np); + return -EINVAL; + } + + for_each_child_of_node(np, child) { + if (of_property_read_u32(child, "reg", &addr)) { + dev_err(i2c_bridge->dev, "No I2C device address '%s'\n", + child->full_name); + /* Try the next one */ + continue; + } + + slave = &linked->slave[linked->num_slaves]; + slave->np = of_node_get(child); + if (i2c_bridge->pass_all) + slave->addr = addr; + else + slave->addr = ds90ux9xx_get_mapped_address(linked, + addr); + + linked->num_slaves++; + } + + of_node_put(np); + + return 0; +} + +static int ds90ux9xx_configure_link(struct ds90ux9xx_i2c *i2c_bridge) +{ + struct device_node *np = i2c_bridge->dev->parent->of_node; + struct i2c_client *client; + int ret; + + /* The link value is updated when established connection is detected */ + i2c_bridge->link = 0; + + client = of_find_i2c_device_by_node(np); + if (!client || !client->adapter) + return -ENODEV; + + i2c_bridge->adapter = client->adapter; + put_device(&client->dev); + + ret = ds90ux9xx_setup_i2c_pass_all(i2c_bridge, i2c_bridge->pass_all); + if (ret) + return ret; + + i2c_bridge->conn_monitor = kthread_run(ds90ux9xx_conn_monitor, + i2c_bridge, + "ds90ux9xx_conn_monitor"); + if (IS_ERR(i2c_bridge->conn_monitor)) + return PTR_ERR(i2c_bridge->conn_monitor); + + return 0; +} + +static int ds90ux9xx_set_link_attributes(struct ds90ux9xx_i2c *i2c_bridge) +{ + struct device_node *np = i2c_bridge->dev->of_node; + struct ds90ux9xx_i2c_linked *linked; + struct of_phandle_args remote; + unsigned int link, num_links, i; + bool auto_ack; + int ret; + + if (of_property_read_bool(np, "ti,i2c-bridge-pass-all")) + i2c_bridge->pass_all = true; + + auto_ack = of_property_read_bool(np, "ti,i2c-bridge-auto-ack"); + ret = ds90ux9xx_setup_auto_ack(i2c_bridge, auto_ack); + if (ret) + return ret; + + ret = ds90ux9xx_parse_address_mappings(i2c_bridge); + if (ret) + return ret; + + num_links = ds90ux9xx_num_fpd_links(i2c_bridge->dev->parent); + + for (i = 0; i < num_links; i++) { + ret = of_parse_phandle_with_fixed_args(np, "ti,i2c-bridges", + 2, i, &remote); + if (ret) { + if (ret == -ENOENT) + break; + goto drop_nodes; + } + + link = remote.args[0]; + if (link >= num_links) { + ret = -EINVAL; + goto drop_nodes; + } + + linked = &i2c_bridge->linked[link]; + if (linked->remote.np) { + ret = -EINVAL; + goto drop_nodes; + } + + linked->remote.np = remote.np; + linked->remote.addr = remote.args[1]; + + /* + * Don't open I2C access over the FPD-link bidirectional channel + * to the remote's slave devices, if the remote is an I2C slave + * attached to a local bus, because the remote's slaves would + * also necessarily have to hang off the same local bus. + * Enabling pass-through in this case will cause I2C collisions + * due to multiple routes to the same device. + */ + if (of_property_read_bool(linked->remote.np, "reg")) { + linked->is_bridged = false; + continue; + } + + linked->is_bridged = true; + + ret = ds90ux9xx_parse_remote_slaves(i2c_bridge, linked); + if (ret) + goto drop_nodes; + } + + ret = ds90ux9xx_configure_link(i2c_bridge); + if (ret) + goto drop_nodes; + + return 0; + +drop_nodes: + ds90ux9xx_remove_linked_devices(i2c_bridge); + + return ret; +} + +static void ds90ux9xx_get_i2c_data(struct ds90ux9xx_i2c *i2c_bridge) +{ + enum ds90ux9xx_device_id id = + ds90ux9xx_get_ic_type(i2c_bridge->dev->parent); + + switch (id) { + case TI_DS90UB925: + case TI_DS90UH925: + i2c_bridge->i2c_data = &ds90ux925_i2c; + break; + case TI_DS90UB927: + case TI_DS90UH927: + i2c_bridge->i2c_data = &ds90ux927_i2c; + break; + case TI_DS90UB926: + case TI_DS90UH926: + case TI_DS90UB928: + case TI_DS90UH928: + case TI_DS90UB940: + case TI_DS90UH940: + i2c_bridge->i2c_data = &ds90ux926_i2c; + break; + default: + dev_err(i2c_bridge->dev, "Not supported hardware: [%d]\n", id); + } +} + +static int ds90ux9xx_i2c_bridge_probe(struct platform_device *pdev) +{ + struct ds90ux9xx_i2c *i2c_bridge; + struct device *dev = &pdev->dev; + + i2c_bridge = devm_kzalloc(dev, sizeof(*i2c_bridge), GFP_KERNEL); + if (!i2c_bridge) + return -ENOMEM; + + i2c_bridge->dev = dev; + i2c_bridge->regmap = dev_get_regmap(dev->parent, NULL); + if (!i2c_bridge->regmap) + return -ENODEV; + + i2c_bridge->i2c_data = of_device_get_match_data(dev); + if (!i2c_bridge->i2c_data) + ds90ux9xx_get_i2c_data(i2c_bridge); + + if (!i2c_bridge->i2c_data) + return -ENODEV; + + platform_set_drvdata(pdev, i2c_bridge); + + if (of_property_read_bool(dev->of_node, "ti,i2c-bridges")) + return ds90ux9xx_set_link_attributes(i2c_bridge); + + i2c_bridge->remote = true; + + return ds90ux9xx_setup_i2c_pass_through(i2c_bridge, false); +} + +static int ds90ux9xx_i2c_bridge_remove(struct platform_device *pdev) +{ + struct ds90ux9xx_i2c *i2c_bridge = platform_get_drvdata(pdev); + + if (i2c_bridge->remote) + return 0; + + kthread_stop(i2c_bridge->conn_monitor); + ds90ux9xx_remove_linked_devices(i2c_bridge); + + return 0; +} + +static const struct of_device_id ds90ux9xx_i2c_dt_ids[] = { + { .compatible = "ti,ds90ux9xx-i2c-bridge", }, + { .compatible = "ti,ds90ux925-i2c-bridge", .data = &ds90ux925_i2c, }, + { .compatible = "ti,ds90ux926-i2c-bridge", .data = &ds90ux926_i2c, }, + { .compatible = "ti,ds90ux927-i2c-bridge", .data = &ds90ux927_i2c, }, + { .compatible = "ti,ds90ux928-i2c-bridge", .data = &ds90ux926_i2c, }, + { .compatible = "ti,ds90ux940-i2c-bridge", .data = &ds90ux926_i2c, }, + { }, +}; +MODULE_DEVICE_TABLE(of, ds90ux9xx_i2c_dt_ids); + +static struct platform_driver ds90ux9xx_i2c_bridge_driver = { + .probe = ds90ux9xx_i2c_bridge_probe, + .remove = ds90ux9xx_i2c_bridge_remove, + .driver = { + .name = "ds90ux9xx-i2c-bridge", + .of_match_table = ds90ux9xx_i2c_dt_ids, + }, +}; +module_platform_driver(ds90ux9xx_i2c_bridge_driver); + +MODULE_AUTHOR("Vladimir Zapolskiy "); +MODULE_AUTHOR("Balasubramani Vivekanandan "); +MODULE_DESCRIPTION("TI DS90Ux9xx I2C bridge/alias controller driver"); +MODULE_LICENSE("GPL"); -- 2.17.1