diff mbox series

[5/7] mfd: ds90ux9xx: add I2C bridge/alias and link connection driver

Message ID 20181008211205.2900-6-vz@mleia.com (mailing list archive)
State New, archived
Headers show
Series mfd/pinctrl: add initial support of TI DS90Ux9xx ICs | expand

Commit Message

Vladimir Zapolskiy Oct. 8, 2018, 9:12 p.m. UTC
From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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

Comments

Lee Jones Oct. 12, 2018, 6:04 a.m. UTC | #1
On Tue, 09 Oct 2018, Vladimir Zapolskiy wrote:

> From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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

Shouldn't this live in drivers/i2c?
Vladimir Zapolskiy Oct. 12, 2018, 7:32 a.m. UTC | #2
Hi Lee,

On 10/12/2018 09:04 AM, Lee Jones wrote:
> On Tue, 09 Oct 2018, Vladimir Zapolskiy wrote:
> 
>> From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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
> 
> Shouldn't this live in drivers/i2c?

no, the driver is not for an I2C controller of any kind, and the driver does
not register itself in the I2C subsystem by calling i2c_add_adapter() or
i2c_add_numbered_adapter() or i2c_mux_add_adapter() etc, this topic was
discussed with Wolfram also.

Formally the driver converts the managed IC into a multi-address I2C
slave device, I understand that it does not sound like a well suited driver
for MFD, but ds90ux9xx-core.c and ds90ux9xx-i2c-bridge.c drivers are quite
tightly coupled.

--
Best wishes,
Vladimir
Lee Jones Oct. 12, 2018, 9:03 a.m. UTC | #3
On Fri, 12 Oct 2018, Vladimir Zapolskiy wrote:
> On 10/12/2018 09:04 AM, Lee Jones wrote:
> > On Tue, 09 Oct 2018, Vladimir Zapolskiy wrote:
> > 
> >> From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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
> > 
> > Shouldn't this live in drivers/i2c?
> 
> no, the driver is not for an I2C controller of any kind, and the driver does
> not register itself in the I2C subsystem by calling i2c_add_adapter() or
> i2c_add_numbered_adapter() or i2c_mux_add_adapter() etc, this topic was
> discussed with Wolfram also.
> 
> Formally the driver converts the managed IC into a multi-address I2C
> slave device, I understand that it does not sound like a well suited driver
> for MFD, but ds90ux9xx-core.c and ds90ux9xx-i2c-bridge.c drivers are quite
> tightly coupled.

Using MFD as the default dumping ground for anything which does more
than one thing has also been discussed before. :)

You need to figure out what function this device provides and
re-locate it into the subsystem which it best fits.
Laurent Pinchart Oct. 12, 2018, 1:12 p.m. UTC | #4
Hi Vladimir,

(CC'ing Wolfram)

On Friday, 12 October 2018 10:32:32 EEST Vladimir Zapolskiy wrote:
> On 10/12/2018 09:04 AM, Lee Jones wrote:
> > On Tue, 09 Oct 2018, Vladimir Zapolskiy wrote:
> >> From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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
> > 
> > Shouldn't this live in drivers/i2c?
> 
> no, the driver is not for an I2C controller of any kind, and the driver does
> not register itself in the I2C subsystem by calling i2c_add_adapter() or
> i2c_add_numbered_adapter() or i2c_mux_add_adapter() etc, this topic was
> discussed with Wolfram also.

(Who is now on CC)

> Formally the driver converts the managed IC into a multi-address I2C
> slave device, I understand that it does not sound like a well suited driver
> for MFD, but ds90ux9xx-core.c and ds90ux9xx-i2c-bridge.c drivers are quite
> tightly coupled.

As mentioned in other e-mails in this thread I don't think this should be 
split out to a separate driver, I would move the functionality to the 
ds90ux9xx driver. You may want to register an I2C mux, but as you have a 
single port, that could be overkill. I haven't studied in details how to best 
support this chip using the existing I2C subsystems APIs (which we may want to 
extend if it needed), but I believe that (in your use cases) the deserializer 
should be a child of the serializer, and modeled as an I2C device.
Vladimir Zapolskiy Oct. 12, 2018, 2:36 p.m. UTC | #5
Hi Laurent

On 10/12/2018 04:12 PM, Laurent Pinchart wrote:
> Hi Vladimir,
> 
> (CC'ing Wolfram)
> 
> On Friday, 12 October 2018 10:32:32 EEST Vladimir Zapolskiy wrote:
>> On 10/12/2018 09:04 AM, Lee Jones wrote:
>>> On Tue, 09 Oct 2018, Vladimir Zapolskiy wrote:
>>>> From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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
>>>
>>> Shouldn't this live in drivers/i2c?
>>
>> no, the driver is not for an I2C controller of any kind, and the driver does
>> not register itself in the I2C subsystem by calling i2c_add_adapter() or
>> i2c_add_numbered_adapter() or i2c_mux_add_adapter() etc, this topic was
>> discussed with Wolfram also.
> 
> (Who is now on CC)
> 

Wolfram has copies of the drivers and discussion right from the beginning,
hopefully he won't get two copies ;)

>> Formally the driver converts the managed IC into a multi-address I2C
>> slave device, I understand that it does not sound like a well suited driver
>> for MFD, but ds90ux9xx-core.c and ds90ux9xx-i2c-bridge.c drivers are quite
>> tightly coupled.
> 
> As mentioned in other e-mails in this thread I don't think this should be 
> split out to a separate driver,> I would move the functionality to the 
> ds90ux9xx driver.

The proposal may have the grounds, but the I2C bridging functionality of ICs
is quite detached from all other ones, thus it found its place in the cell
driver per se.

> You may want to register an I2C mux, but as you have a single port, that
> could be overkill. I haven't studied in details how to best support this
> chip using the existing I2C subsystems APIs (which we may want to extend
> if it needed), but I believe that (in your use cases) the deserializer
> should be a child of the serializer, and modeled as an I2C device.
> 

Formally in OF terms to define a link between devices by a phandle should
be sufficient, panels are not the children of LVDS controllers under OF graph
constraints in DT representation, the panels become secondary in runtime
only, I'd like to reuse the concept. Also it adds a better sense of symmetry
of deserializer <-> serializer connections relatively to a SoC/data source.

--
Best wishes,
Vladimir
Laurent Pinchart Oct. 16, 2018, 2:06 p.m. UTC | #6
Hi Vladimir,

On Friday, 12 October 2018 17:36:39 EEST Vladimir Zapolskiy wrote:
> On 10/12/2018 04:12 PM, Laurent Pinchart wrote:
> > On Friday, 12 October 2018 10:32:32 EEST Vladimir Zapolskiy wrote:
> >> On 10/12/2018 09:04 AM, Lee Jones wrote:
> >>> On Tue, 09 Oct 2018, Vladimir Zapolskiy wrote:
> >>>> From: Vladimir Zapolskiy <vladimir_zapolskiy@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_zapolskiy@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
> >>> 
> >>> Shouldn't this live in drivers/i2c?
> >> 
> >> no, the driver is not for an I2C controller of any kind, and the driver
> >> does not register itself in the I2C subsystem by calling
> >> i2c_add_adapter() or i2c_add_numbered_adapter() or i2c_mux_add_adapter()
> >> etc, this topic was discussed with Wolfram also.
> > 
> > (Who is now on CC)
> 
> Wolfram has copies of the drivers and discussion right from the beginning,
> hopefully he won't get two copies ;)
> 
> >> Formally the driver converts the managed IC into a multi-address I2C
> >> slave device, I understand that it does not sound like a well suited
> >> driver for MFD, but ds90ux9xx-core.c and ds90ux9xx-i2c-bridge.c drivers
> >> are quite tightly coupled.
> > 
> > As mentioned in other e-mails in this thread I don't think this should be
> > split out to a separate driver,> I would move the functionality to the
> > ds90ux9xx driver.
> 
> The proposal may have the grounds, but the I2C bridging functionality of ICs
> is quite detached from all other ones, thus it found its place in the cell
> driver per se.
> 
> > You may want to register an I2C mux, but as you have a single port, that
> > could be overkill. I haven't studied in details how to best support this
> > chip using the existing I2C subsystems APIs (which we may want to extend
> > if it needed), but I believe that (in your use cases) the deserializer
> > should be a child of the serializer, and modeled as an I2C device.
> 
> Formally in OF terms to define a link between devices by a phandle should
> be sufficient, panels are not the children of LVDS controllers under OF
> graph constraints in DT representation, the panels become secondary in
> runtime only, I'd like to reuse the concept. Also it adds a better sense of
> symmetry of deserializer <-> serializer connections relatively to a
> SoC/data source.

As I explained, DT models control buses through parent/child relationships. 
That's why I2C slaves are children of their I2C master. OF graph adds a second 
topology to describe data connections, which are orthogonal to the control bus 
relationship. In your case the device at the remote side of the link is 
controlled over the link, and that control flow goes from the SoC to the 
device on the local side of the link. That's why the remote device should be a 
child of the local device.
diff mbox series

Patch

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_zapolskiy@mentor.com>");
+MODULE_AUTHOR("Balasubramani Vivekanandan <balasubramani_vivekanandan@mentor.com>");
+MODULE_DESCRIPTION("TI DS90Ux9xx I2C bridge/alias controller driver");
+MODULE_LICENSE("GPL");