From: Vladimir Zapolskiy <vladimir_zapols...@mentor.com>

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 <vladimir_zapols...@mentor.com>
---
 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 <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/kthread.h>
+#include <linux/mfd/ds90ux9xx.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* 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 <vladimir_zapols...@mentor.com>");
+MODULE_AUTHOR("Balasubramani Vivekanandan 
<balasubramani_vivekanan...@mentor.com>");
+MODULE_DESCRIPTION("TI DS90Ux9xx I2C bridge/alias controller driver");
+MODULE_LICENSE("GPL");
-- 
2.17.1

Reply via email to