linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v5 0/8] i2c-atr and FPDLink
@ 2022-12-08 10:39 Tomi Valkeinen
  2022-12-08 10:39 ` [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach Tomi Valkeinen
                   ` (8 more replies)
  0 siblings, 9 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:39 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Hi,

You can find v4 of the series from:

https://lore.kernel.org/all/20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com/

You can find a longer introduction of the series in that version's cover
letter.

There has been a lot of changes to the DT bindings and the i2c-atr code in this
version, but they are all fixes and cleanups, no architectural changes. The
FPDLink drivers have not been changed, except to reflect the changes in the
DT.

I will send a diff between v4 and v5 to give a better idea of the changes.

One thing that was discussed a bit but not handled in this version is the
i2c-pool/i2c-alias topic. I believe we have three options: 1) use fixed
addresses, defined in DT, 2) get the addresses from an i2c-pool, 3) dynamically
reserve the addresses at runtime. The current series uses 2).

 Tomi

Luca Ceresoli (2):
  i2c: core: let adapters be notified of client attach/detach
  i2c: add I2C Address Translator (ATR) support

Tomi Valkeinen (6):
  dt-bindings: media: add bindings for TI DS90UB913
  dt-bindings: media: add bindings for TI DS90UB953
  dt-bindings: media: add bindings for TI DS90UB960
  media: i2c: add DS90UB960 driver
  media: i2c: add DS90UB913 driver
  media: i2c: add DS90UB953 driver

 .../bindings/media/i2c/ti,ds90ub913.yaml      |  121 +
 .../bindings/media/i2c/ti,ds90ub953.yaml      |  112 +
 .../bindings/media/i2c/ti,ds90ub960.yaml      |  358 ++
 Documentation/i2c/index.rst                   |    1 +
 Documentation/i2c/muxes/i2c-atr.rst           |   78 +
 MAINTAINERS                                   |    8 +
 drivers/i2c/Kconfig                           |    9 +
 drivers/i2c/Makefile                          |    1 +
 drivers/i2c/i2c-atr.c                         |  503 ++
 drivers/i2c/i2c-core-base.c                   |   18 +-
 drivers/media/i2c/Kconfig                     |   47 +
 drivers/media/i2c/Makefile                    |    3 +
 drivers/media/i2c/ds90ub913.c                 |  892 ++++
 drivers/media/i2c/ds90ub953.c                 | 1607 +++++++
 drivers/media/i2c/ds90ub960.c                 | 4195 +++++++++++++++++
 include/linux/i2c-atr.h                       |   82 +
 include/linux/i2c.h                           |   16 +
 include/media/i2c/ds90ub9xx.h                 |   16 +
 18 files changed, 8066 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
 create mode 100644 Documentation/i2c/muxes/i2c-atr.rst
 create mode 100644 drivers/i2c/i2c-atr.c
 create mode 100644 drivers/media/i2c/ds90ub913.c
 create mode 100644 drivers/media/i2c/ds90ub953.c
 create mode 100644 drivers/media/i2c/ds90ub960.c
 create mode 100644 include/linux/i2c-atr.h
 create mode 100644 include/media/i2c/ds90ub9xx.h

-- 
2.34.1


^ permalink raw reply	[flat|nested] 57+ messages in thread

* [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
@ 2022-12-08 10:39 ` Tomi Valkeinen
  2022-12-08 12:30   ` Andy Shevchenko
  2022-12-11 16:55   ` Laurent Pinchart
  2022-12-08 10:40 ` [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support Tomi Valkeinen
                   ` (7 subsequent siblings)
  8 siblings, 2 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:39 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Luca Ceresoli, Tomi Valkeinen

From: Luca Ceresoli <luca@lucaceresoli.net>

An adapter might need to know when a new device is about to be
added. This will soon bee needed to implement an "I2C address
translator" (ATR for short), a device that propagates I2C transactions
with a different slave address (an "alias" address). An ATR driver
needs to know when a slave is being added to find a suitable alias and
program the device translation map.

Add an attach/detach callback pair to allow adapter drivers to be
notified of clients being added and removed.

Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 drivers/i2c/i2c-core-base.c | 18 +++++++++++++++++-
 include/linux/i2c.h         | 16 ++++++++++++++++
 2 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index b4edf10e8fd0..c8bc71b1db82 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -966,15 +966,23 @@ i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *inf
 		}
 	}
 
+	if (adap->attach_ops &&
+	    adap->attach_ops->attach_client &&
+	    adap->attach_ops->attach_client(adap, info, client) != 0)
+		goto out_remove_swnode;
+
 	status = device_register(&client->dev);
 	if (status)
-		goto out_remove_swnode;
+		goto out_detach_client;
 
 	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
 		client->name, dev_name(&client->dev));
 
 	return client;
 
+out_detach_client:
+	if (adap->attach_ops && adap->attach_ops->detach_client)
+		adap->attach_ops->detach_client(adap, client);
 out_remove_swnode:
 	device_remove_software_node(&client->dev);
 out_err_put_of_node:
@@ -996,9 +1004,17 @@ EXPORT_SYMBOL_GPL(i2c_new_client_device);
  */
 void i2c_unregister_device(struct i2c_client *client)
 {
+	struct i2c_adapter *adap;
+
 	if (IS_ERR_OR_NULL(client))
 		return;
 
+	adap = client->adapter;
+
+	if (adap->attach_ops &&
+	    adap->attach_ops->detach_client)
+		adap->attach_ops->detach_client(adap, client);
+
 	if (client->dev.of_node) {
 		of_node_clear_flag(client->dev.of_node, OF_POPULATED);
 		of_node_put(client->dev.of_node);
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index f7c49bbdb8a1..9a385b6de388 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -584,6 +584,21 @@ struct i2c_lock_operations {
 	void (*unlock_bus)(struct i2c_adapter *adapter, unsigned int flags);
 };
 
+/**
+ * struct i2c_attach_operations - callbacks to notify client attach/detach
+ * @attach_client: Notify of new client being attached
+ * @detach_client: Notify of new client being detached
+ *
+ * Both ops are optional.
+ */
+struct i2c_attach_operations {
+	int  (*attach_client)(struct i2c_adapter *adapter,
+			      const struct i2c_board_info *info,
+			      const struct i2c_client *client);
+	void (*detach_client)(struct i2c_adapter *adapter,
+			      const struct i2c_client *client);
+};
+
 /**
  * struct i2c_timings - I2C timing information
  * @bus_freq_hz: the bus frequency in Hz
@@ -726,6 +741,7 @@ struct i2c_adapter {
 
 	/* data fields that are valid for all devices	*/
 	const struct i2c_lock_operations *lock_ops;
+	const struct i2c_attach_operations *attach_ops;
 	struct rt_mutex bus_lock;
 	struct rt_mutex mux_lock;
 
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
  2022-12-08 10:39 ` [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-08 12:53   ` Andy Shevchenko
  2022-12-08 10:40 ` [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913 Tomi Valkeinen
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Luca Ceresoli, Tomi Valkeinen

From: Luca Ceresoli <luca@lucaceresoli.net>

An ATR is a device that looks similar to an i2c-mux: it has an I2C
slave "upstream" port and N master "downstream" ports, and forwards
transactions from upstream to the appropriate downstream port. But is
is different in that the forwarded transaction has a different slave
address. The address used on the upstream bus is called the "alias"
and is (potentially) different from the physical slave address of the
downstream chip.

Add a helper file (just like i2c-mux.c for a mux or switch) to allow
implementing ATR features in a device driver. The helper takes care or
adapter creation/destruction and translates addresses at each transaction.

Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 Documentation/i2c/index.rst         |   1 +
 Documentation/i2c/muxes/i2c-atr.rst |  78 +++++
 MAINTAINERS                         |   8 +
 drivers/i2c/Kconfig                 |   9 +
 drivers/i2c/Makefile                |   1 +
 drivers/i2c/i2c-atr.c               | 503 ++++++++++++++++++++++++++++
 include/linux/i2c-atr.h             |  82 +++++
 7 files changed, 682 insertions(+)
 create mode 100644 Documentation/i2c/muxes/i2c-atr.rst
 create mode 100644 drivers/i2c/i2c-atr.c
 create mode 100644 include/linux/i2c-atr.h

diff --git a/Documentation/i2c/index.rst b/Documentation/i2c/index.rst
index 6270f1fd7d4e..aaf33d1315f4 100644
--- a/Documentation/i2c/index.rst
+++ b/Documentation/i2c/index.rst
@@ -16,6 +16,7 @@ Introduction
    instantiating-devices
    busses/index
    i2c-topology
+   muxes/i2c-atr
    muxes/i2c-mux-gpio
    i2c-sysfs
 
diff --git a/Documentation/i2c/muxes/i2c-atr.rst b/Documentation/i2c/muxes/i2c-atr.rst
new file mode 100644
index 000000000000..14597c9ec19b
--- /dev/null
+++ b/Documentation/i2c/muxes/i2c-atr.rst
@@ -0,0 +1,78 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=====================
+Kernel driver i2c-atr
+=====================
+
+Author: Luca Ceresoli <luca@lucaceresoli.net>
+
+Description
+-----------
+
+An I2C Address Translator (ATR) is a device with an I2C slave parent
+("upstream") port and N I2C master child ("downstream") ports, and
+forwards transactions from upstream to the appropriate downstream port
+with a modified slave address. The address used on the parent bus is
+called the "alias" and is (potentially) different from the physical
+slave address of the child bus. Address translation is done by the
+hardware.
+
+An ATR looks similar to an i2c-mux except:
+ - the address on the parent and child busses can be different
+ - there is normally no need to select the child port; the alias used on the
+   parent bus implies it
+
+The ATR functionality can be provided by a chip with many other
+features. This file provides a helper to implement an ATR within your
+driver.
+
+The ATR creates a new I2C "child" adapter on each child bus. Adding
+devices on the child bus ends up in invoking the driver code to select
+an available alias. Maintaining an appropriate pool of available aliases
+and picking one for each new device is up to the driver implementer. The
+ATR maintains an table of currently assigned alias and uses it to modify
+all I2C transactions directed to devices on the child buses.
+
+A typical example follows.
+
+Topology::
+
+                      Slave X @ 0x10
+              .-----.   |
+  .-----.     |     |---+---- B
+  | CPU |--A--| ATR |
+  `-----'     |     |---+---- C
+              `-----'   |
+                      Slave Y @ 0x10
+
+Alias table:
+
+.. table::
+
+   ======   =====
+   Client   Alias
+   ======   =====
+   X        0x20
+   Y        0x30
+   ======   =====
+
+Transaction:
+
+ - Slave X driver sends a transaction (on adapter B), slave address 0x10
+ - ATR driver rewrites messages with address 0x20, forwards to adapter A
+ - Physical I2C transaction on bus A, slave address 0x20
+ - ATR chip propagates transaction on bus B with address translated to 0x10
+ - Slave X chip replies on bus B
+ - ATR chip forwards reply on bus A
+ - ATR driver rewrites messages with address 0x10
+ - Slave X driver gets back the msgs[], with reply and address 0x10
+
+Usage:
+
+ 1. In your driver (typically in the probe function) add an ATR by
+    calling i2c_atr_new() passing your attach/detach callbacks
+ 2. When the attach callback is called pick an appropriate alias,
+    configure it in your chip and return the chosen alias in the
+    alias_id parameter
+ 3. When the detach callback is called, deconfigure the alias from
+    your chip and put it back in the pool for later usage
diff --git a/MAINTAINERS b/MAINTAINERS
index 379945f82a64..4c375e30f951 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9568,6 +9568,14 @@ L:	linux-acpi@vger.kernel.org
 S:	Maintained
 F:	drivers/i2c/i2c-core-acpi.c
 
+I2C ADDRESS TRANSLATOR (ATR)
+M:	Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+R:	Luca Ceresoli <luca.ceresoli@bootlin.com>
+L:	linux-i2c@vger.kernel.org
+S:	Maintained
+F:	drivers/i2c/i2c-atr.c
+F:	include/linux/i2c-atr.h
+
 I2C CONTROLLER DRIVER FOR NVIDIA GPU
 M:	Ajay Gupta <ajayg@nvidia.com>
 L:	linux-i2c@vger.kernel.org
diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig
index 438905e2a1d0..c6d1a345ea6d 100644
--- a/drivers/i2c/Kconfig
+++ b/drivers/i2c/Kconfig
@@ -71,6 +71,15 @@ config I2C_MUX
 
 source "drivers/i2c/muxes/Kconfig"
 
+config I2C_ATR
+	tristate "I2C Address Translator (ATR) support"
+	help
+	  Enable support for I2C Address Translator (ATR) chips.
+
+	  An ATR allows accessing multiple I2C busses from a single
+	  physical bus via address translation instead of bus selection as
+	  i2c-muxes do.
+
 config I2C_HELPER_AUTO
 	bool "Autoselect pertinent helper modules"
 	default y
diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile
index c1d493dc9bac..3f71ce4711e3 100644
--- a/drivers/i2c/Makefile
+++ b/drivers/i2c/Makefile
@@ -13,6 +13,7 @@ i2c-core-$(CONFIG_OF) 		+= i2c-core-of.o
 obj-$(CONFIG_I2C_SMBUS)		+= i2c-smbus.o
 obj-$(CONFIG_I2C_CHARDEV)	+= i2c-dev.o
 obj-$(CONFIG_I2C_MUX)		+= i2c-mux.o
+obj-$(CONFIG_I2C_ATR)		+= i2c-atr.o
 obj-y				+= algos/ busses/ muxes/
 obj-$(CONFIG_I2C_STUB)		+= i2c-stub.o
 obj-$(CONFIG_I2C_SLAVE_EEPROM)	+= i2c-slave-eeprom.o
diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c
new file mode 100644
index 000000000000..1d3b25a6550f
--- /dev/null
+++ b/drivers/i2c/i2c-atr.c
@@ -0,0 +1,503 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019,2022 Luca Ceresoli <luca@lucaceresoli.net>
+ *
+ * Originally based on i2c-mux.c
+ */
+
+#include <linux/fwnode.h>
+#include <linux/i2c-atr.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#define ATR_MAX_ADAPTERS 99	/* Just a sanity limit */
+#define ATR_MAX_SYMLINK_LEN 16	/* Longest name is 10 chars: "channel-99" */
+
+/**
+ * struct i2c_atr_cli2alias_pair - Hold the alias assigned to a client.
+ * @node:   List node
+ * @client: Pointer to the client on the child bus
+ * @alias:  I2C alias address assigned by the driver.
+ *          This is the address that will be used to issue I2C transactions
+ *          on the parent (physical) bus.
+ */
+struct i2c_atr_cli2alias_pair {
+	struct list_head node;
+	const struct i2c_client *client;
+	u16 alias;
+};
+
+/* Data for each channel (child bus) */
+struct i2c_atr_chan {
+	struct i2c_adapter adap;
+	struct i2c_atr *atr;
+	u32 chan_id;
+
+	struct list_head alias_list;
+
+	u16 *orig_addrs;
+	unsigned int orig_addrs_size;
+	struct mutex orig_addrs_lock; /* Lock orig_addrs during xfer */
+};
+
+static struct i2c_atr_cli2alias_pair *
+i2c_atr_find_mapping_by_client(const struct list_head *list,
+			       const struct i2c_client *client)
+{
+	struct i2c_atr_cli2alias_pair *c2a;
+
+	list_for_each_entry(c2a, list, node) {
+		if (c2a->client == client)
+			return c2a;
+	}
+
+	return NULL;
+}
+
+static struct i2c_atr_cli2alias_pair *
+i2c_atr_find_mapping_by_addr(const struct list_head *list, u16 phys_addr)
+{
+	struct i2c_atr_cli2alias_pair *c2a;
+
+	list_for_each_entry(c2a, list, node) {
+		if (c2a->client->addr == phys_addr)
+			return c2a;
+	}
+
+	return NULL;
+}
+
+/*
+ * Replace all message addresses with their aliases, saving the original
+ * addresses.
+ *
+ * This function is internal for use in i2c_atr_master_xfer(). It must be
+ * followed by i2c_atr_unmap_msgs() to restore the original addresses.
+ */
+static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
+			    int num)
+{
+	struct i2c_atr *atr = chan->atr;
+	static struct i2c_atr_cli2alias_pair *c2a;
+	int i;
+
+	/* Ensure we have enough room to save the original addresses */
+	if (unlikely(chan->orig_addrs_size < num)) {
+		u16 *new_buf;
+
+		new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
+					GFP_KERNEL);
+		if (!new_buf)
+			return -ENOMEM;
+
+		kfree(chan->orig_addrs);
+		chan->orig_addrs = new_buf;
+		chan->orig_addrs_size = num;
+	}
+
+	for (i = 0; i < num; i++) {
+		chan->orig_addrs[i] = msgs[i].addr;
+
+		c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list,
+						   msgs[i].addr);
+		if (!c2a) {
+			dev_err(atr->dev, "client 0x%02x not mapped!\n",
+				msgs[i].addr);
+			return -ENXIO;
+		}
+
+		msgs[i].addr = c2a->alias;
+	}
+
+	return 0;
+}
+
+/*
+ * Restore all message address aliases with the original addresses. This
+ * function is internal for use in i2c_atr_master_xfer().
+ *
+ * @see i2c_atr_map_msgs()
+ */
+static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
+			       int num)
+{
+	int i;
+
+	for (i = 0; i < num; i++)
+		msgs[i].addr = chan->orig_addrs[i];
+}
+
+static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+			       int num)
+{
+	struct i2c_atr_chan *chan = adap->algo_data;
+	struct i2c_atr *atr = chan->atr;
+	struct i2c_adapter *parent = atr->parent;
+	int ret;
+
+	/* Switch to the right atr port */
+	if (atr->ops->select) {
+		ret = atr->ops->select(atr, chan->chan_id);
+		if (ret < 0)
+			goto out_deselect;
+	}
+
+	/* Translate addresses */
+	mutex_lock(&chan->orig_addrs_lock);
+	ret = i2c_atr_map_msgs(chan, msgs, num);
+	if (ret < 0)
+		goto out_unlock_deselect;
+
+	/* Perform the transfer */
+	ret = i2c_transfer(parent, msgs, num);
+
+	/* Restore addresses */
+	i2c_atr_unmap_msgs(chan, msgs, num);
+
+out_unlock_deselect:
+	mutex_unlock(&chan->orig_addrs_lock);
+
+out_deselect:
+	if (atr->ops->deselect)
+		atr->ops->deselect(atr, chan->chan_id);
+
+	return ret;
+}
+
+static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr,
+			      unsigned short flags, char read_write, u8 command,
+			      int size, union i2c_smbus_data *data)
+{
+	struct i2c_atr_chan *chan = adap->algo_data;
+	struct i2c_atr *atr = chan->atr;
+	struct i2c_adapter *parent = atr->parent;
+	struct i2c_atr_cli2alias_pair *c2a;
+	int ret = 0;
+
+	c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, addr);
+	if (!c2a) {
+		dev_err(atr->dev, "client 0x%02x not mapped!\n", addr);
+		return -ENXIO;
+	}
+
+	if (atr->ops->select)
+		ret = atr->ops->select(atr, chan->chan_id);
+	if (!ret)
+		ret = i2c_smbus_xfer(parent, c2a->alias, flags, read_write,
+				     command, size, data);
+	if (atr->ops->deselect)
+		atr->ops->deselect(atr, chan->chan_id);
+
+	return ret;
+}
+
+static u32 i2c_atr_functionality(struct i2c_adapter *adap)
+{
+	struct i2c_atr_chan *chan = adap->algo_data;
+	struct i2c_adapter *parent = chan->atr->parent;
+
+	return parent->algo->functionality(parent);
+}
+
+static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int flags)
+{
+	struct i2c_atr_chan *chan = adapter->algo_data;
+	struct i2c_atr *atr = chan->atr;
+
+	mutex_lock(&atr->lock);
+}
+
+static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int flags)
+{
+	struct i2c_atr_chan *chan = adapter->algo_data;
+	struct i2c_atr *atr = chan->atr;
+
+	return mutex_trylock(&atr->lock);
+}
+
+static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int flags)
+{
+	struct i2c_atr_chan *chan = adapter->algo_data;
+	struct i2c_atr *atr = chan->atr;
+
+	mutex_unlock(&atr->lock);
+}
+
+static const struct i2c_lock_operations i2c_atr_lock_ops = {
+	.lock_bus =    i2c_atr_lock_bus,
+	.trylock_bus = i2c_atr_trylock_bus,
+	.unlock_bus =  i2c_atr_unlock_bus,
+};
+
+static int i2c_atr_attach_client(struct i2c_adapter *adapter,
+				 const struct i2c_board_info *info,
+				 const struct i2c_client *client)
+{
+	struct i2c_atr_chan *chan = adapter->algo_data;
+	struct i2c_atr *atr = chan->atr;
+	struct i2c_atr_cli2alias_pair *c2a;
+	u16 alias_id;
+	int ret;
+
+	c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
+	if (!c2a)
+		return -ENOMEM;
+
+	ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
+				      &alias_id);
+	if (ret)
+		goto err_free;
+	if (alias_id == 0) {
+		ret = -EINVAL;
+		goto err_free;
+	}
+
+	c2a->client = client;
+	c2a->alias = alias_id;
+	list_add(&c2a->node, &chan->alias_list);
+
+	return 0;
+
+err_free:
+	kfree(c2a);
+	return ret;
+}
+
+static void i2c_atr_detach_client(struct i2c_adapter *adapter,
+				  const struct i2c_client *client)
+{
+	struct i2c_atr_chan *chan = adapter->algo_data;
+	struct i2c_atr *atr = chan->atr;
+	struct i2c_atr_cli2alias_pair *c2a;
+
+	atr->ops->detach_client(atr, chan->chan_id, client);
+
+	c2a = i2c_atr_find_mapping_by_client(&chan->alias_list, client);
+	if (c2a) {
+		list_del(&c2a->node);
+		kfree(c2a);
+	}
+}
+
+static const struct i2c_attach_operations i2c_atr_attach_ops = {
+	.attach_client = i2c_atr_attach_client,
+	.detach_client = i2c_atr_detach_client,
+};
+
+/**
+ * i2c_atr_add_adapter - Create a child ("downstream") I2C bus.
+ * @atr:        The I2C ATR
+ * @chan_id:    Index of the new adapter (0 .. max_adapters-1).  This value is
+ *              passed to the callbacks in `struct i2c_atr_ops`.
+ * @bus_handle: The fwnode handle that points to the adapter's i2c
+ *              peripherals, or NULL.
+ *
+ * After calling this function a new i2c bus will appear. Adding and
+ * removing devices on the downstream bus will result in calls to the
+ * `attach_client` and `detach_client` callbacks for the driver to assign
+ * an alias to the device.
+ *
+ * The adapter's fwnode is set to 'bus_handle', or if 'bus_handle' is NULL the
+ * function looks for a child node whose 'reg' property matches the chan_id
+ * under the i2c-atr device's 'i2c-atr' node.
+
+ * Call i2c_atr_del_adapter() to remove the adapter.
+ *
+ * Return: 0 on success, a negative error code otherwise.
+ */
+int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
+			struct fwnode_handle *bus_handle)
+{
+	struct i2c_adapter *parent = atr->parent;
+	struct device *dev = atr->dev;
+	struct i2c_atr_chan *chan;
+	char symlink_name[ATR_MAX_SYMLINK_LEN];
+	int ret;
+
+	if (chan_id >= atr->max_adapters) {
+		dev_err(dev, "No room for more i2c-atr adapters\n");
+		return -EINVAL;
+	}
+
+	if (atr->adapter[chan_id]) {
+		dev_err(dev, "Adapter %d already present\n", chan_id);
+		return -EEXIST;
+	}
+
+	chan = kzalloc(sizeof(*chan), GFP_KERNEL);
+	if (!chan)
+		return -ENOMEM;
+
+	chan->atr = atr;
+	chan->chan_id = chan_id;
+	INIT_LIST_HEAD(&chan->alias_list);
+	mutex_init(&chan->orig_addrs_lock);
+
+	snprintf(chan->adap.name, sizeof(chan->adap.name), "i2c-%d-atr-%d",
+		 i2c_adapter_id(parent), chan_id);
+	chan->adap.owner = THIS_MODULE;
+	chan->adap.algo = &atr->algo;
+	chan->adap.algo_data = chan;
+	chan->adap.dev.parent = dev;
+	chan->adap.retries = parent->retries;
+	chan->adap.timeout = parent->timeout;
+	chan->adap.quirks = parent->quirks;
+	chan->adap.lock_ops = &i2c_atr_lock_ops;
+	chan->adap.attach_ops = &i2c_atr_attach_ops;
+
+	if (bus_handle) {
+		device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle));
+	} else {
+		struct fwnode_handle *atr_node;
+		struct fwnode_handle *child;
+		u32 reg;
+
+		atr_node = device_get_named_child_node(dev, "i2c-atr");
+
+		fwnode_for_each_child_node(atr_node, child) {
+			ret = fwnode_property_read_u32(child, "reg", &reg);
+			if (ret)
+				continue;
+			if (chan_id == reg)
+				break;
+		}
+
+		device_set_node(&chan->adap.dev, child);
+		fwnode_handle_put(atr_node);
+	}
+
+	ret = i2c_add_adapter(&chan->adap);
+	if (ret) {
+		dev_err(dev, "failed to add atr-adapter %u (error=%d)\n",
+			chan_id, ret);
+		goto err_mutex_destroy;
+	}
+
+	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
+		 chan->chan_id);
+
+	ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device");
+	if (ret)
+		dev_warn(dev, "can't create symlink to atr device\n");
+	ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name);
+	if (ret)
+		dev_warn(dev, "can't create symlink for channel %u\n", chan_id);
+
+	dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap));
+
+	atr->adapter[chan_id] = &chan->adap;
+	return 0;
+
+err_mutex_destroy:
+	fwnode_handle_put(dev_fwnode(&chan->adap.dev));
+	mutex_destroy(&chan->orig_addrs_lock);
+	kfree(chan);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(i2c_atr_add_adapter);
+
+/**
+ * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by
+ * i2c_atr_del_adapter().
+ * @atr:     The I2C ATR
+ * @chan_id: Index of the `adapter to be removed (0 .. max_adapters-1)
+ */
+void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
+{
+	char symlink_name[ATR_MAX_SYMLINK_LEN];
+
+	struct i2c_adapter *adap = atr->adapter[chan_id];
+	struct i2c_atr_chan *chan = adap->algo_data;
+	struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
+	struct device *dev = atr->dev;
+
+	if (!atr->adapter[chan_id]) {
+		dev_err(dev, "Adapter %d does not exist\n", chan_id);
+		return;
+	}
+
+	dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap));
+
+	atr->adapter[chan_id] = NULL;
+
+	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
+		 chan->chan_id);
+	sysfs_remove_link(&dev->kobj, symlink_name);
+	sysfs_remove_link(&chan->adap.dev.kobj, "atr_device");
+
+	i2c_del_adapter(adap);
+	fwnode_handle_put(fwnode);
+	mutex_destroy(&chan->orig_addrs_lock);
+	kfree(chan->orig_addrs);
+	kfree(chan);
+}
+EXPORT_SYMBOL_GPL(i2c_atr_del_adapter);
+
+/**
+ * i2c_atr_new() - Allocate and initialize an I2C ATR helper.
+ * @parent:       The parent (upstream) adapter
+ * @dev:          The device acting as an ATR
+ * @ops:          Driver-specific callbacks
+ * @max_adapters: Maximum number of child adapters
+ *
+ * The new ATR helper is connected to the parent adapter but has no child
+ * adapters. Call i2c_atr_add_adapter() to add some.
+ *
+ * Call i2c_atr_delete() to remove.
+ *
+ * Return: pointer to the new ATR helper object, or ERR_PTR
+ */
+struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
+			    const struct i2c_atr_ops *ops, int max_adapters)
+{
+	struct i2c_atr *atr;
+
+	if (max_adapters > ATR_MAX_ADAPTERS)
+		return ERR_PTR(-EINVAL);
+
+	if (!ops || !ops->attach_client || !ops->detach_client)
+		return ERR_PTR(-EINVAL);
+
+	atr = devm_kzalloc(dev, struct_size(atr, adapter, max_adapters),
+			   GFP_KERNEL);
+	if (!atr)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&atr->lock);
+
+	atr->parent = parent;
+	atr->dev = dev;
+	atr->ops = ops;
+	atr->max_adapters = max_adapters;
+
+	if (parent->algo->master_xfer)
+		atr->algo.master_xfer = i2c_atr_master_xfer;
+	if (parent->algo->smbus_xfer)
+		atr->algo.smbus_xfer = i2c_atr_smbus_xfer;
+	atr->algo.functionality = i2c_atr_functionality;
+
+	return atr;
+}
+EXPORT_SYMBOL_GPL(i2c_atr_new);
+
+/**
+ * i2c_atr_delete - Delete an I2C ATR helper.
+ * @atr: I2C ATR helper to be deleted.
+ *
+ * Precondition: all the adapters added with i2c_atr_add_adapter() mumst be
+ * removed by calling i2c_atr_del_adapter().
+ */
+void i2c_atr_delete(struct i2c_atr *atr)
+{
+	mutex_destroy(&atr->lock);
+}
+EXPORT_SYMBOL_GPL(i2c_atr_delete);
+
+MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
+MODULE_DESCRIPTION("I2C Address Translator");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/i2c-atr.h b/include/linux/i2c-atr.h
new file mode 100644
index 000000000000..044c87c5b336
--- /dev/null
+++ b/include/linux/i2c-atr.h
@@ -0,0 +1,82 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019,2022 Luca Ceresoli <luca@lucaceresoli.net>
+ *
+ * Based on i2c-mux.h
+ */
+
+#ifndef _LINUX_I2C_ATR_H
+#define _LINUX_I2C_ATR_H
+
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+struct device;
+struct i2c_atr;
+struct fwnode_handle;
+
+/**
+ * struct i2c_atr_ops - Callbacks from ATR to the device driver.
+ * @select:        Ask the driver to select a child bus (optional)
+ * @deselect:      Ask the driver to deselect a child bus (optional)
+ * @attach_client: Notify the driver of a new device connected on a child
+ *                 bus. The driver must choose an I2C alias, configure the
+ *                 hardware to use it and return it in `alias_id`.
+ * @detach_client: Notify the driver of a device getting disconnected. The
+ *                 driver must configure the hardware to stop using the
+ *                 alias.
+ *
+ * All these functions return 0 on success, a negative error code otherwise.
+ */
+struct i2c_atr_ops {
+	int (*select)(struct i2c_atr *atr, u32 chan_id);
+	int (*deselect)(struct i2c_atr *atr, u32 chan_id);
+	int (*attach_client)(struct i2c_atr *atr, u32 chan_id,
+			     const struct i2c_board_info *info,
+			     const struct i2c_client *client, u16 *alias_id);
+	void (*detach_client)(struct i2c_atr *atr, u32 chan_id,
+			      const struct i2c_client *client);
+};
+
+/**
+ * struct i2c_atr - Represents the I2C ATR instance
+ */
+struct i2c_atr {
+	/* private: internal use only */
+
+	struct i2c_adapter *parent;
+	struct device *dev;
+	const struct i2c_atr_ops *ops;
+
+	void *priv;
+
+	struct i2c_algorithm algo;
+	/* lock for the I2C bus segment (see struct i2c_lock_operations) */
+	struct mutex lock;
+	int max_adapters;
+
+	struct i2c_adapter *adapter[];
+};
+
+struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
+			    const struct i2c_atr_ops *ops, int max_adapters);
+void i2c_atr_delete(struct i2c_atr *atr);
+
+static inline void i2c_atr_set_clientdata(struct i2c_atr *atr, void *data)
+{
+	atr->priv = data;
+}
+
+static inline void *i2c_atr_get_clientdata(struct i2c_atr *atr)
+{
+	return atr->priv;
+}
+
+int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
+			struct fwnode_handle *bus_np);
+void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id);
+
+#endif /* _LINUX_I2C_ATR_H */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
  2022-12-08 10:39 ` [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach Tomi Valkeinen
  2022-12-08 10:40 ` [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-11 17:13   ` Laurent Pinchart
  2022-12-08 10:40 ` [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953 Tomi Valkeinen
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
 1 file changed, 121 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
new file mode 100644
index 000000000000..3a5b34c6bb64
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
@@ -0,0 +1,121 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
+
+maintainers:
+  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+
+description:
+  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
+
+properties:
+  compatible:
+    enum:
+      - ti,ds90ub913a-q1
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+  clocks:
+    maxItems: 1
+    description:
+      Reference clock connected to the CLKIN pin.
+
+  clock-names:
+    items:
+      - const: clkin
+
+  '#clock-cells':
+    const: 0
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 input port
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: FPD-Link 3 output port
+
+  i2c:
+    $ref: /schemas/i2c/i2c-controller.yaml#
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - '#gpio-cells'
+  - gpio-controller
+  - '#clock-cells'
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    serializer {
+      compatible = "ti,ds90ub913a-q1";
+
+      gpio-controller;
+      #gpio-cells = <2>;
+
+      clocks = <&clk_cam_48M>;
+      clock-names = "clkin";
+
+      #clock-cells = <0>;
+
+      ports {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        port@0 {
+          reg = <0>;
+          ub913_in: endpoint {
+            remote-endpoint = <&sensor_out>;
+          };
+        };
+
+        port@1 {
+          reg = <1>;
+          endpoint {
+            remote-endpoint = <&deser_fpd_in>;
+          };
+        };
+      };
+
+      i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        sensor@48 {
+          compatible = "aptina,mt9v111";
+          reg = <0x48>;
+
+          clocks = <&fixed_clock>;
+
+          port {
+            sensor_out: endpoint {
+              remote-endpoint = <&ub913_in>;
+            };
+          };
+        };
+      };
+    };
+...
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
                   ` (2 preceding siblings ...)
  2022-12-08 10:40 ` [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913 Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-09 21:27   ` Rob Herring
  2022-12-11 17:34   ` Laurent Pinchart
  2022-12-08 10:40 ` [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960 Tomi Valkeinen
                   ` (4 subsequent siblings)
  8 siblings, 2 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 .../bindings/media/i2c/ti,ds90ub953.yaml      | 112 ++++++++++++++++++
 1 file changed, 112 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
new file mode 100644
index 000000000000..fd7d25d93e2c
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
@@ -0,0 +1,112 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub953.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments DS90UB953 FPD-Link 3 Serializer
+
+maintainers:
+  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+
+description:
+  The TI DS90UB953 is an FPD-Link 3 video serializer for MIPI CSI-2.
+
+properties:
+  compatible:
+    enum:
+      - ti,ds90ub953-q1
+      - ti,ds90ub971-q1
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+  '#clock-cells':
+    const: 0
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 input port
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: FPD-Link 3 output port
+
+  i2c:
+    $ref: /schemas/i2c/i2c-controller.yaml#
+    unevaluatedProperties: false
+
+required:
+  - compatible
+  - '#gpio-cells'
+  - gpio-controller
+  - '#clock-cells'
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    serializer {
+      compatible = "ti,ds90ub953-q1";
+
+      gpio-controller;
+      #gpio-cells = <2>;
+
+      #clock-cells = <0>;
+
+      ports {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        port@0 {
+          reg = <0>;
+          ub953_in: endpoint {
+            clock-lanes = <0>;
+            data-lanes = <1 2 3 4>;
+            remote-endpoint = <&sensor_out>;
+          };
+        };
+
+        port@1 {
+          reg = <1>;
+          endpoint {
+            remote-endpoint = <&deser_fpd_in>;
+          };
+        };
+      };
+
+      i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        sensor@1a {
+          compatible = "sony,imx274";
+          reg = <0x1a>;
+
+          reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
+
+          port {
+            sensor_out: endpoint {
+              remote-endpoint = <&ub953_in>;
+            };
+          };
+        };
+      };
+    };
+...
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
                   ` (3 preceding siblings ...)
  2022-12-08 10:40 ` [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953 Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-09 21:30   ` Rob Herring
  2022-12-11 17:58   ` Laurent Pinchart
  2022-12-08 10:40 ` [PATCH v5 6/8] media: i2c: add DS90UB960 driver Tomi Valkeinen
                   ` (3 subsequent siblings)
  8 siblings, 2 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
 1 file changed, 358 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml

diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
new file mode 100644
index 000000000000..d8b5e219d420
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
@@ -0,0 +1,358 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
+
+maintainers:
+  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+
+description:
+  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
+  forwarding.
+
+properties:
+  compatible:
+    enum:
+      - ti,ds90ub960-q1
+      - ti,ds90ub9702-q1
+
+  reg:
+    maxItems: 1
+    description:
+      i2c addresses for the deserializer and the serializers
+
+  clocks:
+    maxItems: 1
+    description:
+      Reference clock connected to the REFCLK pin.
+
+  clock-names:
+    items:
+      - const: refclk
+
+  powerdown-gpios:
+    maxItems: 1
+    description:
+      Specifier for the GPIO connected to the PDB pin.
+
+  i2c-alias-pool:
+    $ref: /schemas/types.yaml#/definitions/uint16-array
+    description:
+      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
+      used to access the remote peripherals. The addresses must be available,
+      not used by any other peripheral. Each remote peripheral is assigned an
+      alias from the pool, and transactions to that address will be forwarded
+      to the remote peripheral, with the address translated to the remote
+      peripheral's real address.
+
+  links:
+    type: object
+    additionalProperties: false
+
+    properties:
+      '#address-cells':
+        const: 1
+
+      '#size-cells':
+        const: 0
+
+      ti,manual-strobe:
+        type: boolean
+        description:
+          Enable manual strobe position and EQ level
+
+    patternProperties:
+      '^link@[0-9a-f]+$':
+        type: object
+        additionalProperties: false
+        properties:
+          reg:
+            description: The link number
+            maxItems: 1
+
+          i2c-alias:
+            description:
+              The i2c address used for the serializer. Transactions to this
+              address on the i2c bus where the deserializer resides are
+              forwarded to the serializer.
+
+          ti,rx-mode:
+            $ref: /schemas/types.yaml#/definitions/uint32
+            enum:
+              - 0 # RAW10
+              - 1 # RAW12 HF
+              - 2 # RAW12 LF
+              - 3 # CSI2 SYNC
+              - 4 # CSI2 NON-SYNC
+            description: FPD-Link Input Mode
+
+          ti,cdr-mode:
+            $ref: /schemas/types.yaml#/definitions/uint32
+            enum:
+              - 0 # FPD3
+              - 1 # FPD4
+            description: FPD-Link CDR Mode
+
+          ti,strobe-pos:
+            $ref: /schemas/types.yaml#/definitions/int32
+            minimum: -13
+            maximum: 13
+            description: Manual strobe position
+
+          ti,eq-level:
+            $ref: /schemas/types.yaml#/definitions/uint32
+            maximum: 14
+            description: Manual EQ level
+
+          serializer:
+            type: object
+            description: FPD-Link Serializer node
+
+        required:
+          - reg
+          - i2c-alias
+          - ti,rx-mode
+          - serializer
+
+  ports:
+    $ref: /schemas/graph.yaml#/properties/ports
+
+    properties:
+      port@0:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: FPD-Link input 0
+
+      port@1:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: FPD-Link input 1
+
+      port@2:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: FPD-Link input 2
+
+      port@3:
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
+        description: FPD-Link input 3
+
+      port@4:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 Output 0
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+      port@5:
+        $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
+        description: CSI-2 Output 1
+
+        properties:
+          endpoint:
+            $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
+
+            properties:
+              data-lanes:
+                minItems: 1
+                maxItems: 4
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+  - ports
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    i2c {
+      clock-frequency = <400000>;
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      deser@3d {
+        compatible = "ti,ds90ub960-q1";
+        reg = <0x3d>;
+
+        clock-names = "refclk";
+        clocks = <&fixed_clock>;
+
+        powerdown-gpios = <&pca9555 7 GPIO_ACTIVE_LOW>;
+
+        i2c-alias-pool = /bits/ 16 <0x4a 0x4b 0x4c 0x4d 0x4e 0x4f>;
+
+        ports {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          /* Port 0, Camera 0 */
+          port@0 {
+            reg = <0>;
+
+            ub960_fpd3_1_in: endpoint {
+              remote-endpoint = <&ub953_1_out>;
+            };
+          };
+
+          /* Port 1, Camera 1 */
+          port@1 {
+            reg = <1>;
+
+            ub960_fpd3_2_in: endpoint {
+              remote-endpoint = <&ub913_2_out>;
+            };
+          };
+
+          /* Port 4, CSI-2 TX */
+          port@4 {
+            reg = <4>;
+            ds90ub960_0_csi_out: endpoint {
+              data-lanes = <1 2 3 4>;
+              link-frequencies = /bits/ 64 <800000000>;
+              remote-endpoint = <&csi2_phy0>;
+            };
+          };
+        };
+
+        links {
+          #address-cells = <1>;
+          #size-cells = <0>;
+
+          /* Link 0 has DS90UB953 serializer and IMX274 sensor */
+
+          link@0 {
+            reg = <0>;
+            i2c-alias = <0x44>;
+
+            ti,rx-mode = <3>;
+
+            serializer1: serializer {
+              compatible = "ti,ds90ub953-q1";
+
+              gpio-controller;
+              #gpio-cells = <2>;
+
+              #clock-cells = <0>;
+
+              ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                  reg = <0>;
+                  ub953_1_in: endpoint {
+                    data-lanes = <1 2 3 4>;
+                    remote-endpoint = <&sensor_1_out>;
+                  };
+                };
+
+                port@1 {
+                  reg = <1>;
+
+                  ub953_1_out: endpoint {
+                    remote-endpoint = <&ub960_fpd3_1_in>;
+                  };
+                };
+              };
+
+              i2c {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                sensor@1a {
+                  compatible = "sony,imx274";
+                  reg = <0x1a>;
+
+                  reset-gpios = <&serializer1 0 GPIO_ACTIVE_LOW>;
+
+                  port {
+                    sensor_1_out: endpoint {
+                      remote-endpoint = <&ub953_1_in>;
+                    };
+                  };
+                };
+              };
+            };
+          };  /* End of link@0 */
+
+          /* Link 1 has DS90UB913 serializer and MT9V111 sensor */
+
+          link@1 {
+            reg = <1>;
+            i2c-alias = <0x45>;
+
+            ti,rx-mode = <0>;
+
+            serializer2: serializer {
+              compatible = "ti,ds90ub913a-q1";
+
+              gpio-controller;
+              #gpio-cells = <2>;
+
+              clocks = <&clk_cam_48M>;
+              clock-names = "clkin";
+
+              #clock-cells = <0>;
+
+              ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                  reg = <0>;
+                  ub913_2_in: endpoint {
+                    remote-endpoint = <&sensor_2_out>;
+                  };
+                };
+
+                port@1 {
+                  reg = <1>;
+
+                  ub913_2_out: endpoint {
+                    remote-endpoint = <&ub960_fpd3_2_in>;
+                  };
+                };
+              };
+
+              i2c {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                sensor@48 {
+                  compatible = "aptina,mt9v111";
+                  reg = <0x48>;
+
+                  clocks = <&serializer2>;
+
+                  port {
+                    sensor_2_out: endpoint {
+                      remote-endpoint = <&ub913_2_in>;
+                    };
+                  };
+                };
+              };
+            };
+          }; /* End of link@1 */
+        };
+      };
+    };
+...
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 6/8] media: i2c: add DS90UB960 driver
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
                   ` (4 preceding siblings ...)
  2022-12-08 10:40 ` [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960 Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-08 10:40 ` [PATCH v5 7/8] media: i2c: add DS90UB913 driver Tomi Valkeinen
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Add driver for TI DS90UB960 FPDLink-3 Deserializer.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 drivers/media/i2c/Kconfig     |   21 +
 drivers/media/i2c/Makefile    |    2 +
 drivers/media/i2c/ds90ub960.c | 4195 +++++++++++++++++++++++++++++++++
 include/media/i2c/ds90ub9xx.h |   16 +
 4 files changed, 4234 insertions(+)
 create mode 100644 drivers/media/i2c/ds90ub960.c
 create mode 100644 include/media/i2c/ds90ub9xx.h

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 7806d4b81716..a23f723b89b5 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1595,4 +1595,25 @@ config VIDEO_THS7303
 
 endmenu
 
+#
+# Video serializers and deserializers (e.g. FPDLink)
+#
+
+menu "Video serializers and deserializers"
+
+config VIDEO_DS90UB960
+	tristate "TI DS90UB960 Deserializer"
+	depends on OF && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	select REGMAP_I2C
+	select OF_GPIO
+	select I2C_ATR
+	help
+	  Device driver for the Texas Instruments DS90UB960
+	  FPD-Link III Deserializer
+
+endmenu
+
 endif # VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 0a2933103dd9..2735b00437bb 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -142,3 +142,5 @@ obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o
 obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
 obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
 obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
+obj-$(CONFIG_VIDEO_DS90UB960)	+= ds90ub960.o
+
diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c
new file mode 100644
index 000000000000..fef704ee5529
--- /dev/null
+++ b/drivers/media/i2c/ds90ub960.c
@@ -0,0 +1,4195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DS90UB960-Q1 video deserializer
+ *
+ * Copyright (c) 2019 Luca Ceresoli <luca@lucaceresoli.net>
+ * Copyright (c) 2022 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c-atr.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include <media/i2c/ds90ub9xx.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+
+#define UB960_POLL_TIME_MS	500
+
+#define UB960_MAX_RX_NPORTS	4
+#define UB960_MAX_TX_NPORTS	2
+#define UB960_MAX_NPORTS	(UB960_MAX_RX_NPORTS + UB960_MAX_TX_NPORTS)
+
+#define UB960_MAX_PORT_ALIASES	8
+#define UB960_MAX_POOL_ALIASES	(UB960_MAX_RX_NPORTS * UB960_MAX_PORT_ALIASES)
+
+#define UB960_NUM_BC_GPIOS		4
+
+/*
+ * Register map
+ *
+ * 0x00-0x32   Shared (UB960_SR)
+ * 0x33-0x3A   CSI-2 TX (per-port paged on DS90UB960, shared on 954) (UB960_TR)
+ * 0x4C        Shared (UB960_SR)
+ * 0x4D-0x7F   FPD-Link RX, per-port paged (UB960_RR)
+ * 0xB0-0xBF   Shared (UB960_SR)
+ * 0xD0-0xDF   FPD-Link RX, per-port paged (UB960_RR)
+ * 0xF0-0xF5   Shared (UB960_SR)
+ * 0xF8-0xFB   Shared (UB960_SR)
+ * All others  Reserved
+ *
+ * Register prefixes:
+ * UB960_SR_* = Shared register
+ * UB960_RR_* = FPD-Link RX, per-port paged register
+ * UB960_TR_* = CSI-2 TX, per-port paged register
+ * UB960_XR_* = Reserved register
+ * UB960_IR_* = Indirect register
+ */
+
+#define UB960_SR_I2C_DEV_ID			0x00
+#define UB960_SR_RESET				0x01
+#define UB960_SR_GEN_CONFIG			0x02
+#define UB960_SR_REV_MASK			0x03
+#define UB960_SR_DEVICE_STS			0x04
+#define UB960_SR_PAR_ERR_THOLD_HI		0x05
+#define UB960_SR_PAR_ERR_THOLD_LO		0x06
+#define UB960_SR_BCC_WDOG_CTL			0x07
+#define UB960_SR_I2C_CTL1			0x08
+#define UB960_SR_I2C_CTL2			0x09
+#define UB960_SR_SCL_HIGH_TIME			0x0A
+#define UB960_SR_SCL_LOW_TIME			0x0B
+#define UB960_SR_RX_PORT_CTL			0x0C
+#define UB960_SR_IO_CTL				0x0D
+#define UB960_SR_GPIO_PIN_STS			0x0E
+#define UB960_SR_GPIO_INPUT_CTL			0x0F
+#define UB960_SR_GPIO_PIN_CTL(n)		(0x10 + (n)) /* n < UB960_NUM_GPIOS */
+#define UB960_SR_FS_CTL				0x18
+#define UB960_SR_FS_HIGH_TIME_1			0x19
+#define UB960_SR_FS_HIGH_TIME_0			0x1A
+#define UB960_SR_FS_LOW_TIME_1			0x1B
+#define UB960_SR_FS_LOW_TIME_0			0x1C
+#define UB960_SR_MAX_FRM_HI			0x1D
+#define UB960_SR_MAX_FRM_LO			0x1E
+#define UB960_SR_CSI_PLL_CTL			0x1F
+
+#define UB960_SR_FWD_CTL1			0x20
+#define UB960_SR_FWD_CTL1_PORT_DIS(n)		BIT((n) + 4)
+
+#define UB960_SR_FWD_CTL2			0x21
+#define UB960_SR_FWD_STS			0x22
+
+#define UB960_SR_INTERRUPT_CTL			0x23
+#define UB960_SR_INTERRUPT_CTL_INT_EN		BIT(7)
+#define UB960_SR_INTERRUPT_CTL_IE_CSI_TX0	BIT(4)
+#define UB960_SR_INTERRUPT_CTL_IE_RX(n)		BIT((n)) /* rxport[n] IRQ */
+#define UB960_SR_INTERRUPT_CTL_ALL		0x83 /* TODO 0x93 to enable CSI */
+
+#define UB960_SR_INTERRUPT_STS			0x24
+#define UB960_SR_INTERRUPT_STS_INT		BIT(7)
+#define UB960_SR_INTERRUPT_STS_IS_CSI_TX(n)	BIT(4 + (n)) /* txport[n] IRQ */
+#define UB960_SR_INTERRUPT_STS_IS_RX(n)		BIT((n)) /* rxport[n] IRQ */
+
+#define UB960_SR_TS_CONFIG			0x25
+#define UB960_SR_TS_CONTROL			0x26
+#define UB960_SR_TS_LINE_HI			0x27
+#define UB960_SR_TS_LINE_LO			0x28
+#define UB960_SR_TS_STATUS			0x29
+#define UB960_SR_TIMESTAMP_P0_HI		0x2A
+#define UB960_SR_TIMESTAMP_P0_LO		0x2B
+#define UB960_SR_TIMESTAMP_P1_HI		0x2C
+#define UB960_SR_TIMESTAMP_P1_LO		0x2D
+
+#define UB960_SR_CSI_PORT_SEL			0x32
+
+#define UB960_TR_CSI_CTL			0x33
+#define UB960_TR_CSI_CTL_CSI_CAL_EN		BIT(6)
+#define UB960_TR_CSI_CTL_CSI_ENABLE		BIT(0)
+
+#define UB960_TR_CSI_CTL2			0x34
+#define UB960_TR_CSI_STS			0x35
+#define UB960_TR_CSI_TX_ICR			0x36
+
+#define UB960_TR_CSI_TX_ISR			0x37
+#define UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR	BIT(3)
+#define UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR	BIT(1)
+
+#define UB960_TR_CSI_TEST_CTL			0x38
+#define UB960_TR_CSI_TEST_PATT_HI		0x39
+#define UB960_TR_CSI_TEST_PATT_LO		0x3A
+
+#define UB960_XR_SFILTER_CFG			0x41
+#define UB960_XR_AEQ_CTL1			0x42
+#define UB960_XR_AEQ_ERR_THOLD			0x43
+
+#define UB960_RR_BCC_ERR_CTL			0x46
+#define UB960_RR_BCC_STATUS			0x47
+#define UB960_RR_BCC_STATUS_SEQ_ERROR		BIT(5)
+#define UB960_RR_BCC_STATUS_MASTER_ERR		BIT(4)
+#define UB960_RR_BCC_STATUS_MASTER_TO		BIT(3)
+#define UB960_RR_BCC_STATUS_SLAVE_ERR		BIT(2)
+#define UB960_RR_BCC_STATUS_SLAVE_TO		BIT(1)
+#define UB960_RR_BCC_STATUS_RESP_ERR		BIT(0)
+
+#define UB960_RR_FPD3_CAP			0x4A
+#define UB960_RR_RAW_EMBED_DTYPE		0x4B
+
+#define UB960_SR_FPD3_PORT_SEL			0x4C
+
+#define UB960_RR_RX_PORT_STS1			0x4D
+#define UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR	BIT(5)
+#define UB960_RR_RX_PORT_STS1_LOCK_STS_CHG	BIT(4)
+#define UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR	BIT(3)
+#define UB960_RR_RX_PORT_STS1_PARITY_ERROR	BIT(2)
+#define UB960_RR_RX_PORT_STS1_PORT_PASS		BIT(1)
+#define UB960_RR_RX_PORT_STS1_LOCK_STS		BIT(0)
+
+#define UB960_RR_RX_PORT_STS2			0x4E
+#define UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE	BIT(7)
+#define UB960_RR_RX_PORT_STS2_LINE_LEN_CHG	BIT(6)
+#define UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR	BIT(5)
+#define UB960_RR_RX_PORT_STS2_BUFFER_ERROR	BIT(4)
+#define UB960_RR_RX_PORT_STS2_CSI_ERROR		BIT(3)
+#define UB960_RR_RX_PORT_STS2_FREQ_STABLE	BIT(2)
+#define UB960_RR_RX_PORT_STS2_CABLE_FAULT	BIT(1)
+#define UB960_RR_RX_PORT_STS2_LINE_CNT_CHG	BIT(0)
+
+#define UB960_RR_RX_FREQ_HIGH			0x4F
+#define UB960_RR_RX_FREQ_LOW			0x50
+#define UB960_RR_SENSOR_STS_0			0x51
+#define UB960_RR_SENSOR_STS_1			0x52
+#define UB960_RR_SENSOR_STS_2			0x53
+#define UB960_RR_SENSOR_STS_3			0x54
+#define UB960_RR_RX_PAR_ERR_HI			0x55
+#define UB960_RR_RX_PAR_ERR_LO			0x56
+#define UB960_RR_BIST_ERR_COUNT			0x57
+
+#define UB960_RR_BCC_CONFIG			0x58
+#define UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH	BIT(6)
+
+#define UB960_RR_DATAPATH_CTL1			0x59
+#define UB960_RR_DATAPATH_CTL2			0x5A
+#define UB960_RR_SER_ID				0x5B
+#define UB960_RR_SER_ALIAS_ID			0x5C
+
+/* For these two register sets: n < UB960_MAX_PORT_ALIASES */
+#define UB960_RR_SLAVE_ID(n)			(0x5D + (n))
+#define UB960_RR_SLAVE_ALIAS(n)			(0x65 + (n))
+
+#define UB960_RR_PORT_CONFIG			0x6D
+#define UB960_RR_BC_GPIO_CTL(n)			(0x6E + (n)) /* n < 2 */
+#define UB960_RR_RAW10_ID			0x70
+#define UB960_RR_RAW12_ID			0x71
+#define UB960_RR_CSI_VC_MAP			0x72
+#define UB960_RR_LINE_COUNT_HI			0x73
+#define UB960_RR_LINE_COUNT_LO			0x74
+#define UB960_RR_LINE_LEN_1			0x75
+#define UB960_RR_LINE_LEN_0			0x76
+#define UB960_RR_FREQ_DET_CTL			0x77
+#define UB960_RR_MAILBOX_1			0x78
+#define UB960_RR_MAILBOX_2			0x79
+
+#define UB960_RR_CSI_RX_STS			0x7A
+#define UB960_RR_CSI_RX_STS_LENGTH_ERR		BIT(3)
+#define UB960_RR_CSI_RX_STS_CKSUM_ERR		BIT(2)
+#define UB960_RR_CSI_RX_STS_ECC2_ERR		BIT(1)
+#define UB960_RR_CSI_RX_STS_ECC1_ERR		BIT(0)
+
+#define UB960_RR_CSI_ERR_COUNTER		0x7B
+#define UB960_RR_PORT_CONFIG2			0x7C
+#define UB960_RR_PORT_PASS_CTL			0x7D
+#define UB960_RR_SEN_INT_RISE_CTL		0x7E
+#define UB960_RR_SEN_INT_FALL_CTL		0x7F
+
+#define UB960_SR_CSI_FRAME_COUNT_HI(n)		(0x90 + 8 * (n))
+#define UB960_SR_CSI_FRAME_COUNT_LO(n)		(0x91 + 8 * (n))
+#define UB960_SR_CSI_FRAME_ERR_COUNT_HI(n)	(0x92 + 8 * (n))
+#define UB960_SR_CSI_FRAME_ERR_COUNT_LO(n)	(0x93 + 8 * (n))
+#define UB960_SR_CSI_LINE_COUNT_HI(n)		(0x94 + 8 * (n))
+#define UB960_SR_CSI_LINE_COUNT_LO(n)		(0x95 + 8 * (n))
+#define UB960_SR_CSI_LINE_ERR_COUNT_HI(n)	(0x96 + 8 * (n))
+#define UB960_SR_CSI_LINE_ERR_COUNT_LO(n)	(0x97 + 8 * (n))
+
+#define UB960_XR_REFCLK_FREQ			0xA5	/* UB960 */
+
+#define UB960_RR_VC_ID_MAP(x)			(0xa0 + (x)) /* UB9702 */
+
+#define UB960_SR_IND_ACC_CTL			0xB0
+#define UB960_SR_IND_ACC_CTL_IA_AUTO_INC	BIT(1)
+
+#define UB960_SR_IND_ACC_ADDR			0xB1
+#define UB960_SR_IND_ACC_DATA			0xB2
+#define UB960_SR_BIST_CONTROL			0xB3
+#define UB960_SR_MODE_IDX_STS			0xB8
+#define UB960_SR_LINK_ERROR_COUNT		0xB9
+#define UB960_SR_FPD3_ENC_CTL			0xBA
+#define UB960_SR_FV_MIN_TIME			0xBC
+#define UB960_SR_GPIO_PD_CTL			0xBE
+
+#define UB960_SR_FPD_RATE_CFG			0xc2	/* UB9702 */
+#define UB960_SR_CSI_PLL_DIV			0xc9	/* UB9702 */
+
+#define UB960_RR_PORT_DEBUG			0xD0
+#define UB960_RR_AEQ_CTL2			0xD2
+#define UB960_RR_AEQ_STATUS			0xD3
+#define UB960_RR_AEQ_BYPASS			0xD4
+#define UB960_RR_AEQ_MIN_MAX			0xD5
+#define UB960_RR_SFILTER_STS_0			0xD6
+#define UB960_RR_SFILTER_STS_1			0xD7
+#define UB960_RR_PORT_ICR_HI			0xD8
+#define UB960_RR_PORT_ICR_LO			0xD9
+#define UB960_RR_PORT_ISR_HI			0xDA
+#define UB960_RR_PORT_ISR_LO			0xDB
+#define UB960_RR_FC_GPIO_STS			0xDC
+#define UB960_RR_FC_GPIO_ICR			0xDD
+#define UB960_RR_SEN_INT_RISE_STS		0xDE
+#define UB960_RR_SEN_INT_FALL_STS		0xDF
+
+#define UB960_RR_CHANNEL_MODE			0xe4	/* UB9702 */
+
+#define UB960_SR_FPD3_RX_ID(n)			(0xF0 + (n))
+
+#define UB960_SR_I2C_RX_ID(n)			(0xF8 + (n)) /* < UB960_FPD_RX_NPORTS */
+
+/* Indirect register blocks */
+#define UB960_IND_TARGET_PAT_GEN		0x00
+#define UB960_IND_TARGET_RX_ANA(n)		(0x01 + (n))
+#define UB960_IND_TARGET_CSI_CSIPLL_REG_1	0x92	/* UB9702 */
+#define UB960_IND_TARGET_CSI_ANA		0x07
+
+/* UB960_IR_PGEN_*: Indirect Registers for Test Pattern Generator */
+
+#define UB960_IR_PGEN_CTL			0x01
+#define UB960_IR_PGEN_CTL_PGEN_ENABLE		BIT(0)
+
+#define UB960_IR_PGEN_CFG			0x02
+#define UB960_IR_PGEN_CSI_DI			0x03
+#define UB960_IR_PGEN_LINE_SIZE1		0x04
+#define UB960_IR_PGEN_LINE_SIZE0		0x05
+#define UB960_IR_PGEN_BAR_SIZE1			0x06
+#define UB960_IR_PGEN_BAR_SIZE0			0x07
+#define UB960_IR_PGEN_ACT_LPF1			0x08
+#define UB960_IR_PGEN_ACT_LPF0			0x09
+#define UB960_IR_PGEN_TOT_LPF1			0x0A
+#define UB960_IR_PGEN_TOT_LPF0			0x0B
+#define UB960_IR_PGEN_LINE_PD1			0x0C
+#define UB960_IR_PGEN_LINE_PD0			0x0D
+#define UB960_IR_PGEN_VBP			0x0E
+#define UB960_IR_PGEN_VFP			0x0F
+#define UB960_IR_PGEN_COLOR(n)			(0x10 + (n)) /* n < 15 */
+
+#define UB960_IR_RX_ANA_STROBE_SET_CLK		0x08
+#define UB960_IR_RX_ANA_STROBE_SET_DATA		0x09
+
+/* EQ related */
+
+#define UB960_MIN_AEQ_STROBE_POS -7
+#define UB960_MAX_AEQ_STROBE_POS 7
+
+#define UB960_MIN_MANUAL_STROBE_POS -(7 + 6)
+#define UB960_MAX_MANUAL_STROBE_POS (7 + 6)
+#define UB960_NUM_MANUAL_STROBE_POS (UB960_MAX_MANUAL_STROBE_POS - UB960_MIN_MANUAL_STROBE_POS + 1)
+
+#define UB960_MIN_EQ_LEVEL 0
+#define UB960_MAX_EQ_LEVEL 14
+#define UB960_NUM_EQ_LEVELS (UB960_MAX_EQ_LEVEL - UB960_MIN_EQ_LEVEL + 1)
+
+struct ub960_hw_data {
+	const char *model;
+	u8 num_rxports;
+	u8 num_txports;
+	bool ub9702;
+	bool fpdlink4;
+};
+
+enum ub960_rxport_mode {
+	RXPORT_MODE_RAW10 = 0,
+	RXPORT_MODE_RAW12_HF = 1,
+	RXPORT_MODE_RAW12_LF = 2,
+	RXPORT_MODE_CSI2_SYNC = 3,
+	RXPORT_MODE_CSI2_ASYNC = 4,
+	RXPORT_MODE_LAST = RXPORT_MODE_CSI2_ASYNC,
+};
+
+enum ub960_rxport_cdr {
+	RXPORT_CDR_FPD3 = 0,
+	RXPORT_CDR_FPD4 = 1,
+	RXPORT_CDR_LAST = RXPORT_CDR_FPD4,
+};
+
+struct ub960_rxport {
+	struct ub960_data      *priv;
+	u8                      nport;	/* RX port number, and index in priv->rxport[] */
+
+	struct v4l2_subdev     *sd;	/* Connected subdev */
+	struct fwnode_handle   *fwnode;
+
+	enum ub960_rxport_mode  rx_mode;
+	enum ub960_rxport_cdr	cdr_mode;
+
+	struct device_node     *remote_of_node;	/* 'serializer' OF node */
+	struct i2c_client      *ser_client;	/* Serializer */
+	unsigned short          ser_alias;	/* Serializer i2c alias (lower 7 bits) */
+
+	struct regulator	*vpoc;
+
+	/* EQ settings */
+	struct {
+		bool manual_eq;
+
+		s8 strobe_pos;
+
+		union {
+			struct {
+				u8 eq_level_min;
+				u8 eq_level_max;
+			} aeq;
+
+			struct {
+				u8 eq_level;
+			} manual;
+		};
+	} eq;
+
+	struct ds90ub9xx_platform_data ser_platform_data;
+};
+
+struct ub960_asd {
+	struct v4l2_async_subdev base;
+	struct ub960_rxport *rxport;
+};
+
+static inline struct ub960_asd *to_ub960_asd(struct v4l2_async_subdev *asd)
+{
+	return container_of(asd, struct ub960_asd, base);
+}
+
+struct ub960_txport {
+	struct ub960_data      *priv;
+	u8                      nport;	/* TX port number, and index in priv->txport[] */
+
+	u32 num_data_lanes;
+};
+
+struct atr_alias_table_entry {
+	u16 alias_id;	/* Alias ID from DT */
+
+	bool reserved;
+	u8 nport;
+	u8 slave_id;	/* i2c client's local i2c address */
+	u8 port_reg_idx;
+};
+
+struct atr_alias_table {
+	/* Protects fields in this struct */
+	struct mutex lock;
+
+	size_t num_entries;
+	struct atr_alias_table_entry *entries;
+};
+
+struct ub960_data {
+	const struct ub960_hw_data	*hw_data;
+	struct i2c_client	*client; /* for shared local registers */
+	struct regmap		*regmap;
+
+	/* lock for register access */
+	struct mutex		reg_lock;
+
+	struct clk		*refclk;
+
+	struct regulator	*vddio;
+
+	struct gpio_desc	*pd_gpio;
+	struct delayed_work	poll_work;
+	struct i2c_atr		*atr;
+	struct ub960_rxport	*rxports[UB960_MAX_RX_NPORTS];
+	struct ub960_txport	*txports[UB960_MAX_TX_NPORTS];
+
+	struct v4l2_subdev	sd;
+	struct media_pad	pads[UB960_MAX_NPORTS];
+
+	struct v4l2_ctrl_handler   ctrl_handler;
+	struct v4l2_async_notifier notifier;
+
+	u32 tx_data_rate;		/* Nominal data rate (Gb/s) */
+	s64 tx_link_freq[1];
+
+	struct atr_alias_table	atr_alias_table;
+
+	u8 current_read_rxport;
+	u8 current_write_rxport_mask;
+
+	u8 current_read_csiport;
+	u8 current_write_csiport_mask;
+
+	u8 current_indirect_target;
+
+	bool streaming;
+
+	u8 stored_fwd_ctl;
+
+	u64 stream_enable_mask[UB960_MAX_NPORTS];
+
+	/* These are common to all ports */
+	struct {
+		bool manual;
+
+		s8 min;
+		s8 max;
+	} strobe;
+};
+
+static void ub960_reset(struct ub960_data *priv, bool reset_regs);
+
+static inline struct ub960_data *sd_to_ub960(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ub960_data, sd);
+}
+
+enum {
+	TEST_PATTERN_DISABLED = 0,
+	TEST_PATTERN_V_COLOR_BARS_1,
+	TEST_PATTERN_V_COLOR_BARS_2,
+	TEST_PATTERN_V_COLOR_BARS_4,
+	TEST_PATTERN_V_COLOR_BARS_8,
+};
+
+static const char * const ub960_tpg_qmenu[] = {
+	"Disabled",
+	"1 vertical color bar",
+	"2 vertical color bars",
+	"4 vertical color bars",
+	"8 vertical color bars",
+};
+
+static inline bool ub960_pad_is_sink(struct ub960_data *priv, u32 pad)
+{
+	return pad < priv->hw_data->num_rxports;
+}
+
+static inline bool ub960_pad_is_source(struct ub960_data *priv, u32 pad)
+{
+	return pad >= priv->hw_data->num_rxports &&
+	       pad < (priv->hw_data->num_rxports + priv->hw_data->num_txports);
+}
+
+static inline unsigned int ub960_pad_to_port(struct ub960_data *priv, u32 pad)
+{
+	if (ub960_pad_is_sink(priv, pad))
+		return pad;
+	else
+		return pad - priv->hw_data->num_rxports;
+}
+
+struct ub960_format_info {
+	u32 code;
+	u32 bpp;
+	u8 datatype;
+	bool meta;
+};
+
+static const struct ub960_format_info ub960_formats[] = {
+	{ .code = MEDIA_BUS_FMT_YUYV8_1X16, .bpp = 16, .datatype = 0x1e, },
+	{ .code = MEDIA_BUS_FMT_UYVY8_1X16, .bpp = 16, .datatype = 0x1e, },
+	{ .code = MEDIA_BUS_FMT_VYUY8_1X16, .bpp = 16, .datatype = 0x1e, },
+	{ .code = MEDIA_BUS_FMT_YVYU8_1X16, .bpp = 16, .datatype = 0x1e, },
+
+	/* Legacy */
+	{ .code = MEDIA_BUS_FMT_YUYV8_2X8, .bpp = 16, .datatype = 0x1e, },
+	{ .code = MEDIA_BUS_FMT_UYVY8_2X8, .bpp = 16, .datatype = 0x1e, },
+	{ .code = MEDIA_BUS_FMT_VYUY8_2X8, .bpp = 16, .datatype = 0x1e, },
+	{ .code = MEDIA_BUS_FMT_YVYU8_2X8, .bpp = 16, .datatype = 0x1e, },
+
+	/* RAW */
+	{ .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, .datatype = 0x2c, },
+	{ .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, .datatype = 0x2c, },
+};
+
+static const struct ub960_format_info *ub960_find_format(u32 code)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(ub960_formats); ++i) {
+		if (ub960_formats[i].code == code)
+			return &ub960_formats[i];
+	}
+
+	return NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * Basic device access
+ */
+
+static int ub960_read(struct ub960_data *priv, u8 reg, u8 *val)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned int v;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = regmap_read(priv->regmap, reg, &v);
+	if (ret)
+		dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+	else
+		*val = v;
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_write(struct ub960_data *priv, u8 reg, u8 val)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = regmap_write(priv->regmap, reg, val);
+	if (ret)
+		dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_update_bits(struct ub960_data *priv, u8 reg, u8 mask, u8 val)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = regmap_update_bits(priv->regmap, reg, mask, val);
+	if (ret)
+		dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int _ub960_rxport_select(struct ub960_data *priv, u8 nport)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	if (priv->current_read_rxport == nport &&
+	    priv->current_write_rxport_mask == BIT(nport))
+		return 0;
+
+	ret = regmap_write(priv->regmap, UB960_SR_FPD3_PORT_SEL,
+			   (nport << 4) | (1 << nport));
+	if (ret) {
+		dev_err(dev, "%s: cannot select rxport %d (%d)!\n", __func__,
+			nport, ret);
+		return ret;
+	}
+
+	priv->current_read_rxport = nport;
+	priv->current_write_rxport_mask = BIT(nport);
+
+	return 0;
+}
+
+static int ub960_rxport_read(struct ub960_data *priv, u8 nport, u8 reg, u8 *val)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned int v;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	_ub960_rxport_select(priv, nport);
+
+	ret = regmap_read(priv->regmap, reg, &v);
+	if (ret)
+		dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+	else
+		*val = v;
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_rxport_write(struct ub960_data *priv, u8 nport, u8 reg, u8 val)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	_ub960_rxport_select(priv, nport);
+
+	ret = regmap_write(priv->regmap, reg, val);
+	if (ret)
+		dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_rxport_update_bits(struct ub960_data *priv, u8 nport, u8 reg,
+				    u8 mask, u8 val)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	_ub960_rxport_select(priv, nport);
+
+	ret = regmap_update_bits(priv->regmap, reg, mask, val);
+
+	if (ret)
+		dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int _ub960_csiport_select(struct ub960_data *priv, u8 nport)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	if (priv->current_read_csiport == nport &&
+	    priv->current_write_csiport_mask == BIT(nport))
+		return 0;
+
+	ret = regmap_write(priv->regmap, UB960_SR_CSI_PORT_SEL,
+			   (nport << 4) | (1 << nport));
+	if (ret) {
+		dev_err(dev, "%s: cannot select csi port %d (%d)!\n", __func__,
+			nport, ret);
+		return ret;
+	}
+
+	priv->current_read_csiport = nport;
+	priv->current_write_csiport_mask = BIT(nport);
+
+	return 0;
+}
+
+static int ub960_csiport_read(struct ub960_data *priv, u8 nport, u8 reg,
+			      u8 *val)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned int v;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	_ub960_csiport_select(priv, nport);
+
+	ret = regmap_read(priv->regmap, reg, &v);
+	if (ret)
+		dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+	else
+		*val = v;
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_csiport_write(struct ub960_data *priv, u8 nport, u8 reg,
+			       u8 val)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	_ub960_csiport_select(priv, nport);
+
+	ret = regmap_write(priv->regmap, reg, val);
+	if (ret)
+		dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_csiport_update_bits(struct ub960_data *priv, u8 nport, u8 reg,
+				     u8 mask, u8 val)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	_ub960_csiport_select(priv, nport);
+
+	ret = regmap_update_bits(priv->regmap, reg, mask, val);
+
+	if (ret)
+		dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n",
+			__func__, reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int _ub960_select_ind_reg_block(struct ub960_data *priv, u8 block)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	if (priv->current_indirect_target == block)
+		return 0;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_CTL, block << 2);
+	if (ret) {
+		dev_err(dev, "%s: cannot select indirect target %u (%d)!\n",
+			__func__, block, ret);
+		return ret;
+	}
+
+	priv->current_indirect_target = block;
+
+	return 0;
+}
+
+static int ub960_read_ind(struct ub960_data *priv, u8 block, u8 reg, u8 *val)
+{
+	unsigned int v;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub960_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v);
+	if (ret)
+		goto out;
+
+	*val = v;
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_write_ind(struct ub960_data *priv, u8 block, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub960_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val);
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_write_ind16(struct ub960_data *priv, u8 block, u8 reg, u16 val)
+{
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub960_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val >> 8);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg + 1);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val & 0xff);
+	if (ret)
+		goto out;
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub960_ind_update_bits(struct ub960_data *priv, u8 block, u8 reg,
+				 u8 mask, u8 val)
+{
+	int ret;
+	u32 v;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub960_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v);
+	if (ret)
+		goto out;
+
+	v &= ~mask;
+	v |= val;
+
+	ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, v);
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * I2C-ATR (address translator)
+ */
+
+static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id,
+				   const struct i2c_board_info *info,
+				   const struct i2c_client *client,
+				   u16 *alias_id)
+{
+	struct ub960_data *priv = i2c_atr_get_clientdata(atr);
+	struct ub960_rxport *rxport = priv->rxports[chan_id];
+	struct device *dev = &priv->client->dev;
+	struct atr_alias_table_entry *entry = NULL;
+	unsigned int reg_idx;
+	unsigned int pool_idx;
+	u16 alias;
+	int ret = 0;
+	u8 port_reg_idx_mask = 0;
+
+	dev_dbg(dev, "rx%d: %s\n", chan_id, __func__);
+
+	mutex_lock(&priv->atr_alias_table.lock);
+
+	/*
+	 * Go through the alias table and:
+	 *	1. Look for an unreserved entry
+	 *	2. Construct a bitmask of port's used alias entries
+	 */
+
+	for (pool_idx = 0; pool_idx < priv->atr_alias_table.num_entries; pool_idx++) {
+		struct atr_alias_table_entry *e;
+
+		e = &priv->atr_alias_table.entries[pool_idx];
+
+		if (!entry && !e->reserved)
+			entry = e;
+
+		if (e->reserved && e->nport == rxport->nport)
+			port_reg_idx_mask |= BIT(e->port_reg_idx);
+	}
+
+	if (!entry) {
+		dev_err(dev, "rx%d: alias pool exhausted\n", rxport->nport);
+		ret = -EADDRNOTAVAIL;
+		goto out;
+	}
+
+	if (port_reg_idx_mask == (1 << UB960_MAX_PORT_ALIASES) - 1) {
+		dev_err(dev, "rx%d: all aliases in use\n", rxport->nport);
+		ret = -EADDRNOTAVAIL;
+		goto out;
+	}
+
+	alias = entry->alias_id;
+
+	reg_idx = ffz(port_reg_idx_mask);
+
+	entry->reserved = true;
+	entry->nport = rxport->nport;
+	entry->slave_id = client->addr;
+	entry->port_reg_idx = reg_idx;
+
+	/* Map alias to slave */
+
+	ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ID(reg_idx),
+			   client->addr << 1);
+	ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx),
+			   alias << 1);
+
+	*alias_id = alias; /* tell the atr which alias we chose */
+
+	dev_dbg(dev, "rx%d: client 0x%02x mapped at alias 0x%02x (%s)\n",
+		rxport->nport, client->addr, alias, client->name);
+
+out:
+	mutex_unlock(&priv->atr_alias_table.lock);
+	return ret;
+}
+
+static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id,
+				    const struct i2c_client *client)
+{
+	struct ub960_data *priv = i2c_atr_get_clientdata(atr);
+	struct ub960_rxport *rxport = priv->rxports[chan_id];
+	struct device *dev = &priv->client->dev;
+	struct atr_alias_table_entry *entry;
+	unsigned int reg_idx;
+	unsigned int pool_idx;
+	u16 alias = 0;
+
+	dev_dbg(dev, "rx%d: %s\n", chan_id, __func__);
+
+	mutex_lock(&priv->atr_alias_table.lock);
+
+	/* Find alias mapped to this client */
+
+	for (pool_idx = 0; pool_idx < priv->atr_alias_table.num_entries; pool_idx++) {
+		entry = &priv->atr_alias_table.entries[pool_idx];
+
+		if (entry->reserved && entry->nport == rxport->nport &&
+		    entry->slave_id == client->addr)
+			break;
+	}
+
+	if (pool_idx == priv->atr_alias_table.num_entries) {
+		dev_err(dev, "rx%d: client 0x%02x is not mapped!\n",
+			rxport->nport, client->addr);
+		goto out;
+	}
+
+	alias = entry->alias_id;
+
+	reg_idx = entry->port_reg_idx;
+
+	/* Unmap */
+
+	ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), 0);
+
+	entry->reserved = false;
+
+	dev_dbg(dev, "rx%d: client 0x%02x unmapped from alias 0x%02x (%s)\n",
+		rxport->nport, client->addr, alias, client->name);
+
+out:
+	mutex_unlock(&priv->atr_alias_table.lock);
+}
+
+static const struct i2c_atr_ops ub960_atr_ops = {
+	.attach_client = ub960_atr_attach_client,
+	.detach_client = ub960_atr_detach_client,
+};
+
+/* -----------------------------------------------------------------------------
+ * CSI ports
+ */
+
+static int ub960_parse_dt_txport(struct ub960_data *priv,
+				 const struct device_node *np, u8 nport)
+{
+	struct device *dev = &priv->client->dev;
+	struct ub960_txport *txport;
+	int ret;
+	u64 freq;
+
+	if (priv->txports[nport]) {
+		dev_err(dev, "OF: %s: duplicate tx port\n",
+			of_node_full_name(np));
+		return -EADDRINUSE;
+	}
+
+	txport = kzalloc(sizeof(*txport), GFP_KERNEL);
+	if (!txport)
+		return -ENOMEM;
+
+	txport->priv = priv;
+	txport->nport = nport;
+
+	priv->txports[nport] = txport;
+
+	ret = of_property_count_u32_elems(np, "data-lanes");
+
+	if (ret <= 0) {
+		dev_err(dev, "OF: %s: failed to parse data-lanes: %d\n",
+			of_node_full_name(np), ret);
+		goto err_free_txport;
+	}
+
+	txport->num_data_lanes = ret;
+
+	ret = of_property_count_u64_elems(np, "link-frequencies");
+	if (ret < 0) {
+		dev_err(dev, "OF: %s: failed to parse link-frequencies: %d\n",
+			of_node_full_name(np), ret);
+		goto err_free_txport;
+	}
+
+	if (ret != 1) {
+		dev_err(dev, "OF: %s: link-frequencies must contain a single frequency: %d\n",
+			of_node_full_name(np), ret);
+		goto err_free_txport;
+	}
+
+	ret = of_property_read_u64(np, "link-frequencies", &freq);
+	if (ret) {
+		dev_err(dev, "OF: %s: failed to read link frequency: %d\n",
+			of_node_full_name(np), ret);
+		goto err_free_txport;
+	}
+
+	if (of_property_read_u32(np, "link-frequencies", &priv->tx_data_rate) != 0) {
+		dev_err(dev, "OF: %s: missing \"data-rate\" node\n",
+			of_node_full_name(np));
+		return -EINVAL;
+	}
+
+	priv->tx_link_freq[0] = freq;
+	priv->tx_data_rate = freq * 2;
+
+	if (priv->tx_data_rate != 1600000000 &&
+	    priv->tx_data_rate != 1200000000 &&
+	    priv->tx_data_rate != 800000000 &&
+	    priv->tx_data_rate != 400000000) {
+		dev_err(dev, "OF: %s: invalid \"link-frequencies\" value\n",
+			of_node_full_name(np));
+		return -EINVAL;
+	}
+
+	dev_dbg(dev, "Nominal data rate: %u", priv->tx_data_rate);
+
+	return 0;
+
+err_free_txport:
+	kfree(txport);
+
+	return ret;
+}
+
+static void ub960_csi_handle_events(struct ub960_data *priv, u8 nport)
+{
+	struct device *dev = &priv->client->dev;
+	u8 csi_tx_isr;
+	int ret;
+
+	ret = ub960_csiport_read(priv, nport, UB960_TR_CSI_TX_ISR, &csi_tx_isr);
+
+	if (!ret) {
+		if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR)
+			dev_warn(dev, "TX%u: CSI_SYNC_ERROR\n", nport);
+
+		if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR)
+			dev_warn(dev, "TX%u: CSI_PASS_ERROR\n", nport);
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * RX ports
+ */
+
+static void ub960_rxport_clear_errors(struct ub960_data *priv,
+				      unsigned int nport)
+{
+	u8 v;
+
+	ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v);
+	ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v);
+	ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &v);
+	ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &v);
+
+	ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v);
+	ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v);
+
+	ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v);
+}
+
+static void ub960_clear_rx_errors(struct ub960_data *priv)
+{
+	unsigned int nport;
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport)
+		ub960_rxport_clear_errors(priv, nport);
+}
+
+static int ub960_rxport_get_strobe_pos(struct ub960_data *priv,
+				       unsigned int nport, s8 *strobe_pos)
+{
+	u8 v;
+	u8 clk_delay, data_delay;
+	int ret;
+
+	ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+		       UB960_IR_RX_ANA_STROBE_SET_CLK, &v);
+
+	clk_delay = v & BIT(3) ? 0 : 6;
+
+	ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+		       UB960_IR_RX_ANA_STROBE_SET_DATA, &v);
+
+	data_delay = v & BIT(3) ? 0 : 6;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v);
+	if (ret)
+		return ret;
+
+	clk_delay += v & 0x7;
+
+	ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_1, &v);
+	if (ret)
+		return ret;
+
+	data_delay += v & 0x7;
+
+	*strobe_pos = data_delay - clk_delay;
+
+	return 0;
+}
+
+static void ub960_rxport_set_strobe_pos(struct ub960_data *priv,
+					unsigned int nport, s8 strobe_pos)
+{
+	u8 clk_delay, data_delay;
+
+	if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
+		    strobe_pos > UB960_MAX_MANUAL_STROBE_POS))
+		return;
+
+	clk_delay = BIT(3);
+	data_delay = BIT(3);
+
+	if (strobe_pos < -7)
+		clk_delay = abs(strobe_pos) - 6;
+	else if (strobe_pos > 7)
+		data_delay = strobe_pos - 6;
+	else if (strobe_pos < 0)
+		clk_delay = abs(strobe_pos) | BIT(3);
+	else if (strobe_pos > 0)
+		data_delay = strobe_pos | BIT(3);
+
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+			UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay);
+
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+			UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay);
+}
+
+static void ub960_rxport_set_strobe_range(struct ub960_data *priv,
+					  s8 strobe_min, s8 strobe_max)
+{
+	WARN_ON(strobe_min < UB960_MIN_AEQ_STROBE_POS);
+	WARN_ON(strobe_max > UB960_MAX_AEQ_STROBE_POS);
+
+	strobe_min += 7;
+	strobe_max += 7;
+
+	ub960_write(priv, UB960_XR_SFILTER_CFG,
+		    (u8)strobe_min | ((u8)strobe_max << 4));
+}
+
+static int ub960_rxport_get_eq_level(struct ub960_data *priv,
+				     unsigned int nport, u8 *eq_level)
+{
+	int ret;
+	u8 v;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_STATUS, &v);
+	if (ret)
+		return ret;
+
+	*eq_level = (v & 0x7) + ((v >> 3) & 0x7);
+
+	return 0;
+}
+
+static void ub960_rxport_set_eq_level(struct ub960_data *priv,
+				      unsigned int nport, u8 eq_level)
+{
+	u8 eq_stage_1_select_value, eq_stage_2_select_value;
+	u8 v;
+
+	if (eq_level <= 7) {
+		eq_stage_1_select_value = eq_level;
+		eq_stage_2_select_value = 0;
+	} else {
+		eq_stage_1_select_value = 7;
+		eq_stage_2_select_value = eq_level - 7;
+	}
+
+	WARN_ON(eq_stage_1_select_value > 7);
+	WARN_ON(eq_stage_2_select_value > 7);
+
+	ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
+
+	v &= ~((0x7 << 5) | (0x7 << 1));
+	v |= eq_stage_1_select_value << 5;
+	v |= eq_stage_2_select_value << 1;
+	v |= BIT(0); /* Enable AEQ Bypass */
+
+	ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v);
+}
+
+static void ub960_rxport_set_eq_range(struct ub960_data *priv,
+				      unsigned int nport, u8 eq_min, u8 eq_max)
+{
+	ub960_rxport_write(priv, nport, UB960_RR_AEQ_MIN_MAX,
+			   eq_min | (eq_max << 4));
+
+	/* Enable AEQ min setting */
+	ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_CTL2, BIT(2),
+				 BIT(2));
+}
+
+static void ub960_rxport_config_eq(struct ub960_data *priv, unsigned int nport)
+{
+	struct ub960_rxport *rxport = priv->rxports[nport];
+
+	/* We also set common settings here. Should be moved elsewhere. */
+
+	if (priv->strobe.manual) {
+		/* Disable AEQ_SFILTER_EN */
+		ub960_update_bits(priv, UB960_XR_AEQ_CTL1, BIT(0), 0);
+	} else {
+		/* Enable SFILTER and error control */
+		ub960_write(priv, UB960_XR_AEQ_CTL1, (0x7 << 4) | BIT(0));
+
+		/* Set AEQ strobe range */
+		ub960_rxport_set_strobe_range(priv, priv->strobe.min,
+					      priv->strobe.max);
+	}
+
+	/* The rest are port specific */
+
+	if (priv->strobe.manual)
+		ub960_rxport_set_strobe_pos(priv, nport, rxport->eq.strobe_pos);
+	else
+		ub960_rxport_set_strobe_pos(priv, nport, 0);
+
+	if (rxport->eq.manual_eq) {
+		ub960_rxport_set_eq_level(priv, nport,
+					  rxport->eq.manual.eq_level);
+
+		/* Enable AEQ Bypass */
+		ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
+					 BIT(0), BIT(0));
+	} else {
+		ub960_rxport_set_eq_range(priv, nport,
+					  rxport->eq.aeq.eq_level_min,
+					  rxport->eq.aeq.eq_level_max);
+
+		/* Disable AEQ Bypass */
+		ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
+					 BIT(0), 0);
+	}
+}
+
+static int ub960_rxport_link_ok(struct ub960_data *priv, unsigned int nport,
+				bool *ok)
+{
+	u8 rx_port_sts1, rx_port_sts2;
+	unsigned int parity_errors;
+	u8 csi_rx_sts;
+	u8 csi_err_cnt;
+	u8 v1, v2;
+	u8 bcc_sts;
+	int ret;
+	bool errors;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1,
+				&rx_port_sts1);
+	if (ret)
+		return ret;
+
+	if (!(rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS)) {
+		*ok = false;
+		return 0;
+	}
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2,
+				&rx_port_sts2);
+	if (ret)
+		return ret;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &csi_rx_sts);
+	if (ret)
+		return ret;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER,
+				&csi_err_cnt);
+	if (ret)
+		return ret;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &bcc_sts);
+	if (ret)
+		return ret;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1);
+	if (ret)
+		return ret;
+
+	ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2);
+	if (ret)
+		return ret;
+
+	parity_errors = (v1 << 8) | v2;
+
+	errors = (rx_port_sts1 & 0x2c) || (rx_port_sts2 & 0x20) ||
+		 (bcc_sts & 0x3f) || (csi_rx_sts & 0xf) || csi_err_cnt ||
+		 parity_errors;
+
+	*ok = !errors;
+
+	return 0;
+}
+
+/*
+ * Wait for the RX ports to lock, have no errors and have stable strobe position
+ * and EQ level.
+ */
+static int ub960_rxport_wait_locks(struct ub960_data *priv,
+				   unsigned long port_mask,
+				   unsigned int *lock_mask)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned long timeout;
+	unsigned int link_ok_mask;
+	unsigned int missing;
+	unsigned int loops;
+	u8 nport;
+	int ret;
+
+	if (port_mask == 0)
+		return 0;
+
+	if (port_mask >= BIT(priv->hw_data->num_rxports))
+		return -EINVAL;
+
+	timeout = jiffies + msecs_to_jiffies(1000);
+	loops = 0;
+	link_ok_mask = 0;
+
+	while (time_before(jiffies, timeout)) {
+		missing = 0;
+
+		for_each_set_bit(nport, &port_mask,
+				 priv->hw_data->num_rxports) {
+			struct ub960_rxport *rxport = priv->rxports[nport];
+			bool ok;
+
+			if (!rxport)
+				continue;
+
+			ret = ub960_rxport_link_ok(priv, nport, &ok);
+			if (ret)
+				return ret;
+
+			if (!ok || !(link_ok_mask & BIT(nport)))
+				missing++;
+
+			if (ok)
+				link_ok_mask |= BIT(nport);
+			else
+				link_ok_mask &= ~BIT(nport);
+		}
+
+		loops++;
+
+		if (missing == 0)
+			break;
+
+		msleep(50);
+	}
+
+	if (lock_mask)
+		*lock_mask = link_ok_mask;
+
+	dev_dbg(dev, "Wait locks done in %u loops\n", loops);
+	for_each_set_bit(nport, &port_mask, priv->hw_data->num_rxports) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+		s8 strobe_pos, eq_level;
+		u8 v1, v2;
+
+		if (!rxport)
+			continue;
+
+		if (!(link_ok_mask & BIT(nport))) {
+			dev_dbg(dev, "\trx%u: not locked\n", nport);
+			continue;
+		}
+
+		ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);
+
+		ret = ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos);
+		if (ret)
+			return ret;
+
+		ret = ub960_rxport_get_eq_level(priv, nport, &eq_level);
+		if (ret)
+			return ret;
+
+		dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
+			nport, strobe_pos, eq_level,
+			v1 * 1000000 + v2 * 1000000 / 256);
+	}
+
+	return 0;
+}
+
+static int ub960_init_atr(struct ub960_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct i2c_adapter *parent_adap = priv->client->adapter;
+
+	priv->atr = i2c_atr_new(parent_adap, dev, &ub960_atr_ops,
+				priv->hw_data->num_rxports);
+	if (IS_ERR(priv->atr))
+		return PTR_ERR(priv->atr);
+
+	i2c_atr_set_clientdata(priv->atr, priv);
+
+	return 0;
+}
+
+static void ub960_uninit_atr(struct ub960_data *priv)
+{
+	i2c_atr_delete(priv->atr);
+	priv->atr = NULL;
+}
+
+static unsigned long ub960_calc_bc_clk_rate_ub960(struct ub960_data *priv,
+						  struct ub960_rxport *rxport)
+{
+	unsigned int mult;
+	unsigned int div;
+
+	switch (rxport->rx_mode) {
+	case RXPORT_MODE_RAW10:
+	case RXPORT_MODE_RAW12_HF:
+	case RXPORT_MODE_RAW12_LF:
+		mult = 1;
+		div = 10;
+		break;
+
+	case RXPORT_MODE_CSI2_SYNC:
+		mult = 2;
+		div = 1;
+		break;
+
+	case RXPORT_MODE_CSI2_ASYNC:
+		mult = 2;
+		div = 5;
+		break;
+
+	default:
+		WARN_ON(true);
+		return 0;
+	}
+
+	return clk_get_rate(priv->refclk) * mult / div;
+}
+
+static unsigned long ub960_calc_bc_clk_rate_ub9702(struct ub960_data *priv,
+						   struct ub960_rxport *rxport)
+{
+	switch (rxport->rx_mode) {
+	case RXPORT_MODE_RAW10:
+	case RXPORT_MODE_RAW12_HF:
+	case RXPORT_MODE_RAW12_LF:
+		return 2359400;
+
+	case RXPORT_MODE_CSI2_SYNC:
+		return 47187500;
+
+	case RXPORT_MODE_CSI2_ASYNC:
+		return 9437500;
+
+	default:
+		WARN_ON(true);
+		return 0;
+	}
+}
+
+static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 nport)
+{
+	struct ub960_rxport *rxport = priv->rxports[nport];
+	struct device *dev = &priv->client->dev;
+	struct ds90ub9xx_platform_data *ser_pdata = &rxport->ser_platform_data;
+	struct i2c_board_info ser_info = {
+		.of_node = rxport->remote_of_node,
+		.platform_data = ser_pdata,
+	};
+
+	ser_pdata->port = nport;
+	ser_pdata->atr = priv->atr;
+	if (priv->hw_data->ub9702)
+		ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub9702(priv, rxport);
+	else
+		ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub960(priv, rxport);
+
+	/*
+	 * Adding the serializer under rxport->adap would be cleaner, but it
+	 * would need tweaks to bypass the alias table. Adding to the
+	 * upstream adapter is way simpler.
+	 */
+	ser_info.addr = rxport->ser_alias;
+	rxport->ser_client =
+		i2c_new_client_device(priv->client->adapter, &ser_info);
+	if (!rxport->ser_client) {
+		dev_err(dev, "rx%d: cannot add %s i2c device", nport,
+			ser_info.type);
+		return -EIO;
+	}
+
+	dev_dbg(dev, "rx%d: remote serializer at alias 0x%02x (%u-%04x)\n",
+		nport, rxport->ser_client->addr,
+		rxport->ser_client->adapter->nr, rxport->ser_client->addr);
+
+	return 0;
+}
+
+static void ub960_rxport_remove_serializer(struct ub960_data *priv, u8 nport)
+{
+	struct ub960_rxport *rxport = priv->rxports[nport];
+
+	i2c_unregister_device(rxport->ser_client);
+	rxport->ser_client = NULL;
+}
+
+/* Add serializer i2c devices for all initialized ports */
+static int ub960_rxport_add_serializers(struct ub960_data *priv)
+{
+	unsigned int nport;
+	int ret;
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+
+		if (!rxport)
+			continue;
+
+		ret = ub960_rxport_add_serializer(priv, nport);
+		if (ret)
+			goto err_remove_sers;
+	}
+
+	return 0;
+
+err_remove_sers:
+	for (; nport > 0; --nport) {
+		struct ub960_rxport *rxport = priv->rxports[nport - 1];
+
+		if (!rxport)
+			continue;
+
+		rxport = priv->rxports[nport - 1];
+		if (!rxport)
+			continue;
+
+		ub960_rxport_remove_serializer(priv, nport - 1);
+	}
+
+	return ret;
+}
+
+static void ub960_rxport_remove_serializers(struct ub960_data *priv)
+{
+	unsigned int nport;
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+
+		if (!rxport)
+			continue;
+
+		ub960_rxport_remove_serializer(priv, nport);
+	}
+}
+
+static void ub960_init_tx_port(struct ub960_data *priv,
+			       struct ub960_txport *txport)
+{
+	unsigned int nport = txport->nport;
+	u8 csi_ctl = 0;
+
+	/*
+	 * From the datasheet: "initial CSI Skew-Calibration
+	 * sequence [...] should be set when operating at 1.6 Gbps"
+	 */
+	if (priv->tx_data_rate == 1600000000)
+		csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN;
+
+	csi_ctl |= (4 - txport->num_data_lanes) << 4;
+
+	ub960_csiport_write(priv, nport, UB960_TR_CSI_CTL, csi_ctl);
+}
+
+static int ub960_init_tx_ports(struct ub960_data *priv)
+{
+	unsigned int nport;
+	u8 speed_select;
+	u8 pll_div;
+
+	/* TX ports */
+
+	switch (priv->tx_data_rate) {
+	case 1600000000:
+	default:
+		speed_select = 0;
+		pll_div = 0x10;
+		break;
+	case 1200000000:
+		speed_select = 1;
+		break;
+	case 800000000:
+		speed_select = 2;
+		pll_div = 0x10;
+		break;
+	case 400000000:
+		speed_select = 3;
+		pll_div = 0x10;
+		break;
+	}
+
+	ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select);
+
+	if (priv->hw_data->ub9702) {
+		ub960_write(priv, UB960_SR_CSI_PLL_DIV, pll_div);
+
+		switch (priv->tx_data_rate) {
+		case 1600000000:
+		default:
+			ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0x80);
+			ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4B, 0x2A);
+			break;
+		case 800000000:
+			ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0x90);
+			ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4F, 0x2A);
+			ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4B, 0x2A);
+			break;
+		case 400000000:
+			ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0xA0);
+			break;
+		}
+	}
+
+	for (nport = 0; nport < priv->hw_data->num_txports; nport++) {
+		struct ub960_txport *txport = priv->txports[nport];
+
+		if (!txport)
+			continue;
+
+		ub960_init_tx_port(priv, txport);
+	}
+
+	return 0;
+}
+
+static void ub960_init_rx_port_ub960(struct ub960_data *priv,
+				     struct ub960_rxport *rxport)
+{
+	unsigned int nport = rxport->nport;
+	u32 bc_freq_val;
+
+	/*
+	 * Back channel frequency select.
+	 * Override FREQ_SELECT from the strap.
+	 * 0 - 2.5 Mbps (DS90UB913A-Q1 / DS90UB933-Q1)
+	 * 2 - 10 Mbps
+	 * 6 - 50 Mbps (DS90UB953-Q1)
+	 *
+	 * Note that changing this setting will result in some errors on the back
+	 * channel for a short period of time.
+	 */
+
+	switch (rxport->rx_mode) {
+	default:
+		WARN_ON(true);
+		fallthrough;
+
+	case RXPORT_MODE_RAW10:
+	case RXPORT_MODE_RAW12_HF:
+	case RXPORT_MODE_RAW12_LF:
+		bc_freq_val = 0;
+		break;
+
+	case RXPORT_MODE_CSI2_ASYNC:
+		bc_freq_val = 2;
+		break;
+
+	case RXPORT_MODE_CSI2_SYNC:
+		bc_freq_val = 6;
+		break;
+	}
+
+	ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7,
+				 bc_freq_val);
+
+	switch (rxport->rx_mode) {
+	default:
+		WARN_ON(true);
+		fallthrough;
+
+	case RXPORT_MODE_RAW10:
+		/* FPD3_MODE = RAW10 Mode (DS90UB913A-Q1 / DS90UB933-Q1 compatible) */
+		ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3,
+					 0x3);
+
+		/*
+		 * RAW10_8BIT_CTL = 0b11 : 8-bit processing using lower 8 bits
+		 * 0b10 : 8-bit processing using upper 8 bits
+		 */
+		ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2,
+					 0x3 << 6, 0x2 << 6);
+
+		break;
+
+	case RXPORT_MODE_CSI2_SYNC:
+	case RXPORT_MODE_CSI2_ASYNC:
+		/* CSI-2 Mode (DS90UB953-Q1 compatible) */
+		ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3,
+					 0x0);
+
+		break;
+	}
+
+	/* LV_POLARITY & FV_POLARITY */
+	ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, 0x1);
+
+	/* Enable all interrupt sources from this port */
+	ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07);
+	ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f);
+
+	/* Enable I2C_PASS_THROUGH */
+	ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG,
+				 UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH,
+				 UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH);
+
+	/* Enable I2C communication to the serializer via the alias addr */
+	ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID,
+			   rxport->ser_alias << 1);
+
+	/* Configure EQ related settings */
+	ub960_rxport_config_eq(priv, nport);
+
+	/* Enable RX port */
+	ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport));
+}
+
+static void ub960_init_rx_port_ub9702_fpd3(struct ub960_data *priv,
+					   struct ub960_rxport *rxport)
+{
+	unsigned int nport = rxport->nport;
+	u8 bc_freq_val;
+	u8 fpd_func_mode;
+
+	switch (rxport->rx_mode) {
+	case RXPORT_MODE_RAW10:
+		bc_freq_val = 0;
+		fpd_func_mode = 5;
+		break;
+
+	case RXPORT_MODE_RAW12_HF:
+		bc_freq_val = 0;
+		fpd_func_mode = 4;
+		break;
+
+	case RXPORT_MODE_RAW12_LF:
+		bc_freq_val = 0;
+		fpd_func_mode = 6;
+		break;
+
+	case RXPORT_MODE_CSI2_SYNC:
+		bc_freq_val = 6;
+		fpd_func_mode = 2;
+		break;
+
+	case RXPORT_MODE_CSI2_ASYNC:
+		bc_freq_val = 2;
+		fpd_func_mode = 2;
+		break;
+
+	default:
+		WARN_ON(true);
+		break;
+	}
+
+	ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7,
+				 bc_freq_val);
+	ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, fpd_func_mode);
+
+	/* set serdes_eq_mode = 1 */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xA8, 0x80);
+
+	/* enable serdes driver */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x0D, 0x7F);
+
+	/* set serdes_eq_offset=4 */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04);
+
+	/* init default serdes_eq_max in 0xA9 */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xA9, 0x23);
+
+	/* init serdes_eq_min in 0xAA */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xAA, 0);
+
+	/* serdes_driver_ctl2 control: DS90UB953-Q1/DS90UB933-Q1/DS90UB913A-Q1 */
+	ub960_ind_update_bits(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1b,
+			      BIT(3), BIT(3));
+
+	/* RX port to half-rate */
+	ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2),
+			  1 << (nport * 2));
+}
+
+static void ub960_init_rx_port_ub9702_fpd4_aeq(struct ub960_data *priv,
+					       struct ub960_rxport *rxport)
+{
+	unsigned int nport = rxport->nport;
+	bool first_time_power_up = true;
+
+	if (first_time_power_up) {
+		u8 v;
+
+		/* AEQ init */
+		ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2C, &v);
+
+		ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, v);
+		ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, v + 1);
+
+		ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x00);
+	}
+
+	/* enable serdes_eq_ctl2 */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x9E, 0x00);
+
+	/* enable serdes_eq_ctl1 */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x90, 0x40);
+
+	/* enable serdes_eq_en */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2E, 0x40);
+
+	/* disable serdes_eq_override */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xF0, 0x00);
+
+	/* disable serdes_gain_override */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x71, 0x00);
+}
+
+static void ub960_init_rx_port_ub9702_fpd4(struct ub960_data *priv,
+					   struct ub960_rxport *rxport)
+{
+	unsigned int nport = rxport->nport;
+	u8 bc_freq_val;
+
+	switch (rxport->rx_mode) {
+	case RXPORT_MODE_RAW10:
+		bc_freq_val = 0;
+		break;
+
+	case RXPORT_MODE_RAW12_HF:
+		bc_freq_val = 0;
+		break;
+
+	case RXPORT_MODE_RAW12_LF:
+		bc_freq_val = 0;
+		break;
+
+	case RXPORT_MODE_CSI2_SYNC:
+		bc_freq_val = 6;
+		break;
+
+	case RXPORT_MODE_CSI2_ASYNC:
+		bc_freq_val = 2;
+		break;
+
+	default:
+		WARN_ON(true);
+		break;
+	}
+
+	ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7,
+				 bc_freq_val);
+
+	/* FPD4 Sync Mode */
+	ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, 0);
+
+	/* add serdes_eq_offset of 4 */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04);
+
+	/* FPD4 serdes_start_eq in 0x27: assign default */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, 0x0);
+	/* FPD4 serdes_end_eq in 0x28: assign default */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, 0x23);
+
+	/* set serdes_driver_mode into FPD IV mode */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x04, 0x00);
+	/* set FPD PBC drv into FPD IV mode */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1B, 0x00);
+
+	/* set serdes_system_init to 0x2f */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x21, 0x2f);
+	/* set serdes_system_rst in reset mode */
+	ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0xC1);
+
+	/* RX port to 7.55G mode */
+	ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2),
+			  0 << (nport * 2));
+
+	ub960_init_rx_port_ub9702_fpd4_aeq(priv, rxport);
+}
+
+static void ub960_init_rx_port_ub9702(struct ub960_data *priv,
+				      struct ub960_rxport *rxport)
+{
+	unsigned int nport = rxport->nport;
+
+	if (rxport->cdr_mode == RXPORT_CDR_FPD3)
+		ub960_init_rx_port_ub9702_fpd3(priv, rxport);
+	else /* RXPORT_CDR_FPD4 */
+		ub960_init_rx_port_ub9702_fpd4(priv, rxport);
+
+	switch (rxport->rx_mode) {
+	default:
+		WARN_ON(true);
+		fallthrough;
+
+	case RXPORT_MODE_RAW10:
+		/*
+		 * RAW10_8BIT_CTL = 0b11 : 8-bit processing using lower 8 bits
+		 * 0b10 : 8-bit processing using upper 8 bits
+		 */
+		ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2,
+					 0x3 << 6, 0x2 << 6);
+
+		break;
+
+	case RXPORT_MODE_CSI2_SYNC:
+	case RXPORT_MODE_CSI2_ASYNC:
+
+		break;
+	}
+
+	/* LV_POLARITY & FV_POLARITY */
+	ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, 0x1);
+
+	/* Enable all interrupt sources from this port */
+	ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07);
+	ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f);
+
+	/* Enable I2C_PASS_THROUGH */
+	ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG,
+				 UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH,
+				 UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH);
+
+	/* Enable I2C communication to the serializer via the alias addr */
+	ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID,
+			   rxport->ser_alias << 1);
+
+	/* Enable RX port */
+	ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport));
+
+	if (rxport->cdr_mode == RXPORT_CDR_FPD4) {
+		/* unreset 960 AEQ */
+		ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0x41);
+	}
+}
+
+static int ub960_init_rx_ports(struct ub960_data *priv)
+{
+	unsigned int nport;
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; nport++) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+
+		if (!rxport)
+			continue;
+
+		if (priv->hw_data->ub9702)
+			ub960_init_rx_port_ub9702(priv, rxport);
+		else
+			ub960_init_rx_port_ub960(priv, rxport);
+	}
+
+	return 0;
+}
+
+static void ub960_rxport_handle_events(struct ub960_data *priv, u8 nport)
+{
+	struct device *dev = &priv->client->dev;
+	u8 rx_port_sts1;
+	u8 rx_port_sts2;
+	u8 csi_rx_sts;
+	u8 bcc_sts;
+	int ret = 0;
+
+	/* Read interrupts (also clears most of them) */
+	if (!ret)
+		ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1,
+					&rx_port_sts1);
+	if (!ret)
+		ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2,
+					&rx_port_sts2);
+	if (!ret)
+		ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS,
+					&csi_rx_sts);
+	if (!ret)
+		ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS,
+					&bcc_sts);
+
+	if (ret)
+		return;
+
+	if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PARITY_ERROR) {
+		u8 v1, v2;
+
+		ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2);
+		dev_err(dev, "RX%u parity errors: %u\n", nport, (v1 << 8) | v2);
+	}
+
+	if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR)
+		dev_err(dev, "RX%u BCC CRC error\n", nport);
+
+	if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR)
+		dev_err(dev, "RX%u BCC SEQ error\n", nport);
+
+	if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE)
+		dev_err(dev, "RX%u line length unstable\n", nport);
+
+	if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR)
+		dev_err(dev, "RX%u FPD3 encode error\n", nport);
+
+	if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_BUFFER_ERROR)
+		dev_err(dev, "RX%u buffer error\n", nport);
+
+	if (csi_rx_sts)
+		dev_err(dev, "RX%u CSI error: %#02x\n", nport, csi_rx_sts);
+
+	if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC1_ERR)
+		dev_err(dev, "RX%u CSI ECC1 error\n", nport);
+
+	if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC2_ERR)
+		dev_err(dev, "RX%u CSI ECC2 error\n", nport);
+
+	if (csi_rx_sts & UB960_RR_CSI_RX_STS_CKSUM_ERR)
+		dev_err(dev, "RX%u CSI checksum error\n", nport);
+
+	if (csi_rx_sts & UB960_RR_CSI_RX_STS_LENGTH_ERR)
+		dev_err(dev, "RX%u CSI length error\n", nport);
+
+	if (bcc_sts)
+		dev_err(dev, "RX%u BCC error: %#02x\n", nport, bcc_sts);
+
+	if (bcc_sts & UB960_RR_BCC_STATUS_RESP_ERR)
+		dev_err(dev, "RX%u BCC response error", nport);
+
+	if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_TO)
+		dev_err(dev, "RX%u BCC slave timeout", nport);
+
+	if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_ERR)
+		dev_err(dev, "RX%u BCC slave error", nport);
+
+	if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_TO)
+		dev_err(dev, "RX%u BCC master timeout", nport);
+
+	if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_ERR)
+		dev_err(dev, "RX%u BCC master error", nport);
+
+	if (bcc_sts & UB960_RR_BCC_STATUS_SEQ_ERROR)
+		dev_err(dev, "RX%u BCC sequence error", nport);
+
+	if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_CHG) {
+		u8 v1, v2;
+
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2);
+		dev_dbg(dev, "RX%u line len changed: %u\n", nport,
+			(v1 << 8) | v2);
+	}
+
+	if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_CNT_CHG) {
+		u8 v1, v2;
+
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2);
+		dev_dbg(dev, "RX%u line count changed: %u\n", nport,
+			(v1 << 8) | v2);
+	}
+
+	if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS_CHG) {
+		dev_dbg(dev, "RX%u: %s, %s, %s, %s\n", nport,
+			(rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS) ? "locked" : "unlocked",
+			(rx_port_sts1 & UB960_RR_RX_PORT_STS1_PORT_PASS) ? "passed" : "not passed",
+			(rx_port_sts2 & UB960_RR_RX_PORT_STS2_CABLE_FAULT) ? "no clock" : "clock ok",
+			(rx_port_sts2 & UB960_RR_RX_PORT_STS2_FREQ_STABLE) ? "stable freq" : "unstable freq");
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2
+ */
+
+static void ub960_get_vc_maps(struct ub960_data *priv,
+			      struct v4l2_subdev_state *state, u8 *vc)
+{
+	const struct v4l2_subdev_krouting *routing = &state->routing;
+	u8 cur_vc[UB960_MAX_TX_NPORTS] = { 0 };
+	u8 handled_mask = 0;
+	unsigned int i;
+
+	for (i = 0; i < routing->num_routes; ++i) {
+		struct v4l2_subdev_route *route = &routing->routes[i];
+		unsigned int rx, tx;
+
+		rx = ub960_pad_to_port(priv, route->sink_pad);
+
+		if (BIT(rx) & handled_mask)
+			continue;
+
+		tx = ub960_pad_to_port(priv, route->source_pad);
+
+		vc[rx] = cur_vc[tx]++;
+		handled_mask |= BIT(rx);
+	}
+}
+
+static int ub960_enable_tx_port(struct ub960_data *priv, unsigned int nport)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "enable TX port %u\n", nport);
+
+	ub960_csiport_update_bits(priv, nport, UB960_TR_CSI_CTL,
+				  UB960_TR_CSI_CTL_CSI_ENABLE,
+				  UB960_TR_CSI_CTL_CSI_ENABLE);
+
+	return 0;
+}
+
+static void ub960_disable_tx_port(struct ub960_data *priv, unsigned int nport)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "disable TX port %u\n", nport);
+
+	ub960_csiport_update_bits(priv, nport, UB960_TR_CSI_CTL,
+				  UB960_TR_CSI_CTL_CSI_ENABLE, 0);
+}
+
+static int ub960_enable_rx_port(struct ub960_data *priv, unsigned int nport)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "enable RX port %u\n", nport);
+
+	/* Enable forwarding */
+	ub960_update_bits(priv, UB960_SR_FWD_CTL1, BIT(4 + nport), 0);
+
+	return 0;
+}
+
+static void ub960_disable_rx_port(struct ub960_data *priv, unsigned int nport)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "disable RX port %u\n", nport);
+
+	/* Disable forwarding */
+	ub960_update_bits(priv, UB960_SR_FWD_CTL1, BIT(4 + nport),
+			  BIT(4 + nport));
+}
+
+static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
+					       struct v4l2_subdev_state *state)
+{
+	const struct v4l2_subdev_krouting *routing = &state->routing;
+	unsigned int i;
+	unsigned int nport;
+	u8 fwd_ctl;
+	struct {
+		u32 num_streams;
+		u8 pixel_dt;
+		u8 meta_dt;
+		u32 meta_lines;
+		u32 tx_port;
+	} rx_data[UB960_MAX_RX_NPORTS] = { 0 };
+	u8 vc_map[UB960_MAX_RX_NPORTS] = { 0 };
+
+	ub960_get_vc_maps(priv, state, vc_map);
+
+	for (i = 0; i < routing->num_routes; ++i) {
+		struct v4l2_subdev_route *route = &routing->routes[i];
+		struct ub960_rxport *rxport;
+		struct ub960_txport *txport;
+		struct v4l2_mbus_framefmt *fmt;
+		const struct ub960_format_info *ub960_fmt;
+		unsigned int nport;
+
+		nport = ub960_pad_to_port(priv, route->sink_pad);
+
+		rxport = priv->rxports[nport];
+		if (!rxport)
+			return -EINVAL;
+
+		txport = priv->txports[ub960_pad_to_port(priv, route->source_pad)];
+		if (!txport)
+			return -EINVAL;
+
+		rx_data[nport].tx_port = ub960_pad_to_port(priv, route->source_pad);
+
+		rx_data[nport].num_streams++;
+
+		/* For the rest, we are only interested in parallel busses */
+		if (rxport->rx_mode == RXPORT_MODE_CSI2_SYNC ||
+		    rxport->rx_mode == RXPORT_MODE_CSI2_ASYNC)
+			continue;
+
+		if (rx_data[nport].num_streams > 2)
+			return -EPIPE;
+
+		fmt = v4l2_subdev_state_get_stream_format(state, route->sink_pad,
+							  route->sink_stream);
+		if (!fmt)
+			return -EPIPE;
+
+		ub960_fmt = ub960_find_format(fmt->code);
+		if (!ub960_fmt)
+			return -EPIPE;
+
+		if (ub960_fmt->meta) {
+			if (fmt->height > 3) {
+				dev_err(&priv->client->dev,
+					"Unsupported metadata height %u\n",
+					fmt->height);
+				return -EPIPE;
+			}
+
+			rx_data[nport].meta_dt = ub960_fmt->datatype;
+			rx_data[nport].meta_lines = fmt->height;
+		} else {
+			rx_data[nport].pixel_dt = ub960_fmt->datatype;
+		}
+	}
+
+	/* Configure RX ports */
+
+	fwd_ctl = 0;
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+		u8 vc = vc_map[nport];
+
+		if (rx_data[nport].num_streams == 0)
+			continue;
+
+		switch (rxport->rx_mode) {
+		default:
+			WARN_ON(true);
+			fallthrough;
+
+		case RXPORT_MODE_RAW10:
+			ub960_rxport_write(priv, nport, UB960_RR_RAW10_ID,
+					   rx_data[nport].pixel_dt | (vc << 6));
+
+			ub960_rxport_write(priv, rxport->nport,
+				UB960_RR_RAW_EMBED_DTYPE,
+				(rx_data[nport].meta_lines << 6) | rx_data[nport].meta_dt);
+
+			break;
+
+		case RXPORT_MODE_CSI2_SYNC:
+		case RXPORT_MODE_CSI2_ASYNC:
+			if (!priv->hw_data->ub9702) {
+				/* Map all VCs from this port to the same VC */
+				ub960_rxport_write(priv, nport, UB960_RR_CSI_VC_MAP,
+						   (vc << 6) | (vc << 4) |
+							   (vc << 2) | (vc << 0));
+			} else {
+				unsigned int i;
+
+				/* Map all VCs from this port to VC(nport) */
+				for (i = 0; i < 8; ++i)
+					ub960_rxport_write(priv, nport,
+						UB960_RR_VC_ID_MAP(i), nport);
+			}
+
+			break;
+		}
+
+		/* Forwarding */
+
+		fwd_ctl |= BIT(4 + nport); /* forward disable */
+
+		if (rx_data[nport].tx_port == 1)
+			fwd_ctl |= BIT(nport); /* forward to TX1 */
+		else
+			fwd_ctl &= ~BIT(nport); /* forward to TX0 */
+	}
+
+	ub960_write(priv, UB960_SR_FWD_CTL1, fwd_ctl);
+
+	return 0;
+}
+
+static void ub960_update_streaming_status(struct ub960_data *priv)
+{
+	unsigned int i;
+	bool streaming = false;
+
+	for (i = 0; i < UB960_MAX_NPORTS; ++i) {
+		if (priv->stream_enable_mask[i]) {
+			streaming = true;
+			break;
+		}
+	}
+
+	priv->streaming = streaming;
+}
+
+static int ub960_enable_streams(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state, u32 source_pad,
+				u64 source_streams_mask)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+	struct device *dev = &priv->client->dev;
+	const struct v4l2_subdev_krouting *routing;
+	unsigned int source_stream;
+	int ret;
+	u64 sink_streams[UB960_MAX_RX_NPORTS] = { 0 };
+	unsigned int nport;
+	unsigned int failed_port;
+
+	dev_dbg(dev, "Enable streams %u:%#llx\n", source_pad,
+		source_streams_mask);
+
+	if (priv->stream_enable_mask[source_pad] & source_streams_mask) {
+		dev_err(dev,
+			"cannot enable already enabled streams on pad %u mask %#llx\n",
+			source_pad, source_streams_mask);
+		return -EBUSY;
+	}
+
+	routing = &state->routing;
+
+	if (!priv->streaming) {
+		dev_dbg(dev, "Prepare for streaming\n");
+		ret = ub960_configure_ports_for_streaming(priv, state);
+		if (ret)
+			return ret;
+	}
+
+	/* Enable TX port if not yet enabled */
+	if (!priv->stream_enable_mask[source_pad]) {
+		ret = ub960_enable_tx_port(priv,
+					   ub960_pad_to_port(priv, source_pad));
+		if (ret)
+			return ret;
+	}
+
+	priv->stream_enable_mask[source_pad] |= source_streams_mask;
+
+	/* Collect sink streams per pad which we need to enable */
+	for (source_stream = 0; source_stream < sizeof(source_streams_mask) * 8;
+	     ++source_stream) {
+		struct v4l2_subdev_route *route;
+
+		if (!(source_streams_mask & BIT_ULL(source_stream)))
+			continue;
+
+		for_each_active_route(routing, route) {
+			if (!(route->source_pad == source_pad) ||
+			    !(route->source_stream == source_stream))
+				continue;
+
+			nport = ub960_pad_to_port(priv, route->sink_pad);
+
+			sink_streams[nport] |= BIT_ULL(route->sink_stream);
+		}
+	}
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct v4l2_subdev *source_sd;
+		struct media_pad *source_sd_pad;
+
+		if (!sink_streams[nport])
+			continue;
+
+		/* Enable the RX port if not yet enabled */
+		if (!priv->stream_enable_mask[nport]) {
+			ret = ub960_enable_rx_port(priv, nport);
+			if (ret) {
+				failed_port = nport;
+				goto err;
+			}
+		}
+
+		priv->stream_enable_mask[nport] |= sink_streams[nport];
+
+		dev_dbg(dev, "Enable RX port %u streams %#llx\n", nport,
+			sink_streams[nport]);
+
+		source_sd_pad = media_pad_remote_pad_first(&priv->pads[nport]);
+		source_sd = priv->rxports[nport]->sd;
+
+		ret = v4l2_subdev_enable_streams(
+			source_sd, source_sd_pad->index, sink_streams[nport]);
+		if (ret) {
+			priv->stream_enable_mask[nport] &= ~sink_streams[nport];
+
+			if (!priv->stream_enable_mask[nport])
+				ub960_disable_rx_port(priv, nport);
+
+			failed_port = nport;
+			goto err;
+		}
+	}
+
+	priv->streaming = true;
+
+	return 0;
+
+err:
+	for (nport = 0; nport < failed_port; ++nport) {
+		struct v4l2_subdev *source_sd;
+		struct media_pad *source_sd_pad;
+
+		if (!sink_streams[nport])
+			continue;
+
+		dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport,
+			sink_streams[nport]);
+
+		source_sd_pad = media_pad_remote_pad_first(&priv->pads[nport]);
+		source_sd = priv->rxports[nport]->sd;
+
+		ret = v4l2_subdev_disable_streams(
+			source_sd, source_sd_pad->index, sink_streams[nport]);
+		WARN_ON(ret);
+
+		priv->stream_enable_mask[nport] &= ~sink_streams[nport];
+
+		/* Disable RX port if no active streams */
+		if (!priv->stream_enable_mask[nport])
+			ub960_disable_rx_port(priv, nport);
+	}
+
+	priv->stream_enable_mask[source_pad] &= ~source_streams_mask;
+
+	if (!priv->stream_enable_mask[source_pad])
+		ub960_disable_tx_port(priv,
+				      ub960_pad_to_port(priv, source_pad));
+
+	ub960_update_streaming_status(priv);
+
+	return ret;
+}
+
+static int ub960_disable_streams(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state,
+				 u32 source_pad, u64 source_streams_mask)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+	struct device *dev = &priv->client->dev;
+	const struct v4l2_subdev_krouting *routing;
+	int ret;
+	unsigned int source_stream;
+	u64 sink_streams[UB960_MAX_RX_NPORTS] = { 0 };
+	unsigned int nport;
+
+	dev_dbg(dev, "Disable streams %u:%#llx\n", source_pad,
+		source_streams_mask);
+
+	if ((priv->stream_enable_mask[source_pad] & source_streams_mask) != source_streams_mask) {
+		dev_err(dev,
+			"cannot disable already disabled streams on pad %u mask %#llx\n",
+			source_pad, source_streams_mask);
+		return -EBUSY;
+	}
+
+	routing = &state->routing;
+
+	/* Collect sink streams per pad which we need to disable */
+	for (source_stream = 0; source_stream < sizeof(source_streams_mask) * 8;
+	     ++source_stream) {
+		struct v4l2_subdev_route *route;
+
+		if (!(source_streams_mask & BIT_ULL(source_stream)))
+			continue;
+
+		for_each_active_route(routing, route) {
+			if (!(route->source_pad == source_pad) ||
+			    !(route->source_stream == source_stream))
+				continue;
+
+			nport = ub960_pad_to_port(priv, route->sink_pad);
+
+			sink_streams[nport] |= BIT_ULL(route->sink_stream);
+		}
+	}
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct v4l2_subdev *source_sd;
+		struct media_pad *source_sd_pad;
+
+		if (!sink_streams[nport])
+			continue;
+
+		dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport,
+			sink_streams[nport]);
+
+		source_sd_pad = media_pad_remote_pad_first(&priv->pads[nport]);
+		source_sd = priv->rxports[nport]->sd;
+
+		ret = v4l2_subdev_disable_streams(
+			source_sd, source_sd_pad->index, sink_streams[nport]);
+		WARN_ON(ret);
+
+		priv->stream_enable_mask[nport] &= ~sink_streams[nport];
+
+		/* Disable RX port if no active streams */
+		if (!priv->stream_enable_mask[nport])
+			ub960_disable_rx_port(priv, nport);
+	}
+
+	/* Disable TX port if no active streams */
+
+	priv->stream_enable_mask[source_pad] &= ~source_streams_mask;
+
+	if (!priv->stream_enable_mask[source_pad])
+		ub960_disable_tx_port(priv,
+				      ub960_pad_to_port(priv, source_pad));
+
+	ub960_update_streaming_status(priv);
+
+	return 0;
+}
+
+static int ub960_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+	const struct v4l2_subdev_krouting *routing;
+	struct v4l2_subdev_state *state;
+	struct v4l2_subdev_route *route;
+	u64 pad_stream_masks[UB960_MAX_TX_NPORTS] = { 0 };
+	unsigned int nport;
+	int ret = 0;
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	routing = &state->routing;
+
+	for_each_active_route(routing, route)
+		pad_stream_masks[ub960_pad_to_port(priv, route->source_pad)] |=
+			BIT_ULL(route->source_stream);
+
+	if (enable) {
+		for (nport = 0; nport < UB960_MAX_TX_NPORTS; ++nport) {
+			if (pad_stream_masks[nport] == 0)
+				continue;
+
+			ret = ub960_enable_streams(
+				sd, state, priv->hw_data->num_rxports + nport,
+				pad_stream_masks[nport]);
+
+			if (ret) {
+				for (; nport > 0; --nport) {
+					if (pad_stream_masks[nport - 1] == 0)
+						continue;
+
+					ub960_disable_streams(
+						sd, state,
+						priv->hw_data->num_rxports +
+							nport - 1,
+						pad_stream_masks[nport - 1]);
+				}
+
+				break;
+			}
+		}
+	} else {
+		for (nport = 0; nport < UB960_MAX_TX_NPORTS; ++nport) {
+			if (pad_stream_masks[nport] == 0)
+				continue;
+
+			ub960_disable_streams(
+				sd, state, priv->hw_data->num_rxports + nport,
+				pad_stream_masks[nport]);
+		}
+	}
+
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static const struct v4l2_subdev_video_ops ub960_video_ops = {
+	.s_stream = ub960_s_stream,
+};
+
+static int _ub960_set_routing(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state,
+			      struct v4l2_subdev_krouting *routing)
+{
+	const struct v4l2_mbus_framefmt format = {
+		.width = 640,
+		.height = 480,
+		.code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	/*
+	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
+	 * frame desc is made dynamically allocated.
+	 */
+
+	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
+		return -E2BIG;
+
+	/*
+	 * TODO: We need a new flag to validate that all streams from a sink pad
+	 * go to a single source pad.
+	 */
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub960_set_routing(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *state,
+			     enum v4l2_subdev_format_whence which,
+			     struct v4l2_subdev_krouting *routing)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming)
+		return -EBUSY;
+
+	return _ub960_set_routing(sd, state, routing);
+}
+
+static int ub960_get_source_frame_desc(struct ub960_data *priv,
+				       struct v4l2_mbus_frame_desc *desc,
+				       u8 nport)
+{
+	struct v4l2_subdev *sd;
+	struct media_pad *pad;
+	int ret;
+
+	pad = media_pad_remote_pad_first(&priv->pads[nport]);
+	if (!pad)
+		return -EPIPE;
+
+	sd = priv->rxports[nport]->sd;
+
+	ret = v4l2_subdev_call(sd, pad, get_frame_desc, pad->index, desc);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+				struct v4l2_mbus_frame_desc *fd)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+	const struct v4l2_subdev_krouting *routing;
+	struct v4l2_subdev_route *route;
+	struct v4l2_subdev_state *state;
+	int ret = 0;
+	struct device *dev = &priv->client->dev;
+	u8 vc_map[UB960_MAX_RX_NPORTS] = { 0 };
+
+	dev_dbg(dev, "%s for pad %d\n", __func__, pad);
+
+	if (!ub960_pad_is_source(priv, pad))
+		return -EINVAL;
+
+	state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
+
+	ub960_get_vc_maps(priv, state, vc_map);
+
+	routing = &state->routing;
+
+	memset(fd, 0, sizeof(*fd));
+
+	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+	for_each_active_route(routing, route) {
+		struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
+		struct v4l2_mbus_frame_desc source_fd;
+		unsigned int j;
+		unsigned int nrxport;
+
+		if (route->source_pad != pad)
+			continue;
+
+		ret = ub960_get_source_frame_desc(priv, &source_fd,
+						  route->sink_pad);
+		if (ret) {
+			dev_err(dev,
+				"Failed to get source frame desc for port %u\n",
+				route->sink_pad);
+			goto out;
+		}
+
+		for (j = 0; j < source_fd.num_entries; ++j)
+			if (source_fd.entry[j].stream == route->sink_stream) {
+				source_entry = &source_fd.entry[j];
+				break;
+			}
+
+		if (!source_entry) {
+			dev_err(dev,
+				"Failed to find stream from source frame desc\n");
+			ret = -EPIPE;
+			goto out;
+		}
+
+		fd->entry[fd->num_entries].stream = route->source_stream;
+
+		fd->entry[fd->num_entries].flags =
+			V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
+		fd->entry[fd->num_entries].length = source_entry->length;
+		fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode;
+
+		nrxport = ub960_pad_to_port(priv, route->sink_pad);
+
+		fd->entry[fd->num_entries].bus.csi2.vc = vc_map[nrxport];
+
+		if (source_fd.type == V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
+			fd->entry[fd->num_entries].bus.csi2.dt =
+				source_entry->bus.csi2.dt;
+		} else {
+			const struct ub960_format_info *ub960_fmt;
+			struct v4l2_mbus_framefmt *fmt;
+
+			fmt = v4l2_subdev_state_get_stream_format(
+				state, pad, route->source_stream);
+
+			if (!fmt) {
+				ret = -EINVAL;
+				goto out;
+			}
+
+			ub960_fmt = ub960_find_format(fmt->code);
+			if (!ub960_fmt) {
+				dev_err(dev, "Unable to find format\n");
+				ret = -EINVAL;
+				goto out;
+			}
+
+			fd->entry[fd->num_entries].bus.csi2.dt =
+				ub960_fmt->datatype;
+		}
+
+		fd->num_entries++;
+	}
+
+out:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int ub960_set_fmt(struct v4l2_subdev *sd,
+			 struct v4l2_subdev_state *state,
+			 struct v4l2_subdev_format *format)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+	struct v4l2_mbus_framefmt *fmt;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming)
+		return -EBUSY;
+
+	/* No transcoding, source and sink formats must match. */
+	if (ub960_pad_is_source(priv, format->pad))
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	/* TODO: implement fmt validation */
+
+	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
+						  format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int ub960_init_cfg(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *state)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = 0,
+			.sink_stream = 0,
+			.source_pad = priv->hw_data->num_rxports,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	return _ub960_set_routing(sd, state, &routing);
+}
+
+static const struct v4l2_subdev_pad_ops ub960_pad_ops = {
+	.enable_streams = ub960_enable_streams,
+	.disable_streams = ub960_disable_streams,
+
+	.set_routing = ub960_set_routing,
+	.get_frame_desc = ub960_get_frame_desc,
+
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ub960_set_fmt,
+
+	.init_cfg = ub960_init_cfg,
+};
+
+static int ub960_log_status(struct v4l2_subdev *sd)
+{
+	struct ub960_data *priv = sd_to_ub960(sd);
+	struct device *dev = &priv->client->dev;
+	struct v4l2_subdev_state *state;
+	unsigned int nport;
+	u8 v = 0, v1 = 0, v2 = 0;
+	unsigned int i;
+	char id[7];
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	for (i = 0; i < 6; ++i)
+		ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
+	id[6] = 0;
+
+	dev_info(dev, "ID '%s'\n", id);
+
+	for (nport = 0; nport < priv->hw_data->num_txports; ++nport) {
+		struct ub960_txport *txport = priv->txports[nport];
+
+		dev_info(dev, "TX %u\n", nport);
+
+		if (!txport) {
+			dev_info(dev, "\tNot initialized\n");
+			continue;
+		}
+
+		ub960_csiport_read(priv, nport, UB960_TR_CSI_STS, &v);
+		dev_info(dev, "\tsync %u, pass %u\n", v & (u8)BIT(1), v & (u8)BIT(0));
+
+		ub960_read(priv, UB960_SR_CSI_FRAME_COUNT_HI(nport), &v1);
+		ub960_read(priv, UB960_SR_CSI_FRAME_COUNT_LO(nport), &v2);
+		dev_info(dev, "\tframe counter %u\n", (v1 << 8) | v2);
+
+		ub960_read(priv, UB960_SR_CSI_FRAME_ERR_COUNT_HI(nport), &v1);
+		ub960_read(priv, UB960_SR_CSI_FRAME_ERR_COUNT_LO(nport), &v2);
+		dev_info(dev, "\tframe error counter %u\n", (v1 << 8) | v2);
+
+		ub960_read(priv, UB960_SR_CSI_LINE_COUNT_HI(nport), &v1);
+		ub960_read(priv, UB960_SR_CSI_LINE_COUNT_LO(nport), &v2);
+		dev_info(dev, "\tline counter %u\n", (v1 << 8) | v2);
+
+		ub960_read(priv, UB960_SR_CSI_LINE_ERR_COUNT_HI(nport), &v1);
+		ub960_read(priv, UB960_SR_CSI_LINE_ERR_COUNT_LO(nport), &v2);
+		dev_info(dev, "\tline error counter %u\n", (v1 << 8) | v2);
+	}
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+		u8 eq_level;
+		s8 strobe_pos;
+		unsigned int i;
+
+		dev_info(dev, "RX %u\n", nport);
+
+		if (!rxport) {
+			dev_info(dev, "\tNot initialized\n");
+			continue;
+		}
+
+		ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v);
+
+		if (v & UB960_RR_RX_PORT_STS1_LOCK_STS)
+			dev_info(dev, "\tLocked\n");
+		else
+			dev_info(dev, "\tNot locked\n");
+
+		dev_info(dev, "\trx_port_sts1 %#x\n", v);
+		ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v);
+		dev_info(dev, "\trx_port_sts2 %#x\n", v);
+
+		ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);
+		dev_info(dev, "\tlink freq %u MHz\n", v1 * 1000000 + v2 * 1000000 / 256);
+
+		ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2);
+		dev_info(dev, "\tparity errors %u\n", (v1 << 8) | v2);
+
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2);
+		dev_info(dev, "\tlines per frame %u\n", (v1 << 8) | v2);
+
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1);
+		ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2);
+		dev_info(dev, "\tbytes per line %u\n", (v1 << 8) | v2);
+
+		ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v);
+		dev_info(dev, "\tcsi_err_counter %u\n", v);
+
+		/* Strobe */
+
+		ub960_read(priv, UB960_XR_AEQ_CTL1, &v);
+
+		dev_info(dev, "\t%s strobe\n",
+			 (v & BIT(0)) ? "Adaptive" : "Manual");
+
+		if (v & BIT(0)) {
+			ub960_read(priv, UB960_XR_SFILTER_CFG, &v);
+
+			dev_info(dev, "\tStrobe range [%d, %d]\n",
+				 (v & 0xf) - 7, ((v >> 4) & 0xf) - 7);
+		}
+
+		ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos);
+
+		dev_info(dev, "\tStrobe pos %d\n", strobe_pos);
+
+		/* EQ */
+
+		ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
+
+		dev_info(dev, "\t%s EQ\n",
+			 (v & BIT(0)) ? "Manual" : "Adaptive");
+
+		if (!(v & BIT(0))) {
+			ub960_rxport_read(priv, nport, UB960_RR_AEQ_MIN_MAX,
+					  &v);
+
+			dev_info(dev, "\tEQ range [%u, %u]\n", v & 0xf,
+				 (v >> 4) & 0xf);
+		}
+
+		ub960_rxport_get_eq_level(priv, nport, &eq_level);
+
+		dev_info(dev, "\tEQ level %u\n", eq_level);
+
+		/* GPIOs */
+		for (i = 0; i < UB960_NUM_BC_GPIOS; ++i) {
+			u8 ctl_reg;
+			u8 ctl_shift;
+
+			ctl_reg = UB960_RR_BC_GPIO_CTL(i / 2);
+			ctl_shift = (i % 2) * 4;
+
+			ub960_rxport_read(priv, nport, ctl_reg, &v);
+
+			dev_info(dev, "\tGPIO%u: mode %u\n", i,
+				 (v >> ctl_shift) & 0xf);
+		}
+	}
+
+	v4l2_subdev_unlock_state(state);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops ub960_subdev_core_ops = {
+	.log_status		= ub960_log_status,
+	.subscribe_event	= v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event	= v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops ub960_subdev_ops = {
+	.core		= &ub960_subdev_core_ops,
+	.video		= &ub960_video_ops,
+	.pad		= &ub960_pad_ops,
+};
+
+static const struct media_entity_operations ub960_entity_ops = {
+	.get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1,
+	.link_validate = v4l2_subdev_link_validate,
+	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
+};
+
+static void ub960_enable_tpg(struct ub960_data *priv, int tpg_num)
+{
+	/*
+	 * Note: no need to write UB960_REG_IND_ACC_CTL: the only indirect
+	 * registers target we use is "CSI-2 Pattern Generator & Timing
+	 * Registers", which is the default
+	 */
+
+	/*
+	 * TPG can only provide a single stream per CSI TX port. If
+	 * multiple streams are currently enabled, only the first
+	 * one will use the TPG, other streams will be halted.
+	 */
+
+	struct v4l2_mbus_framefmt *fmt;
+	u8 vbp, vfp;
+	u16 blank_lines;
+	u16 width;
+	u16 height;
+
+	u16 bytespp = 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */
+	u8 cbars_idx = tpg_num - TEST_PATTERN_V_COLOR_BARS_1;
+	u8 num_cbars = 1 << cbars_idx;
+
+	u16 line_size;	/* Line size [bytes] */
+	u16 bar_size;	/* cbar size [bytes] */
+	u16 act_lpf;	/* active lines/frame */
+	u16 tot_lpf;	/* tot lines/frame */
+	u16 line_pd;	/* Line period in 10-ns units */
+
+	struct v4l2_subdev_state *state;
+
+	state = v4l2_subdev_get_locked_active_state(&priv->sd);
+
+	vbp = 33;
+	vfp = 10;
+	blank_lines = vbp + vfp + 2; /* total blanking lines */
+
+	fmt = v4l2_subdev_state_get_stream_format(state, 4, 0);
+	if (!fmt) {
+		dev_err(&priv->client->dev, "failed to enable TPG\n");
+		return;
+	}
+
+	width = fmt->width;
+	height = fmt->height;
+
+	line_size = width * bytespp;
+	bar_size = line_size / num_cbars;
+	act_lpf = height;
+	tot_lpf = act_lpf + blank_lines;
+	line_pd = 100000000 / 60 / tot_lpf;
+
+	/* Disable forwarding from FPD-3 RX ports */
+	ub960_read(priv, UB960_SR_FWD_CTL1, &priv->stored_fwd_ctl);
+	ub960_write(priv, UB960_SR_FWD_CTL1, 0xf << 4);
+
+	ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CTL,
+			UB960_IR_PGEN_CTL_PGEN_ENABLE);
+
+	/* YUV422 8bit: 2 bytes/block, CSI-2 data type 0x1e */
+	ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CFG,
+			cbars_idx << 4 | 0x2);
+	ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CSI_DI,
+			0x1e);
+
+	ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN,
+			  UB960_IR_PGEN_LINE_SIZE1, line_size);
+	ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN,
+			  UB960_IR_PGEN_BAR_SIZE1, bar_size);
+	ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN,
+			  UB960_IR_PGEN_ACT_LPF1, act_lpf);
+	ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN,
+			  UB960_IR_PGEN_TOT_LPF1, tot_lpf);
+	ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN,
+			  UB960_IR_PGEN_LINE_PD1, line_pd);
+	ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_VBP, vbp);
+	ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_VFP, vfp);
+}
+
+static void ub960_disable_tpg(struct ub960_data *priv)
+{
+	/* TPG off, enable forwarding from FPD-3 RX ports */
+	ub960_write(priv, UB960_SR_FWD_CTL1, priv->stored_fwd_ctl);
+
+	ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CTL, 0x00);
+}
+
+static int ub960_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ub960_data *priv =
+		container_of(ctrl->handler, struct ub960_data, ctrl_handler);
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		if (ctrl->val == 0)
+			ub960_disable_tpg(priv);
+		else
+			ub960_enable_tpg(priv, ctrl->val);
+		break;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops ub960_ctrl_ops = {
+	.s_ctrl = ub960_s_ctrl,
+};
+
+/* -----------------------------------------------------------------------------
+ * Core
+ */
+
+static irqreturn_t ub960_handle_events(int irq, void *arg)
+{
+	struct ub960_data *priv = arg;
+	unsigned int i;
+	u8 int_sts;
+	int ret;
+
+	ret = ub960_read(priv, UB960_SR_INTERRUPT_STS, &int_sts);
+
+	if (!ret && int_sts) {
+		u8 fwd_sts;
+
+		dev_dbg(&priv->client->dev, "INTERRUPT_STS %x\n", int_sts);
+
+		ub960_read(priv, UB960_SR_FWD_STS, &fwd_sts);
+
+		dev_dbg(&priv->client->dev, "FWD_STS %#x\n", fwd_sts);
+
+		for (i = 0; i < priv->hw_data->num_txports; ++i) {
+			if (int_sts & UB960_SR_INTERRUPT_STS_IS_CSI_TX(i))
+				ub960_csi_handle_events(priv, i);
+		}
+
+		for (i = 0; i < priv->hw_data->num_rxports; i++) {
+			if (!priv->rxports[i])
+				continue;
+
+			if (int_sts & UB960_SR_INTERRUPT_STS_IS_RX(i))
+				ub960_rxport_handle_events(priv, i);
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void ub960_handler_work(struct work_struct *work)
+{
+	struct delayed_work *dwork = to_delayed_work(work);
+	struct ub960_data *priv =
+		container_of(dwork, struct ub960_data, poll_work);
+
+	ub960_handle_events(0, priv);
+
+	schedule_delayed_work(&priv->poll_work,
+			      msecs_to_jiffies(UB960_POLL_TIME_MS));
+}
+
+static void ub960_txport_free_ports(struct ub960_data *priv)
+{
+	unsigned int nport;
+
+	for (nport = 0; nport < priv->hw_data->num_txports; nport++) {
+		struct ub960_txport *txport = priv->txports[nport];
+
+		if (!txport)
+			continue;
+
+		kfree(txport);
+		priv->txports[nport] = NULL;
+	}
+}
+
+static void ub960_rxport_free_ports(struct ub960_data *priv)
+{
+	unsigned int nport;
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; nport++) {
+		struct ub960_rxport *rxport = priv->rxports[nport];
+
+		if (!rxport)
+			continue;
+
+		if (rxport->vpoc)
+			regulator_disable(rxport->vpoc);
+
+		fwnode_handle_put(rxport->fwnode);
+		of_node_put(rxport->remote_of_node);
+
+		kfree(rxport);
+		priv->rxports[nport] = NULL;
+	}
+}
+
+static int ub960_parse_dt_base(struct ub960_data *priv)
+{
+	struct device_node *np = priv->client->dev.of_node;
+	struct device *dev = &priv->client->dev;
+	size_t table_size;
+	u16 *aliases;
+	int ret;
+
+	ret = of_property_count_u16_elems(np, "i2c-alias-pool");
+	if (ret < 0) {
+		dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n",
+			ret);
+		return ret;
+	}
+
+	table_size = ret;
+	priv->atr_alias_table.num_entries = ret;
+
+	if (!table_size)
+		return 0;
+
+	priv->atr_alias_table.entries =
+		devm_kcalloc(dev, table_size,
+			     sizeof(struct atr_alias_table_entry), GFP_KERNEL);
+	if (!priv->atr_alias_table.entries)
+		return -ENOMEM;
+
+	aliases = kcalloc(table_size, sizeof(u16), GFP_KERNEL);
+	if (!aliases)
+		return -ENOMEM;
+
+	ret = of_property_read_u16_array(np, "i2c-alias-pool", aliases,
+					 table_size);
+	if (ret < 0) {
+		dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n",
+			ret);
+		kfree(aliases);
+		return ret;
+	}
+
+	for (unsigned int i = 0; i < table_size; ++i)
+		priv->atr_alias_table.entries[i].alias_id = aliases[i];
+
+	kfree(aliases);
+
+	dev_dbg(dev, "i2c-alias-pool has %zu aliases", table_size);
+
+	return 0;
+}
+
+static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
+						 struct device_node *np,
+						 struct ub960_rxport *rxport)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+	u32 rx_mode;
+	u32 cdr_mode;
+	s32 strobe_pos;
+	u32 eq_level;
+	unsigned int nport = rxport->nport;
+
+	ret = of_property_read_u32(np, "ti,cdr-mode", &cdr_mode);
+	if (ret == -EINVAL) {
+		cdr_mode = RXPORT_CDR_FPD3;
+	} else if (ret < 0) {
+		dev_err(dev, "Failed to get RX port CDR mode: %d\n", ret);
+		return ret;
+	}
+
+	if (cdr_mode > RXPORT_CDR_LAST) {
+		dev_err(dev, "Bad RX port cdr %u\n", cdr_mode);
+		return -EINVAL;
+	}
+
+	if (!priv->hw_data->fpdlink4 && cdr_mode == RXPORT_CDR_FPD4) {
+		dev_err(dev, "FPD-Link 4 CDR not supported on RX port %u\n",
+			nport);
+		return -EINVAL;
+	}
+
+	rxport->cdr_mode = cdr_mode;
+
+	ret = of_property_read_u32(np, "ti,rx-mode", &rx_mode);
+	if (ret < 0) {
+		dev_err(dev, "Missing RX port RX mode: %d\n", ret);
+		return ret;
+	}
+
+	if (rx_mode > RXPORT_MODE_LAST) {
+		dev_err(dev, "Bad RX port RX mode %u\n", rx_mode);
+		return -EINVAL;
+	}
+
+	rxport->rx_mode = rx_mode;
+
+	/* EQ & Strobe related */
+
+	/* Defaults */
+	rxport->eq.manual_eq = false;
+	rxport->eq.aeq.eq_level_min = UB960_MIN_EQ_LEVEL;
+	rxport->eq.aeq.eq_level_max = UB960_MAX_EQ_LEVEL;
+
+	ret = of_property_read_s32(np, "ti,strobe-pos", &strobe_pos);
+	if (ret) {
+		if (ret != -EINVAL) {
+			dev_err(dev, "Failed to read 'ti,strobe-pos': %d\n", ret);
+			return ret;
+		}
+	} else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
+		   strobe_pos > UB960_MAX_MANUAL_STROBE_POS) {
+		dev_err(dev, "Illegal 'strobe-pos' value: %d\n", strobe_pos);
+	} else {
+		// NOTE: ignored unless global manual strobe pos is set
+		rxport->eq.strobe_pos = strobe_pos;
+		if (!priv->strobe.manual)
+			dev_warn(dev, "'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n");
+	}
+
+	ret = of_property_read_u32(np, "ti,eq-level", &eq_level);
+	if (ret) {
+		if (ret != -EINVAL) {
+			dev_err(dev, "Failed to read 'ti,eq-level': %d\n", ret);
+			return ret;
+		}
+	} else if (eq_level > UB960_MAX_EQ_LEVEL) {
+		dev_err(dev, "Illegal 'ti,eq-level' value: %d\n", eq_level);
+	} else {
+		rxport->eq.manual_eq = true;
+		rxport->eq.manual.eq_level = eq_level;
+	}
+
+	return 0;
+}
+
+static int ub960_parse_dt_rxport(struct ub960_data *priv,
+				 struct device_node *np)
+{
+	const char *vpoc_names[UB960_MAX_RX_NPORTS] = { "vpoc0", "vpoc1",
+							"vpoc2", "vpoc3" };
+	struct device *dev = &priv->client->dev;
+	unsigned int nport;
+	int ret;
+	struct ub960_rxport *rxport;
+	u32 ser_i2c_alias;
+	struct device_node *ep_np;
+	struct fwnode_handle *remote_ep_node;
+
+	ret = of_property_read_u32(np, "reg", &nport);
+	if (ret) {
+		dev_err(dev, "Failed to get link reg\n");
+		return ret;
+	}
+
+	if (priv->rxports[nport]) {
+		dev_err(dev, "OF: %s: reg value %d is duplicated\n",
+			of_node_full_name(np), nport);
+		return -EADDRINUSE;
+	}
+
+	rxport = kzalloc(sizeof(*rxport), GFP_KERNEL);
+	if (!rxport)
+		return -ENOMEM;
+
+	priv->rxports[nport] = rxport;
+
+	rxport->nport = nport;
+	rxport->priv = priv;
+
+	ret = ub960_parse_dt_rxport_link_properties(priv, np, rxport);
+	if (ret)
+		goto err_free_rxport;
+
+	ret = of_property_read_u32(np, "i2c-alias", &ser_i2c_alias);
+	if (ret) {
+		dev_err(dev, "Failed to get serializer i2c-alias: %d\n", ret);
+		goto err_free_rxport;
+	}
+	rxport->ser_alias = ser_i2c_alias;
+
+	rxport->vpoc = devm_regulator_get_optional(dev, vpoc_names[nport]);
+	if (IS_ERR(rxport->vpoc)) {
+		ret = PTR_ERR(rxport->vpoc);
+		if (ret == -ENODEV) {
+			rxport->vpoc = NULL;
+		} else {
+			dev_err(dev, "Failed to get VPOC supply: %d\n", ret);
+			goto err_free_rxport;
+		}
+	}
+
+	rxport->remote_of_node = of_get_child_by_name(np, "serializer");
+	if (!rxport->remote_of_node) {
+		dev_err(dev, "OF: %s: missing remote-chip child\n",
+			of_node_full_name(np));
+		ret = -EINVAL;
+		goto err_free_rxport;
+	}
+
+	/* We also parse the endpoint data here */
+
+	ep_np = of_graph_get_endpoint_by_regs(priv->client->dev.of_node, nport, 0);
+	if (!ep_np) {
+		dev_err(dev, "Missing OF graph endpoint for link %u\n", nport);
+		ret = -EINVAL;
+		goto err_ser_node_put;
+	}
+
+	remote_ep_node = fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep_np));
+
+	of_node_put(ep_np);
+
+	if (!remote_ep_node) {
+		dev_err(dev, "No remote endpoint for rxport%d\n", nport);
+		ret = -ENODEV;
+		goto err_ser_node_put;
+	}
+
+	rxport->fwnode = remote_ep_node;
+
+	// XXX enable somewhere else?
+	if (rxport->vpoc) {
+		ret = regulator_enable(rxport->vpoc);
+		if (ret)
+			goto err_ep_node_put;
+	}
+
+	return 0;
+
+err_ep_node_put:
+	fwnode_handle_put(rxport->fwnode);
+err_ser_node_put:
+	of_node_put(rxport->remote_of_node);
+err_free_rxport:
+	priv->rxports[nport] = NULL;
+	kfree(rxport);
+	return ret;
+}
+
+static struct device_node *
+ub960_of_get_link_by_regs(struct device_node *links_np, unsigned int nport)
+{
+	struct device_node *link_np;
+	int ret;
+
+	for_each_child_of_node(links_np, link_np) {
+		u32 link_num;
+
+		if (!of_node_name_eq(link_np, "link"))
+			continue;
+
+		ret = of_property_read_u32(link_np, "reg", &link_num);
+		if (ret)
+			return NULL;
+
+		if (nport == link_num) {
+			of_node_get(link_np);
+			return link_np;
+		}
+	}
+
+	return NULL;
+}
+
+static int ub960_parse_dt_rxports(struct ub960_data *priv)
+{
+	struct device_node *np = priv->client->dev.of_node;
+	struct device *dev = &priv->client->dev;
+	struct device_node *links_np;
+	unsigned int nport;
+	int ret;
+
+	links_np = of_get_child_by_name(np, "links");
+	if (!links_np) {
+		dev_err(dev, "links node missing\n");
+		return -ENODEV;
+	}
+
+	/* Defaults, recommended by TI */
+	priv->strobe.min = 2;
+	priv->strobe.max = 3;
+
+	priv->strobe.manual = of_property_read_bool(links_np, "ti,manual-strobe");
+
+	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+		struct device_node *link_np;
+
+		link_np = ub960_of_get_link_by_regs(links_np, nport);
+		if (!link_np)
+			continue;
+
+		ret = ub960_parse_dt_rxport(priv, link_np);
+
+		of_node_put(link_np);
+
+		if (ret) {
+			dev_err(dev, "Failed to parse link node\n");
+			goto err_put_links;
+		}
+	}
+
+	of_node_put(links_np);
+
+	return 0;
+
+err_put_links:
+	of_node_put(links_np);
+
+	return ret;
+}
+
+static int ub960_parse_dt_txports(struct ub960_data *priv)
+{
+	struct device_node *np = priv->client->dev.of_node;
+	u32 nport;
+	int ret;
+
+	for (nport = 0; nport < priv->hw_data->num_txports; ++nport) {
+		struct device_node *ep_np;
+
+		ep_np = of_graph_get_endpoint_by_regs(np, nport + priv->hw_data->num_rxports, 0);
+		if (!ep_np)
+			continue;
+
+		ret = ub960_parse_dt_txport(priv, ep_np, nport);
+
+		of_node_put(ep_np);
+
+		if (ret)
+			break;
+	}
+
+	return 0;
+}
+
+static int ub960_parse_dt(struct ub960_data *priv)
+{
+	struct device_node *np = priv->client->dev.of_node;
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	if (!np) {
+		dev_err(dev, "OF: no device tree node!\n");
+		return -ENOENT;
+	}
+
+	ret = ub960_parse_dt_base(priv);
+	if (ret)
+		return ret;
+
+	ret = ub960_parse_dt_rxports(priv);
+	if (ret)
+		return ret;
+
+	ret = ub960_parse_dt_txports(priv);
+	if (ret)
+		goto err_free_rxports;
+
+	return 0;
+
+err_free_rxports:
+	ub960_rxport_free_ports(priv);
+
+	return ret;
+}
+
+static int ub960_notify_bound(struct v4l2_async_notifier *notifier,
+			      struct v4l2_subdev *subdev,
+			      struct v4l2_async_subdev *asd)
+{
+	struct ub960_data *priv = sd_to_ub960(notifier->sd);
+	struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport;
+	struct device *dev = &priv->client->dev;
+	u8 nport = rxport->nport;
+	unsigned int src_pad;
+	unsigned int i;
+	int ret;
+
+	dev_dbg(dev, "Bind %s\n", subdev->name);
+
+	ret = media_entity_get_fwnode_pad(&subdev->entity, rxport->fwnode,
+					  MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(dev, "Failed to find pad for %s\n", subdev->name);
+		return ret;
+	}
+
+	rxport->sd = subdev;
+	src_pad = ret;
+
+	ret = media_create_pad_link(&rxport->sd->entity, src_pad,
+				    &priv->sd.entity, nport,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret) {
+		dev_err(dev, "Unable to link %s:%u -> %s:%u\n",
+			rxport->sd->name, src_pad, priv->sd.name, nport);
+		return ret;
+	}
+
+	dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name, src_pad,
+		nport);
+
+	for (i = 0; i < priv->hw_data->num_rxports; ++i) {
+		if (priv->rxports[i] && !priv->rxports[i]->sd) {
+			dev_dbg(dev, "Waiting for more subdevs to be bound\n");
+			return 0;
+		}
+	}
+
+	dev_dbg(dev, "All subdevs bound\n");
+
+	return 0;
+}
+
+static void ub960_notify_unbind(struct v4l2_async_notifier *notifier,
+				struct v4l2_subdev *subdev,
+				struct v4l2_async_subdev *asd)
+{
+	struct ub960_data *priv = sd_to_ub960(notifier->sd);
+	struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport;
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "Unbind %s\n", subdev->name);
+
+	rxport->sd = NULL;
+}
+
+static const struct v4l2_async_notifier_operations ub960_notify_ops = {
+	.bound = ub960_notify_bound,
+	.unbind = ub960_notify_unbind,
+};
+
+static int ub960_v4l2_notifier_register(struct ub960_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned int i;
+	int ret;
+
+	v4l2_async_nf_init(&priv->notifier);
+
+	for (i = 0; i < priv->hw_data->num_rxports; ++i) {
+		struct ub960_rxport *rxport = priv->rxports[i];
+		struct ub960_asd *asd;
+
+		if (!rxport)
+			continue;
+
+		asd = v4l2_async_nf_add_fwnode(&priv->notifier, rxport->fwnode,
+					       struct ub960_asd);
+		if (IS_ERR(asd)) {
+			dev_err(dev, "Failed to add subdev for source %u: %ld",
+				i, PTR_ERR(asd));
+			v4l2_async_nf_cleanup(&priv->notifier);
+			return PTR_ERR(asd);
+		}
+
+		asd->rxport = rxport;
+	}
+
+	priv->notifier.ops = &ub960_notify_ops;
+
+	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
+	if (ret) {
+		dev_err(dev, "Failed to register subdev_notifier");
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ub960_v4l2_notifier_unregister(struct ub960_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "Unregister async notif\n");
+
+	v4l2_async_nf_unregister(&priv->notifier);
+	v4l2_async_nf_cleanup(&priv->notifier);
+}
+
+static int ub960_create_subdev(struct ub960_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned int i;
+	int ret;
+
+	v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub960_subdev_ops);
+	v4l2_ctrl_handler_init(&priv->ctrl_handler,
+			       ARRAY_SIZE(ub960_tpg_qmenu) - 1);
+	priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub960_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ub960_tpg_qmenu) - 1, 0, 0,
+				     ub960_tpg_qmenu);
+
+	v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ,
+			       ARRAY_SIZE(priv->tx_link_freq) - 1, 0,
+			       priv->tx_link_freq);
+
+	if (priv->ctrl_handler.error) {
+		ret = priv->ctrl_handler.error;
+		goto err_free_ctrl;
+	}
+
+	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
+		V4L2_SUBDEV_FL_STREAMS;
+	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	priv->sd.entity.ops = &ub960_entity_ops;
+
+	for (i = 0; i < priv->hw_data->num_rxports + priv->hw_data->num_txports; i++) {
+		priv->pads[i].flags = ub960_pad_is_sink(priv, i) ?
+			MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE;
+	}
+
+	ret = media_entity_pads_init(&priv->sd.entity,
+				     priv->hw_data->num_rxports +
+					     priv->hw_data->num_txports,
+				     priv->pads);
+	if (ret)
+		goto err_free_ctrl;
+
+	priv->sd.state_lock = priv->sd.ctrl_handler->lock;
+
+	ret = v4l2_subdev_init_finalize(&priv->sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = ub960_v4l2_notifier_register(priv);
+	if (ret) {
+		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
+		goto err_free_state;
+	}
+
+	ret = v4l2_async_register_subdev(&priv->sd);
+	if (ret) {
+		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
+		goto err_unreg_notif;
+	}
+
+	return 0;
+
+err_unreg_notif:
+	ub960_v4l2_notifier_unregister(priv);
+err_free_state:
+	v4l2_subdev_cleanup(&priv->sd);
+err_entity_cleanup:
+	media_entity_cleanup(&priv->sd.entity);
+err_free_ctrl:
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+	return ret;
+}
+
+static void ub960_destroy_subdev(struct ub960_data *priv)
+{
+	ub960_v4l2_notifier_unregister(priv);
+	v4l2_async_unregister_subdev(&priv->sd);
+
+	v4l2_subdev_cleanup(&priv->sd);
+
+	media_entity_cleanup(&priv->sd.entity);
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+}
+
+static const struct regmap_config ub960_regmap_config = {
+	.name = "ds90ub960",
+
+	.reg_bits = 8,
+	.val_bits = 8,
+
+	.max_register = 0xff,
+
+	/*
+	 * We do locking in the driver to cover the TX/RX port selection and the
+	 * indirect register access.
+	 */
+	.disable_locking = true,
+};
+
+static void ub960_reset(struct ub960_data *priv, bool reset_regs)
+{
+	struct device *dev = &priv->client->dev;
+	unsigned int v;
+	int ret;
+	u8 bit;
+
+	bit = reset_regs ? BIT(1) : BIT(0);
+
+	ub960_write(priv, UB960_SR_RESET, bit);
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = regmap_read_poll_timeout(priv->regmap, UB960_SR_RESET, v,
+				       (v & bit) == 0, 2000, 100000);
+
+	mutex_unlock(&priv->reg_lock);
+
+	if (ret)
+		dev_err(dev, "reset failed: %d\n", ret);
+}
+
+static int ub960_get_hw_resources(struct ub960_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	priv->regmap = devm_regmap_init_i2c(priv->client, &ub960_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	priv->vddio = devm_regulator_get(dev, "vddio");
+	if (IS_ERR(priv->vddio)) {
+		dev_err(dev, "cannot get VDDIO regulator\n");
+		return PTR_ERR(priv->vddio);
+	}
+
+	/* get power-down pin from DT */
+	priv->pd_gpio =
+		devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
+	if (IS_ERR(priv->pd_gpio)) {
+		ret = PTR_ERR(priv->pd_gpio);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "Cannot get powerdown GPIO (%d)", ret);
+		return ret;
+	}
+
+	priv->refclk = devm_clk_get(dev, "refclk");
+	if (IS_ERR(priv->refclk)) {
+		ret = PTR_ERR(priv->refclk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "Cannot get REFCLK (%d)", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ub960_enable_core_hw(struct ub960_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	u8 rev_mask;
+	int ret;
+	u8 dev_sts;
+	u8 refclk_freq;
+
+	ret = regulator_enable(priv->vddio);
+	if (ret) {
+		dev_err(dev, "failed to enable VDDIO regulator\n");
+		return ret;
+	}
+
+	ret = clk_prepare_enable(priv->refclk);
+	if (ret) {
+		dev_err(dev, "Failed to renable refclk: %d\n", ret);
+		goto err_disable_vddio;
+	}
+
+	if (priv->pd_gpio) {
+		gpiod_set_value_cansleep(priv->pd_gpio, 1);
+		/* wait min 2 ms for reset to complete */
+		usleep_range(2000, 5000);
+		gpiod_set_value_cansleep(priv->pd_gpio, 0);
+		/* wait min 2 ms for power up to finish */
+		usleep_range(2000, 5000);
+	}
+
+	ub960_reset(priv, true);
+
+	/* Runtime check register accessibility */
+	ret = ub960_read(priv, UB960_SR_REV_MASK, &rev_mask);
+	if (ret) {
+		dev_err(dev, "Cannot read first register (%d), abort\n", ret);
+		goto err_pd_gpio;
+	}
+
+	dev_dbg(dev, "Found %s (rev/mask %#04x)\n", priv->hw_data->model,
+		rev_mask);
+
+	ret = ub960_read(priv, UB960_SR_DEVICE_STS, &dev_sts);
+	if (ret)
+		goto err_pd_gpio;
+
+	ret = ub960_read(priv, UB960_XR_REFCLK_FREQ, &refclk_freq);
+	if (ret)
+		goto err_pd_gpio;
+
+	dev_dbg(dev, "refclk valid %u freq %u MHz (clk fw freq %lu MHz)\n",
+		!!(dev_sts & BIT(4)), refclk_freq,
+		clk_get_rate(priv->refclk) / 1000000);
+
+	/* Disable all RX ports by default */
+	ub960_write(priv, UB960_SR_RX_PORT_CTL, 0);
+
+	return 0;
+
+err_pd_gpio:
+	if (priv->pd_gpio)
+		gpiod_set_value_cansleep(priv->pd_gpio, 1);
+
+	clk_disable_unprepare(priv->refclk);
+err_disable_vddio:
+	regulator_disable(priv->vddio);
+
+	return ret;
+}
+
+static void ub960_disable_core_hw(struct ub960_data *priv)
+{
+	if (priv->pd_gpio)
+		gpiod_set_value_cansleep(priv->pd_gpio, 1);
+
+	clk_disable_unprepare(priv->refclk);
+
+	regulator_disable(priv->vddio);
+}
+
+static int ub960_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct ub960_data *priv;
+	int ret;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+
+	priv->hw_data = of_device_get_match_data(dev);
+	if (!priv->hw_data)
+		return -ENODEV;
+
+	mutex_init(&priv->reg_lock);
+	mutex_init(&priv->atr_alias_table.lock);
+
+	INIT_DELAYED_WORK(&priv->poll_work, ub960_handler_work);
+
+	/*
+	 * Initialize these to invalid values so that the first reg writes will
+	 * configure the target.
+	 */
+	priv->current_indirect_target = 0xff;
+	priv->current_read_rxport = 0xff;
+	priv->current_write_rxport_mask = 0xff;
+	priv->current_read_csiport = 0xff;
+	priv->current_write_csiport_mask = 0xff;
+
+	ret = ub960_get_hw_resources(priv);
+	if (ret)
+		goto err_mutex_destroy;
+
+	ret = ub960_enable_core_hw(priv);
+	if (ret)
+		goto err_mutex_destroy;
+
+	/* release GPIO lock */
+	if (priv->hw_data->ub9702)
+		ub960_update_bits(priv, UB960_SR_RESET, BIT(5), BIT(5));
+
+	ret = ub960_parse_dt(priv);
+	if (ret)
+		goto err_disable_core_hw;
+
+	ret = ub960_init_tx_ports(priv);
+	if (ret)
+		goto err_free_ports;
+
+	ret = ub960_init_rx_ports(priv);
+	if (ret)
+		goto err_free_ports;
+
+	ub960_reset(priv, false);
+
+	ub960_rxport_wait_locks(priv, 0xf, NULL);
+
+	/*
+	 * Clear any errors caused by switching the RX port settings while
+	 * probing.
+	 */
+	ub960_clear_rx_errors(priv);
+
+	ret = ub960_init_atr(priv);
+	if (ret)
+		goto err_free_ports;
+
+	ret = ub960_rxport_add_serializers(priv);
+	if (ret)
+		goto err_uninit_atr;
+
+	ret = ub960_create_subdev(priv);
+	if (ret)
+		goto err_free_sers;
+
+	if (client->irq) {
+		dev_dbg(dev, "using IRQ %d\n", client->irq);
+
+		ret = devm_request_threaded_irq(dev, client->irq, NULL,
+						ub960_handle_events,
+						IRQF_ONESHOT, client->name,
+						priv);
+		if (ret) {
+			dev_err(dev, "Cannot enable IRQ (%d)\n", ret);
+			goto err_irq;
+		}
+
+		/* Disable GPIO3 as input */
+		ub960_update_bits(priv, UB960_SR_GPIO_INPUT_CTL, BIT(3), 0);
+		/* Enable GPIO3 as output, active low interrupt */
+		ub960_write(priv, UB960_SR_GPIO_PIN_CTL(3), 0xd1);
+
+		ub960_write(priv, UB960_SR_INTERRUPT_CTL,
+			    UB960_SR_INTERRUPT_CTL_ALL);
+	} else {
+		/* No IRQ, fallback to polling */
+		schedule_delayed_work(&priv->poll_work,
+				      msecs_to_jiffies(UB960_POLL_TIME_MS));
+
+		dev_dbg(dev, "using polling mode\n");
+	}
+
+	dev_info(dev, "Successfully probed\n");
+
+	return 0;
+
+err_irq:
+	ub960_destroy_subdev(priv);
+err_free_sers:
+	ub960_rxport_remove_serializers(priv);
+err_uninit_atr:
+	ub960_uninit_atr(priv);
+err_free_ports:
+	ub960_rxport_free_ports(priv);
+	ub960_txport_free_ports(priv);
+err_disable_core_hw:
+	ub960_disable_core_hw(priv);
+err_mutex_destroy:
+	mutex_destroy(&priv->atr_alias_table.lock);
+	mutex_destroy(&priv->reg_lock);
+	return ret;
+}
+
+static void ub960_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ub960_data *priv = sd_to_ub960(sd);
+
+	dev_dbg(&client->dev, "Removing\n");
+
+	cancel_delayed_work_sync(&priv->poll_work);
+
+	ub960_destroy_subdev(priv);
+	ub960_rxport_remove_serializers(priv);
+	ub960_uninit_atr(priv);
+	ub960_rxport_free_ports(priv);
+	ub960_txport_free_ports(priv);
+	ub960_disable_core_hw(priv);
+	mutex_destroy(&priv->atr_alias_table.lock);
+	mutex_destroy(&priv->reg_lock);
+
+	dev_dbg(&client->dev, "Remove done\n");
+}
+
+static const struct ub960_hw_data ds90ub960_hw = {
+	.model = "ub960",
+	.num_rxports = 4,
+	.num_txports = 2,
+};
+
+static const struct ub960_hw_data ds90ub9702_hw = {
+	.model = "ub9702",
+	.num_rxports = 4,
+	.num_txports = 2,
+	.ub9702 = true,
+	.fpdlink4 = true,
+};
+
+static const struct i2c_device_id ub960_id[] = {
+	{ "ds90ub960-q1", 0 },
+	{ "ds90ub9702-q1", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, ub960_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id ub960_dt_ids[] = {
+	{ .compatible = "ti,ds90ub960-q1", .data = &ds90ub960_hw },
+	{ .compatible = "ti,ds90ub9702-q1", .data = &ds90ub9702_hw },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ub960_dt_ids);
+#endif
+
+static struct i2c_driver ds90ub960_driver = {
+	.probe_new	= ub960_probe,
+	.remove		= ub960_remove,
+	.id_table	= ub960_id,
+	.driver = {
+		.name	= "ds90ub960",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(ub960_dt_ids),
+	},
+};
+
+module_i2c_driver(ds90ub960_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments DS90UB960-Q1 FPDLink-3 deserializer driver");
+MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>");
diff --git a/include/media/i2c/ds90ub9xx.h b/include/media/i2c/ds90ub9xx.h
new file mode 100644
index 000000000000..42d47d732c03
--- /dev/null
+++ b/include/media/i2c/ds90ub9xx.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __MEDIA_I2C_DS90UB9XX_H__
+#define __MEDIA_I2C_DS90UB9XX_H__
+
+#include <linux/types.h>
+
+struct i2c_atr;
+
+struct ds90ub9xx_platform_data {
+	u32 port;
+	struct i2c_atr *atr;
+	unsigned long bc_rate;
+};
+
+#endif /* __MEDIA_I2C_DS90UB9XX_H__ */
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
                   ` (5 preceding siblings ...)
  2022-12-08 10:40 ` [PATCH v5 6/8] media: i2c: add DS90UB960 driver Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-11 18:33   ` Laurent Pinchart
  2022-12-08 10:40 ` [PATCH v5 8/8] media: i2c: add DS90UB953 driver Tomi Valkeinen
  2022-12-08 10:42 ` [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
  8 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Add driver for TI DS90UB913 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 drivers/media/i2c/Kconfig     |  13 +
 drivers/media/i2c/Makefile    |   2 +-
 drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++
 3 files changed, 906 insertions(+), 1 deletion(-)
 create mode 100644 drivers/media/i2c/ds90ub913.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index a23f723b89b5..ff5847aed5ae 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1614,6 +1614,19 @@ config VIDEO_DS90UB960
 	  Device driver for the Texas Instruments DS90UB960
 	  FPD-Link III Deserializer
 
+config VIDEO_DS90UB913
+	tristate "TI DS90UB913 Serializer"
+	depends on OF && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	select REGMAP_I2C
+	select OF_GPIO
+	select I2C_ATR
+	help
+	  Device driver for the Texas Instruments DS90UB913
+	  FPD-Link III Serializer.
+
 endmenu
 
 endif # VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 2735b00437bb..376886f2ded6 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -143,4 +143,4 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
 obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
 obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
 obj-$(CONFIG_VIDEO_DS90UB960)	+= ds90ub960.o
-
+obj-$(CONFIG_VIDEO_DS90UB913)	+= ds90ub913.o
diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
new file mode 100644
index 000000000000..6001a622e622
--- /dev/null
+++ b/drivers/media/i2c/ds90ub913.c
@@ -0,0 +1,892 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DS90UB913 video serializer
+ *
+ * Based on a driver from Luca Ceresoli <luca@lucaceresoli.net>
+ *
+ * Copyright (c) 2019 Luca Ceresoli <luca@lucaceresoli.net>
+ * Copyright (c) 2022 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c-atr.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/regmap.h>
+
+#include <media/i2c/ds90ub9xx.h>
+#include <media/v4l2-subdev.h>
+
+#define UB913_PAD_SINK			0
+#define UB913_PAD_SOURCE		1
+
+/*
+ * UB913 has 4 gpios, but gpios 3 and 4 are reserved for external oscillator
+ * mode. Thus we only support 2 gpios for now.
+ */
+#define UB913_NUM_GPIOS			2
+
+#define UB913_REG_RESET_CTL			0x01
+#define UB913_REG_RESET_CTL_DIGITAL_RESET_1	BIT(1)
+#define UB913_REG_RESET_CTL_DIGITAL_RESET_0	BIT(0)
+
+#define UB913_REG_GENERAL_CFG			0x03
+#define UB913_REG_MODE_SEL			0x05
+
+#define UB913_REG_CRC_ERRORS_LSB		0x0a
+#define UB913_REG_CRC_ERRORS_MSB		0x0b
+
+#define UB913_REG_GENERAL_STATUS		0x0c
+
+#define UB913_REG_GPIO_CFG(n)			(0x0d + (n))
+#define UB913_REG_GPIO_CFG_ENABLE(n)		BIT(0 + (n) * 4)
+#define UB913_REG_GPIO_CFG_DIR_INPUT(n)		BIT(1 + (n) * 4)
+#define UB913_REG_GPIO_CFG_REMOTE_EN(n)		BIT(2 + (n) * 4)
+#define UB913_REG_GPIO_CFG_OUT_VAL(n)		BIT(3 + (n) * 4)
+#define UB913_REG_GPIO_CFG_MASK(n)		(0xf << ((n) * 4))
+
+#define UB913_REG_SCL_HIGH_TIME			0x11
+#define UB913_REG_SCL_LOW_TIME			0x12
+
+#define UB913_REG_PLL_OVR			0x35
+
+struct ub913_data {
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+	struct clk		*clkin;
+
+	u32			gpio_func[UB913_NUM_GPIOS];
+
+	struct gpio_chip	gpio_chip;
+	char			gpio_chip_name[64];
+
+	struct v4l2_subdev	sd;
+	struct media_pad	pads[2];
+
+	struct v4l2_async_notifier	notifier;
+
+	struct v4l2_subdev	*source_sd;
+
+	u64			enabled_source_streams;
+
+	struct device_node	*tx_ep_np;
+
+	struct clk_hw		*clkout_clk_hw;
+
+	struct ds90ub9xx_platform_data *plat_data;
+
+	/* Have we succefully called i2c_atr_add_adapter() */
+	bool			has_i2c_adapter;
+};
+
+static inline struct ub913_data *sd_to_ub913(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ub913_data, sd);
+}
+
+static int ub913_read(const struct ub913_data *priv, u8 reg, u8 *val)
+{
+	unsigned int v;
+	int ret;
+
+	ret = regmap_read(priv->regmap, reg, &v);
+	if (ret < 0) {
+		dev_err(&priv->client->dev,
+			"Cannot read register 0x%02x: %d!\n", reg, ret);
+		return ret;
+	}
+
+	*val = v;
+	return 0;
+}
+
+static int ub913_write(const struct ub913_data *priv, u8 reg, u8 val)
+{
+	int ret;
+
+	ret = regmap_write(priv->regmap, reg, val);
+	if (ret < 0)
+		dev_err(&priv->client->dev,
+			"Cannot write register 0x%02x: %d!\n", reg, ret);
+
+	return ret;
+}
+
+/*
+ * GPIO chip
+ */
+static int ub913_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int offset,
+				    int value)
+{
+	struct ub913_data *priv = gpiochip_get_data(gc);
+	unsigned int reg_idx;
+	unsigned int field_idx;
+	int ret;
+
+	reg_idx = offset / 2;
+	field_idx = offset % 2;
+
+	ret = regmap_update_bits(
+		priv->regmap, UB913_REG_GPIO_CFG(reg_idx),
+		UB913_REG_GPIO_CFG_MASK(field_idx),
+		UB913_REG_GPIO_CFG_ENABLE(field_idx) |
+			(value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : 0));
+
+	return ret;
+}
+
+static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	ub913_gpio_direction_out(gc, offset, value);
+}
+
+static int ub913_gpio_of_xlate(struct gpio_chip *gc,
+			       const struct of_phandle_args *gpiospec,
+			       u32 *flags)
+{
+	if (flags)
+		*flags = gpiospec->args[1];
+
+	return gpiospec->args[0];
+}
+
+static int ub913_gpiochip_probe(struct ub913_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct gpio_chip *gc = &priv->gpio_chip;
+	int ret;
+
+	/* Initialize GPIOs 0 and 1 to local control, tri-state */
+	ub913_write(priv, UB913_REG_GPIO_CFG(0), 0);
+
+	scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s",
+		  dev_name(dev));
+
+	gc->label = priv->gpio_chip_name;
+	gc->parent = dev;
+	gc->owner = THIS_MODULE;
+	gc->base = -1;
+	gc->can_sleep = 1;
+	gc->ngpio = UB913_NUM_GPIOS;
+	gc->get_direction = ub913_gpio_get_direction;
+	gc->direction_output = ub913_gpio_direction_out;
+	gc->set = ub913_gpio_set;
+	gc->of_xlate = ub913_gpio_of_xlate;
+	gc->of_node = priv->client->dev.of_node;
+	gc->of_gpio_n_cells = 2;
+
+	ret = gpiochip_add_data(gc, priv);
+	if (ret) {
+		dev_err(dev, "Failed to add GPIOs: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ub913_gpiochip_remove(struct ub913_data *priv)
+{
+	gpiochip_remove(&priv->gpio_chip);
+}
+
+static int ub913_parse_dt(struct ub913_data *priv)
+{
+	struct device_node *np = priv->client->dev.of_node;
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	if (!np) {
+		dev_err(dev, "OF: no device tree node!\n");
+		return -ENOENT;
+	}
+
+	/* optional, if absent all GPIO pins are unused */
+	ret = of_property_read_u32_array(np, "gpio-functions", priv->gpio_func,
+					 ARRAY_SIZE(priv->gpio_func));
+	if (ret && ret != -EINVAL)
+		dev_err(dev, "DT: invalid gpio-functions property (%d)", ret);
+
+	return 0;
+}
+
+static const struct regmap_config ub913_regmap_config = {
+	.name = "ds90ub913",
+	.reg_bits = 8,
+	.val_bits = 8,
+	.reg_format_endian = REGMAP_ENDIAN_DEFAULT,
+	.val_format_endian = REGMAP_ENDIAN_DEFAULT,
+};
+
+/*
+ * V4L2
+ */
+
+static int ub913_enable_streams(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state, u32 pad,
+				u64 streams_mask)
+{
+	struct ub913_data *priv = sd_to_ub913(sd);
+	struct media_pad *remote_pad;
+	u64 sink_streams;
+	int ret;
+
+	if (streams_mask & priv->enabled_source_streams)
+		return -EALREADY;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(
+		state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask);
+
+	remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
+
+	ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index,
+					 sink_streams);
+	if (ret)
+		return ret;
+
+	priv->enabled_source_streams |= streams_mask;
+
+	return 0;
+}
+
+static int ub913_disable_streams(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state, u32 pad,
+				 u64 streams_mask)
+{
+	struct ub913_data *priv = sd_to_ub913(sd);
+	struct media_pad *remote_pad;
+	int ret;
+	u64 sink_streams;
+
+	if ((streams_mask & priv->enabled_source_streams) != streams_mask)
+		return -EALREADY;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(
+		state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask);
+
+	remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
+
+	ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index,
+					  sink_streams);
+	if (ret)
+		return ret;
+
+	priv->enabled_source_streams &= ~streams_mask;
+
+	return 0;
+}
+
+static int _ub913_set_routing(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state,
+			      struct v4l2_subdev_krouting *routing)
+{
+	const struct v4l2_mbus_framefmt format = {
+		.width = 640,
+		.height = 480,
+		.code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	/*
+	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
+	 * frame desc is made dynamically allocated.
+	 */
+
+	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
+		return -EINVAL;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub913_set_routing(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *state,
+			     enum v4l2_subdev_format_whence which,
+			     struct v4l2_subdev_krouting *routing)
+{
+	struct ub913_data *priv = sd_to_ub913(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
+		return -EBUSY;
+
+	return _ub913_set_routing(sd, state, routing);
+}
+
+static int ub913_get_source_frame_desc(struct ub913_data *priv,
+				       struct v4l2_mbus_frame_desc *desc)
+{
+	struct media_pad *pad;
+	int ret;
+
+	pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
+	if (!pad)
+		return -EPIPE;
+
+	ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index,
+			       desc);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+				struct v4l2_mbus_frame_desc *fd)
+{
+	struct ub913_data *priv = sd_to_ub913(sd);
+	const struct v4l2_subdev_krouting *routing;
+	struct v4l2_mbus_frame_desc source_fd;
+	struct v4l2_subdev_route *route;
+	struct v4l2_subdev_state *state;
+	int ret = 0;
+
+	if (pad != 1) /* first tx pad */
+		return -EINVAL;
+
+	ret = ub913_get_source_frame_desc(priv, &source_fd);
+	if (ret)
+		return ret;
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	routing = &state->routing;
+
+	memset(fd, 0, sizeof(*fd));
+
+	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
+
+	for_each_active_route(routing, route) {
+		unsigned int j;
+
+		if (route->source_pad != pad)
+			continue;
+
+		for (j = 0; j < source_fd.num_entries; ++j)
+			if (source_fd.entry[j].stream == route->sink_stream)
+				break;
+
+		if (j == source_fd.num_entries) {
+			dev_err(&priv->client->dev,
+				"Failed to find stream from source frame desc\n");
+			ret = -EPIPE;
+			goto out;
+		}
+
+		fd->entry[fd->num_entries].stream = route->source_stream;
+
+		fd->entry[fd->num_entries].flags =
+			V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
+		fd->entry[fd->num_entries].length = source_fd.entry[j].length;
+		fd->entry[fd->num_entries].pixelcode =
+			source_fd.entry[j].pixelcode;
+
+		fd->num_entries++;
+	}
+
+out:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int ub913_set_fmt(struct v4l2_subdev *sd,
+			 struct v4l2_subdev_state *state,
+			 struct v4l2_subdev_format *format)
+{
+	struct ub913_data *priv = sd_to_ub913(sd);
+	struct v4l2_mbus_framefmt *fmt;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+	    priv->enabled_source_streams)
+		return -EBUSY;
+
+	/* No transcoding, source and sink formats must match. */
+	if (format->pad == 1)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	/* Set sink format */
+	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
+						  format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	/* Propagate to source format */
+	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int ub913_init_cfg(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = 0,
+			.sink_stream = 0,
+			.source_pad = 1,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	return _ub913_set_routing(sd, state, &routing);
+}
+
+static int ub913_log_status(struct v4l2_subdev *sd)
+{
+	struct ub913_data *priv = sd_to_ub913(sd);
+	struct device *dev = &priv->client->dev;
+	u8 v, v1, v2;
+
+	ub913_read(priv, UB913_REG_MODE_SEL, &v);
+	dev_info(dev, "MODE_SEL %#x\n", v);
+
+	ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1);
+	ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2);
+	dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8));
+
+	ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
+	dev_info(dev, "GENERAL_STATUS %#x\n", v);
+
+	ub913_read(priv, UB913_REG_PLL_OVR, &v);
+	dev_info(dev, "PLL_OVR %#x\n", v);
+
+	/* clear CRC errors */
+	ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
+	ub913_write(priv, UB913_REG_GENERAL_CFG, v | BIT(5));
+	ub913_write(priv, UB913_REG_GENERAL_CFG, v);
+
+	return 0;
+}
+
+static const struct v4l2_subdev_core_ops ub913_subdev_core_ops = {
+	.log_status = ub913_log_status,
+};
+
+static const struct v4l2_subdev_pad_ops ub913_pad_ops = {
+	.enable_streams = ub913_enable_streams,
+	.disable_streams = ub913_disable_streams,
+	.set_routing = ub913_set_routing,
+	.get_frame_desc	= ub913_get_frame_desc,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ub913_set_fmt,
+	.init_cfg = ub913_init_cfg,
+};
+
+static const struct v4l2_subdev_ops ub913_subdev_ops = {
+	.core = &ub913_subdev_core_ops,
+	.pad = &ub913_pad_ops,
+};
+
+static const struct media_entity_operations ub913_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+static int ub913_notify_bound(struct v4l2_async_notifier *notifier,
+			      struct v4l2_subdev *source_subdev,
+			      struct v4l2_async_subdev *asd)
+{
+	struct ub913_data *priv = sd_to_ub913(notifier->sd);
+	struct device *dev = &priv->client->dev;
+	unsigned int src_pad;
+	int ret;
+
+	dev_dbg(dev, "Bind %s\n", source_subdev->name);
+
+	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
+					  source_subdev->fwnode,
+					  MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(dev, "Failed to find pad for %s\n",
+			source_subdev->name);
+		return ret;
+	}
+
+	priv->source_sd = source_subdev;
+	src_pad = ret;
+
+	ret = media_create_pad_link(&source_subdev->entity, src_pad,
+				    &priv->sd.entity, 0,
+				    MEDIA_LNK_FL_ENABLED |
+				    MEDIA_LNK_FL_IMMUTABLE);
+	if (ret) {
+		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
+			source_subdev->name, src_pad, priv->sd.name);
+		return ret;
+	}
+
+	dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad);
+
+	dev_dbg(dev, "All subdevs bound\n");
+
+	return 0;
+}
+
+static void ub913_notify_unbind(struct v4l2_async_notifier *notifier,
+				struct v4l2_subdev *source_subdev,
+				struct v4l2_async_subdev *asd)
+{
+	struct ub913_data *priv = sd_to_ub913(notifier->sd);
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "Unbind %s\n", source_subdev->name);
+}
+
+static const struct v4l2_async_notifier_operations ub913_notify_ops = {
+	.bound = ub913_notify_bound,
+	.unbind = ub913_notify_unbind,
+};
+
+static int ub913_v4l2_notifier_register(struct ub913_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct v4l2_async_subdev *asd;
+	struct device_node *ep_node;
+	int ret;
+
+	dev_dbg(dev, "register async notif\n");
+
+	ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+	if (!ep_node) {
+		dev_err(dev, "No graph endpoint\n");
+		return -ENODEV;
+	}
+
+	v4l2_async_nf_init(&priv->notifier);
+
+	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier,
+					      of_fwnode_handle(ep_node),
+					      struct v4l2_async_subdev);
+
+	of_node_put(ep_node);
+
+	if (IS_ERR(asd)) {
+		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return PTR_ERR(asd);
+	}
+
+	priv->notifier.ops = &ub913_notify_ops;
+
+	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
+	if (ret) {
+		dev_err(dev, "Failed to register subdev_notifier");
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ub913_v4l2_nf_unregister(struct ub913_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "Unregister async notif\n");
+
+	v4l2_async_nf_unregister(&priv->notifier);
+	v4l2_async_nf_cleanup(&priv->notifier);
+}
+
+static int ub913_register_clkout(struct ub913_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	const char *name;
+	int ret;
+
+	name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev));
+
+	priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name,
+		__clk_get_name(priv->clkin), 0, 1, 2);
+
+	kfree(name);
+
+	if (IS_ERR(priv->clkout_clk_hw))
+		return dev_err_probe(dev, PTR_ERR(priv->clkout_clk_hw),
+				     "Cannot register clkout hw\n");
+
+	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+					  priv->clkout_clk_hw);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Cannot add OF clock provider\n");
+
+	return 0;
+}
+
+static int ub913_i2c_master_init(struct ub913_data *priv)
+{
+	/* i2c fast mode */
+	u32 scl_high = 600 + 300; /* high period + rise time, ns */
+	u32 scl_low = 1300 + 300; /* low period + fall time, ns */
+	unsigned long ref;
+	int ret;
+
+	ref = clk_get_rate(priv->clkin) / 2;
+
+	scl_high = div64_u64((u64)scl_high * ref, 1000000000);
+	scl_low = div64_u64((u64)scl_low * ref, 1000000000);
+
+	ret = ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high);
+	if (ret)
+		return ret;
+
+	ret = ub913_write(priv, UB913_REG_SCL_LOW_TIME, scl_low);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub913_add_i2c_adapter(struct ub913_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct fwnode_handle *i2c_handle;
+	int ret;
+
+	i2c_handle = device_get_named_child_node(dev, "i2c");
+	if (!i2c_handle)
+		return 0;
+
+	ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port,
+				  i2c_handle);
+
+	fwnode_handle_put(i2c_handle);
+
+	if (ret)
+		return ret;
+
+	priv->has_i2c_adapter = true;
+
+	return 0;
+}
+
+static void ub913_remove_i2c_adapter(struct ub913_data *priv)
+{
+	if (priv->has_i2c_adapter)
+		i2c_atr_del_adapter(priv->plat_data->atr,
+				    priv->plat_data->port);
+}
+
+static int ub913_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct ub913_data *priv;
+	int ret;
+	u8 v;
+	bool mode_override;
+	u8 mode;
+
+	dev_dbg(dev, "probing, addr 0x%02x\n", client->addr);
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+
+	priv->plat_data = dev_get_platdata(&client->dev);
+	if (!priv->plat_data) {
+		dev_err(dev, "Platform data missing\n");
+		return -ENODEV;
+	}
+
+	priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
+	if (IS_ERR(priv->regmap)) {
+		dev_err(dev, "Failed to init regmap\n");
+		return PTR_ERR(priv->regmap);
+	}
+
+	/* ub913 can also work without ext clock, but that is not supported */
+	priv->clkin = devm_clk_get(dev, "clkin");
+	if (IS_ERR(priv->clkin)) {
+		ret = PTR_ERR(priv->clkin);
+		if (ret != -EPROBE_DEFER)
+			dev_err(dev, "Cannot get CLKIN (%d)", ret);
+		return ret;
+	}
+
+	ret = ub913_parse_dt(priv);
+	if (ret)
+		return ret;
+
+	ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
+	if (ret)
+		return ret;
+
+	if (!(v & BIT(4))) {
+		dev_err(dev, "Mode value not stabilized\n");
+		return -ENODEV;
+	}
+
+	mode_override = v & BIT(5);
+	mode = v & 0xf;
+
+	dev_dbg(dev, "mode from %s: %#x\n",
+		mode_override ? "reg" : "deserializer", mode);
+
+	ret = ub913_i2c_master_init(priv);
+	if (ret) {
+		dev_err(dev, "i2c master init failed: %d\n", ret);
+		return ret;
+	}
+
+	ret = ub913_gpiochip_probe(priv);
+	if (ret) {
+		dev_err(dev, "Failed to init gpiochip\n");
+		return ret;
+	}
+
+	ret = ub913_register_clkout(priv);
+	if (ret) {
+		dev_err(dev, "Failed to register clkout\n");
+		goto err_gpiochip_remove;
+	}
+
+	v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops);
+	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
+	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	priv->sd.entity.ops = &ub913_entity_ops;
+
+	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
+	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
+	if (ret) {
+		dev_err(dev, "Failed to init pads\n");
+		goto err_gpiochip_remove;
+	}
+
+	priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0);
+	if (priv->tx_ep_np)
+		priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np);
+
+	ret = v4l2_subdev_init_finalize(&priv->sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = ub913_v4l2_notifier_register(priv);
+	if (ret) {
+		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
+		goto err_free_state;
+	}
+
+	ret = v4l2_async_register_subdev(&priv->sd);
+	if (ret) {
+		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
+		goto err_unreg_notif;
+	}
+
+	ret = ub913_add_i2c_adapter(priv);
+	if (ret) {
+		dev_err(dev, "failed to add remote i2c adapter\n");
+		goto err_unreg_async_subdev;
+	}
+
+	dev_dbg(dev, "Successfully probed\n");
+
+	return 0;
+
+err_unreg_async_subdev:
+	v4l2_async_unregister_subdev(&priv->sd);
+err_unreg_notif:
+	ub913_v4l2_nf_unregister(priv);
+err_free_state:
+	v4l2_subdev_cleanup(&priv->sd);
+err_entity_cleanup:
+	if (priv->tx_ep_np)
+		of_node_put(priv->tx_ep_np);
+
+	media_entity_cleanup(&priv->sd.entity);
+err_gpiochip_remove:
+	ub913_gpiochip_remove(priv);
+
+	return ret;
+}
+
+static void ub913_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ub913_data *priv = sd_to_ub913(sd);
+
+	dev_dbg(&client->dev, "Removing\n");
+
+	ub913_remove_i2c_adapter(priv);
+
+	v4l2_async_unregister_subdev(&priv->sd);
+
+	ub913_v4l2_nf_unregister(priv);
+
+	v4l2_subdev_cleanup(&priv->sd);
+
+	if (priv->tx_ep_np)
+		of_node_put(priv->tx_ep_np);
+
+	media_entity_cleanup(&priv->sd.entity);
+
+	ub913_gpiochip_remove(priv);
+}
+
+static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} };
+MODULE_DEVICE_TABLE(i2c, ub913_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id ub913_dt_ids[] = {
+	{ .compatible = "ti,ds90ub913a-q1", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, ub913_dt_ids);
+#endif
+
+static struct i2c_driver ds90ub913_driver = {
+	.probe_new	= ub913_probe,
+	.remove		= ub913_remove,
+	.id_table	= ub913_id,
+	.driver = {
+		.name	= "ds90ub913a",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(ub913_dt_ids),
+	},
+};
+
+module_i2c_driver(ds90ub913_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments DS90UB913 serializer driver");
+MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* [PATCH v5 8/8] media: i2c: add DS90UB953 driver
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
                   ` (6 preceding siblings ...)
  2022-12-08 10:40 ` [PATCH v5 7/8] media: i2c: add DS90UB913 driver Tomi Valkeinen
@ 2022-12-08 10:40 ` Tomi Valkeinen
  2022-12-08 10:42 ` [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
  8 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:40 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut,
	Tomi Valkeinen

Add driver for TI DS90UB953 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 drivers/media/i2c/Kconfig     |   13 +
 drivers/media/i2c/Makefile    |    1 +
 drivers/media/i2c/ds90ub953.c | 1607 +++++++++++++++++++++++++++++++++
 3 files changed, 1621 insertions(+)
 create mode 100644 drivers/media/i2c/ds90ub953.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index ff5847aed5ae..b24f89f58807 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1627,6 +1627,19 @@ config VIDEO_DS90UB913
 	  Device driver for the Texas Instruments DS90UB913
 	  FPD-Link III Serializer.
 
+config VIDEO_DS90UB953
+	tristate "TI DS90UB953 Serializer"
+	depends on OF && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	select REGMAP_I2C
+	select OF_GPIO
+	select I2C_ATR
+	help
+	  Device driver for the Texas Instruments DS90UB953
+	  FPD-Link III Serializer.
+
 endmenu
 
 endif # VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 376886f2ded6..377cdf6fa30b 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -144,3 +144,4 @@ obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
 obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
 obj-$(CONFIG_VIDEO_DS90UB960)	+= ds90ub960.o
 obj-$(CONFIG_VIDEO_DS90UB913)	+= ds90ub913.o
+obj-$(CONFIG_VIDEO_DS90UB953)	+= ds90ub953.o
diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c
new file mode 100644
index 000000000000..c7f5d08e07ef
--- /dev/null
+++ b/drivers/media/i2c/ds90ub953.c
@@ -0,0 +1,1607 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DS90UB953 video serializer
+ *
+ * Based on a driver from Luca Ceresoli <luca@lucaceresoli.net>
+ *
+ * Copyright (c) 2019 Luca Ceresoli <luca@lucaceresoli.net>
+ * Copyright (c) 2022 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c-atr.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/rational.h>
+#include <linux/regmap.h>
+
+#include <media/i2c/ds90ub9xx.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-subdev.h>
+
+#define UB953_PAD_SINK			0
+#define UB953_PAD_SOURCE		1
+
+#define UB953_NUM_GPIOS			4
+
+#define UB953_REG_RESET_CTL			0x01
+#define UB953_REG_RESET_CTL_DIGITAL_RESET_1	BIT(1)
+#define UB953_REG_RESET_CTL_DIGITAL_RESET_0	BIT(0)
+
+#define UB953_REG_GENERAL_CFG			0x02
+#define UB953_REG_MODE_SEL			0x03
+
+#define UB953_REG_CLKOUT_CTRL0			0x06
+#define UB953_REG_CLKOUT_CTRL1			0x07
+
+#define UB953_REG_SCL_HIGH_TIME			0x0B
+#define UB953_REG_SCL_LOW_TIME			0x0C
+
+#define UB953_REG_LOCAL_GPIO_DATA		0x0d
+#define UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(n)		BIT(4 + (n))
+#define UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(n)	BIT(0 + (n))
+
+#define UB953_REG_GPIO_INPUT_CTRL		0x0e
+#define UB953_REG_GPIO_INPUT_CTRL_OUT_EN(n)	BIT(4 + (n))
+#define UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(n)	BIT(0 + (n))
+
+#define UB953_REG_REV_MASK_ID			0x50
+#define UB953_REG_GENERAL_STATUS		0x52
+
+#define UB953_REG_GPIO_PIN_STS			0x53
+#define UB953_REG_GPIO_PIN_STS_GPIO_STS(n)	BIT(0 + (n))
+
+#define UB953_REG_BIST_ERR_CNT			0x54
+#define UB953_REG_CRC_ERR_CNT1			0x55
+#define UB953_REG_CRC_ERR_CNT2			0x56
+
+#define UB953_REG_CSI_ERR_CNT			0x5c
+#define UB953_REG_CSI_ERR_STATUS		0x5D
+#define UB953_REG_CSI_ERR_DLANE01		0x5E
+#define UB953_REG_CSI_ERR_DLANE23		0x5F
+#define UB953_REG_CSI_ERR_CLK_LANE		0x60
+#define UB953_REG_CSI_PKT_HDR_VC_ID		0x61
+#define UB953_REG_PKT_HDR_WC_LSB		0x62
+#define UB953_REG_PKT_HDR_WC_MSB		0x63
+#define UB953_REG_CSI_ECC			0x64
+
+#define UB953_REG_IND_ACC_CTL			0xb0
+#define UB953_REG_IND_ACC_ADDR			0xb1
+#define UB953_REG_IND_ACC_DATA			0xb2
+
+#define UB953_REG_FPD3_RX_ID(n)			(0xf0 + (n))
+
+/* Indirect register blocks */
+#define UB953_IND_TARGET_PAT_GEN		0x00
+#define UB953_IND_TARGET_FPD3_TX		0x01
+#define UB953_IND_TARGET_DIE_ID			0x02
+
+#define UB953_IND_PGEN_CTL			0x01
+#define UB953_IND_PGEN_CTL_PGEN_ENABLE		BIT(0)
+#define UB953_IND_PGEN_CFG			0x02
+#define UB953_IND_PGEN_CSI_DI			0x03
+#define UB953_IND_PGEN_LINE_SIZE1		0x04
+#define UB953_IND_PGEN_LINE_SIZE0		0x05
+#define UB953_IND_PGEN_BAR_SIZE1		0x06
+#define UB953_IND_PGEN_BAR_SIZE0		0x07
+#define UB953_IND_PGEN_ACT_LPF1			0x08
+#define UB953_IND_PGEN_ACT_LPF0			0x09
+#define UB953_IND_PGEN_TOT_LPF1			0x0A
+#define UB953_IND_PGEN_TOT_LPF0			0x0B
+#define UB953_IND_PGEN_LINE_PD1			0x0C
+#define UB953_IND_PGEN_LINE_PD0			0x0D
+#define UB953_IND_PGEN_VBP			0x0E
+#define UB953_IND_PGEN_VFP			0x0F
+#define UB953_IND_PGEN_COLOR(n)			(0x10 + (n)) /* n <= 15 */
+
+/* Note: Only sync mode supported for now */
+enum ub953_mode {
+	/* FPD-Link 3 CSI-2 synchronous mode */
+	UB953_MODE_SYNC,
+	/* FPD-Link 3 CSI-2 non-synchronous mode, external ref clock */
+	UB953_MODE_NONSYNC_EXT,
+	/* FPD-Link 3 CSI-2 non-synchronous mode, internal ref clock */
+	UB953_MODE_NONSYNC_INT,
+	/* FPD-Link 3 DVP mode */
+	UB953_MODE_DVP,
+};
+
+struct ub953_hw_data {
+	const char *model;
+	bool ub971;
+};
+
+struct ub953_data {
+	const struct ub953_hw_data	*hw_data;
+
+	struct i2c_client	*client;
+	struct regmap		*regmap;
+
+	u32			num_data_lanes;
+
+	struct gpio_chip	gpio_chip;
+	char			gpio_chip_name[64];
+
+	struct v4l2_subdev	sd;
+	struct media_pad	pads[2];
+
+	struct v4l2_async_notifier	notifier;
+
+	struct v4l2_subdev	*source_sd;
+
+	struct v4l2_ctrl_handler   ctrl_handler;
+
+	u64			enabled_source_streams;
+
+	struct device_node	*tx_ep_np;
+
+	/* lock for register access */
+	struct mutex		reg_lock;
+
+	u8			current_indirect_target;
+
+	struct clk_hw		clkout_clk_hw;
+
+	enum ub953_mode		mode;
+
+	struct ds90ub9xx_platform_data *plat_data;
+
+	/* Have we succefully called i2c_atr_add_adapter() */
+	bool			has_i2c_adapter;
+};
+
+static inline struct ub953_data *sd_to_ub953(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct ub953_data, sd);
+}
+
+/*
+ * HW Access
+ */
+
+static int ub953_read(struct ub953_data *priv, u8 reg, u8 *val)
+{
+	unsigned int v;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = regmap_read(priv->regmap, reg, &v);
+	if (ret)
+		dev_err(&priv->client->dev,
+			"Cannot read register 0x%02x: %d!\n", reg, ret);
+	else
+		*val = v;
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub953_write(struct ub953_data *priv, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = regmap_write(priv->regmap, reg, val);
+	if (ret)
+		dev_err(&priv->client->dev,
+			"Cannot write register 0x%02x: %d!\n", reg, ret);
+
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int _ub953_select_ind_reg_block(struct ub953_data *priv, u8 block)
+{
+	struct device *dev = &priv->client->dev;
+	int ret;
+
+	if (priv->current_indirect_target == block)
+		return 0;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_CTL, block << 2);
+	if (ret) {
+		dev_err(dev, "%s: cannot select indirect target %u (%d)!\n",
+			__func__, block, ret);
+		return ret;
+	}
+
+	priv->current_indirect_target = block;
+
+	return 0;
+}
+
+__maybe_unused
+static int ub953_read_ind(struct ub953_data *priv, u8 block, u8 reg, u8 *val)
+{
+	unsigned int v;
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub953_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v);
+	if (ret)
+		goto out;
+
+	*val = v;
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub953_write_ind(struct ub953_data *priv, u8 block, u8 reg, u8 val)
+{
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub953_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val);
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+static int ub953_write_ind16(struct ub953_data *priv, u8 block, u8 reg, u16 val)
+{
+	int ret;
+
+	mutex_lock(&priv->reg_lock);
+
+	ret = _ub953_select_ind_reg_block(priv, block);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val >> 8);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg + 1);
+	if (ret)
+		goto out;
+
+	ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val & 0xff);
+	if (ret)
+		goto out;
+
+out:
+	mutex_unlock(&priv->reg_lock);
+
+	return ret;
+}
+
+/*
+ * GPIO chip
+ */
+static int ub953_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ub953_data *priv = gpiochip_get_data(gc);
+	int ret;
+	u8 v;
+
+	ret = ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &v);
+	if (ret)
+		return ret;
+
+	if (v & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset))
+		return GPIO_LINE_DIRECTION_IN;
+	else
+		return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int ub953_gpio_direction_in(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ub953_data *priv = gpiochip_get_data(gc);
+
+	return regmap_update_bits(
+		priv->regmap, UB953_REG_GPIO_INPUT_CTRL,
+		UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) |
+			UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset),
+		UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset));
+}
+
+static int ub953_gpio_direction_out(struct gpio_chip *gc, unsigned int offset,
+				    int value)
+{
+	struct ub953_data *priv = gpiochip_get_data(gc);
+	int ret;
+
+	ret = regmap_update_bits(
+		priv->regmap, UB953_REG_LOCAL_GPIO_DATA,
+		UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset),
+		value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : 0);
+
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(
+		priv->regmap, UB953_REG_GPIO_INPUT_CTRL,
+		UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) |
+			UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset),
+		UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset));
+}
+
+static int ub953_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ub953_data *priv = gpiochip_get_data(gc);
+	int ret;
+	u8 v;
+
+	ret = ub953_read(priv, UB953_REG_GPIO_PIN_STS, &v);
+	if (ret)
+		return ret;
+
+	return !!(v & UB953_REG_GPIO_PIN_STS_GPIO_STS(offset));
+}
+
+static void ub953_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	struct ub953_data *priv = gpiochip_get_data(gc);
+
+	regmap_update_bits(
+		priv->regmap, UB953_REG_LOCAL_GPIO_DATA,
+		UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset),
+		value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : 0);
+}
+
+static int ub953_gpio_of_xlate(struct gpio_chip *gc,
+			       const struct of_phandle_args *gpiospec,
+			       u32 *flags)
+{
+	if (flags)
+		*flags = gpiospec->args[1];
+
+	return gpiospec->args[0];
+}
+
+static int ub953_gpiochip_probe(struct ub953_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct gpio_chip *gc = &priv->gpio_chip;
+	int ret;
+
+	/* Set all GPIOs to local input mode */
+	ub953_write(priv, UB953_REG_LOCAL_GPIO_DATA, 0);
+	ub953_write(priv, UB953_REG_GPIO_INPUT_CTRL, 0xf);
+
+	scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s",
+		  dev_name(dev));
+
+	gc->label = priv->gpio_chip_name;
+	gc->parent = dev;
+	gc->owner = THIS_MODULE;
+	gc->base = -1;
+	gc->can_sleep = 1;
+	gc->ngpio = UB953_NUM_GPIOS;
+	gc->get_direction = ub953_gpio_get_direction;
+	gc->direction_input = ub953_gpio_direction_in;
+	gc->direction_output = ub953_gpio_direction_out;
+	gc->get = ub953_gpio_get;
+	gc->set = ub953_gpio_set;
+	gc->of_xlate = ub953_gpio_of_xlate;
+	gc->of_node = priv->client->dev.of_node;
+	gc->of_gpio_n_cells = 2;
+
+	ret = gpiochip_add_data(gc, priv);
+	if (ret) {
+		dev_err(dev, "Failed to add GPIOs: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ub953_gpiochip_remove(struct ub953_data *priv)
+{
+	gpiochip_remove(&priv->gpio_chip);
+}
+
+/*
+ * V4L2
+ */
+
+static int _ub953_set_routing(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_state *state,
+			      struct v4l2_subdev_krouting *routing)
+{
+	const struct v4l2_mbus_framefmt format = {
+		.width = 640,
+		.height = 480,
+		.code = MEDIA_BUS_FMT_UYVY8_2X8,
+		.field = V4L2_FIELD_NONE,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.ycbcr_enc = V4L2_YCBCR_ENC_601,
+		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
+		.xfer_func = V4L2_XFER_FUNC_SRGB,
+	};
+	int ret;
+
+	/*
+	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
+	 * frame desc is made dynamically allocated.
+	 */
+
+	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
+		return -EINVAL;
+
+	ret = v4l2_subdev_routing_validate(sd, routing,
+					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
+	if (ret)
+		return ret;
+
+	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub953_set_routing(struct v4l2_subdev *sd,
+			     struct v4l2_subdev_state *state,
+			     enum v4l2_subdev_format_whence which,
+			     struct v4l2_subdev_krouting *routing)
+{
+	struct ub953_data *priv = sd_to_ub953(sd);
+
+	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
+		return -EBUSY;
+
+	return _ub953_set_routing(sd, state, routing);
+}
+
+static int ub953_get_source_frame_desc(struct ub953_data *priv,
+				       struct v4l2_mbus_frame_desc *desc)
+{
+	struct media_pad *pad;
+	int ret;
+
+	pad = media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]);
+	if (!pad)
+		return -EPIPE;
+
+	ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index,
+			       desc);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub953_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+				struct v4l2_mbus_frame_desc *fd)
+{
+	struct ub953_data *priv = sd_to_ub953(sd);
+	const struct v4l2_subdev_krouting *routing;
+	struct v4l2_mbus_frame_desc source_fd;
+	struct v4l2_subdev_route *route;
+	struct v4l2_subdev_state *state;
+	int ret = 0;
+
+	if (pad != 1) /* first tx pad */
+		return -EINVAL;
+
+	ret = ub953_get_source_frame_desc(priv, &source_fd);
+	if (ret)
+		return ret;
+
+	state = v4l2_subdev_lock_and_get_active_state(sd);
+
+	routing = &state->routing;
+
+	memset(fd, 0, sizeof(*fd));
+
+	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+	for_each_active_route(routing, route) {
+		struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
+		unsigned int j;
+
+		if (route->source_pad != pad)
+			continue;
+
+		for (j = 0; j < source_fd.num_entries; ++j)
+			if (source_fd.entry[j].stream == route->sink_stream) {
+				source_entry = &source_fd.entry[j];
+				break;
+			}
+
+		if (!source_entry) {
+			dev_err(&priv->client->dev,
+				"Failed to find stream from source frame desc\n");
+			ret = -EPIPE;
+			goto out;
+		}
+
+		fd->entry[fd->num_entries].stream = route->source_stream;
+
+		fd->entry[fd->num_entries].flags =
+			V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
+		fd->entry[fd->num_entries].length = source_entry->length;
+		fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode;
+		fd->entry[fd->num_entries].bus.csi2.vc =
+			source_entry->bus.csi2.vc;
+		fd->entry[fd->num_entries].bus.csi2.dt =
+			source_entry->bus.csi2.dt;
+
+		fd->num_entries++;
+	}
+
+out:
+	v4l2_subdev_unlock_state(state);
+
+	return ret;
+}
+
+static int ub953_set_fmt(struct v4l2_subdev *sd,
+			 struct v4l2_subdev_state *state,
+			 struct v4l2_subdev_format *format)
+{
+	struct ub953_data *priv = sd_to_ub953(sd);
+	struct v4l2_mbus_framefmt *fmt;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
+	    priv->enabled_source_streams)
+		return -EBUSY;
+
+	/* No transcoding, source and sink formats must match. */
+	if (format->pad == 1)
+		return v4l2_subdev_get_fmt(sd, state, format);
+
+	/* Set sink format */
+	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
+						  format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	/* Propagate to source format */
+	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+							   format->stream);
+	if (!fmt)
+		return -EINVAL;
+
+	*fmt = format->format;
+
+	return 0;
+}
+
+static int ub953_init_cfg(struct v4l2_subdev *sd,
+			  struct v4l2_subdev_state *state)
+{
+	struct v4l2_subdev_route routes[] = {
+		{
+			.sink_pad = 0,
+			.sink_stream = 0,
+			.source_pad = 1,
+			.source_stream = 0,
+			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+		},
+	};
+
+	struct v4l2_subdev_krouting routing = {
+		.num_routes = ARRAY_SIZE(routes),
+		.routes = routes,
+	};
+
+	return _ub953_set_routing(sd, state, &routing);
+}
+
+static int ub953_log_status(struct v4l2_subdev *sd)
+{
+	struct ub953_data *priv = sd_to_ub953(sd);
+	struct device *dev = &priv->client->dev;
+	u8 v = 0, v1 = 0, v2 = 0;
+	unsigned int i;
+	char id[7];
+	u8 gpio_local_data;
+	u8 gpio_input_ctrl;
+	u8 gpio_pin_sts;
+
+	for (i = 0; i < 6; ++i)
+		ub953_read(priv, UB953_REG_FPD3_RX_ID(i), &id[i]);
+	id[6] = 0;
+
+	dev_info(dev, "ID '%s'\n", id);
+
+	ub953_read(priv, UB953_REG_GENERAL_STATUS, &v);
+	dev_info(dev, "GENERAL_STATUS %#x\n", v);
+
+	ub953_read(priv, UB953_REG_CRC_ERR_CNT1, &v1);
+	ub953_read(priv, UB953_REG_CRC_ERR_CNT2, &v2);
+	dev_info(dev, "CRC error count %u\n", v1 | (v2 << 8));
+
+	ub953_read(priv, UB953_REG_CSI_ERR_CNT, &v);
+	dev_info(dev, "CSI error count %u\n", v);
+
+	ub953_read(priv, UB953_REG_CSI_ERR_STATUS, &v);
+	dev_info(dev, "CSI_ERR_STATUS %#x\n", v);
+
+	ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v);
+	dev_info(dev, "CSI_ERR_DLANE01 %#x\n", v);
+
+	ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v);
+	dev_info(dev, "CSI_ERR_DLANE23 %#x\n", v);
+
+	ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v);
+	dev_info(dev, "CSI_ERR_CLK_LANE %#x\n", v);
+
+	ub953_read(priv, UB953_REG_CSI_PKT_HDR_VC_ID, &v);
+	dev_info(dev, "CSI packet header VC %u ID %u\n", v >> 6, v & 0x3f);
+
+	ub953_read(priv, UB953_REG_PKT_HDR_WC_LSB, &v1);
+	ub953_read(priv, UB953_REG_PKT_HDR_WC_MSB, &v2);
+	dev_info(dev, "CSI packet header WC %u\n", (v2 << 8) | v1);
+
+	ub953_read(priv, UB953_REG_CSI_ECC, &v);
+	dev_info(dev, "CSI ECC %#x\n", v);
+
+	ub953_read(priv, UB953_REG_LOCAL_GPIO_DATA, &gpio_local_data);
+	ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &gpio_input_ctrl);
+	ub953_read(priv, UB953_REG_GPIO_PIN_STS, &gpio_pin_sts);
+
+	for (i = 0; i < UB953_NUM_GPIOS; ++i) {
+		dev_info(dev,
+			 "GPIO%u: remote: %u is_input: %u is_output: %u val: %u sts: %u\n",
+			 i,
+			 !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(i)),
+			 !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(i)),
+			 !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_OUT_EN(i)),
+			 !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(i)),
+			 !!(gpio_pin_sts & UB953_REG_GPIO_PIN_STS_GPIO_STS(i)));
+	}
+
+	return 0;
+}
+
+static int ub953_enable_streams(struct v4l2_subdev *sd,
+				struct v4l2_subdev_state *state, u32 pad,
+				u64 streams_mask)
+{
+	struct ub953_data *priv = sd_to_ub953(sd);
+	struct media_pad *remote_pad;
+	u64 sink_streams;
+	int ret;
+
+	if (streams_mask & priv->enabled_source_streams)
+		return -EALREADY;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(
+		state, UB953_PAD_SOURCE, UB953_PAD_SINK, &streams_mask);
+
+	remote_pad = media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]);
+
+	ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index,
+					 sink_streams);
+	if (ret)
+		return ret;
+
+	priv->enabled_source_streams |= streams_mask;
+
+	return 0;
+}
+
+static int ub953_disable_streams(struct v4l2_subdev *sd,
+				 struct v4l2_subdev_state *state, u32 pad,
+				 u64 streams_mask)
+{
+	struct ub953_data *priv = sd_to_ub953(sd);
+	struct media_pad *remote_pad;
+	int ret;
+	u64 sink_streams;
+
+	if ((streams_mask & priv->enabled_source_streams) != streams_mask)
+		return -EALREADY;
+
+	sink_streams = v4l2_subdev_state_xlate_streams(
+		state, UB953_PAD_SOURCE, UB953_PAD_SINK, &streams_mask);
+
+	remote_pad = media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]);
+
+	ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index,
+					  sink_streams);
+	if (ret)
+		return ret;
+
+	priv->enabled_source_streams &= ~streams_mask;
+
+	return 0;
+}
+
+static const struct v4l2_subdev_pad_ops ub953_pad_ops = {
+	.enable_streams = ub953_enable_streams,
+	.disable_streams = ub953_disable_streams,
+	.set_routing = ub953_set_routing,
+	.get_frame_desc = ub953_get_frame_desc,
+	.get_fmt = v4l2_subdev_get_fmt,
+	.set_fmt = ub953_set_fmt,
+	.init_cfg = ub953_init_cfg,
+};
+
+static const struct v4l2_subdev_core_ops ub953_subdev_core_ops = {
+	.log_status		= ub953_log_status,
+	.subscribe_event	= v4l2_ctrl_subdev_subscribe_event,
+	.unsubscribe_event	= v4l2_event_subdev_unsubscribe,
+};
+
+static const struct v4l2_subdev_ops ub953_subdev_ops = {
+	.core = &ub953_subdev_core_ops,
+	.pad = &ub953_pad_ops,
+};
+
+static const struct media_entity_operations ub953_entity_ops = {
+	.link_validate = v4l2_subdev_link_validate,
+};
+
+enum {
+	TEST_PATTERN_DISABLED = 0,
+	TEST_PATTERN_V_COLOR_BARS_1,
+	TEST_PATTERN_V_COLOR_BARS_2,
+	TEST_PATTERN_V_COLOR_BARS_4,
+	TEST_PATTERN_V_COLOR_BARS_8,
+};
+
+static const char *const ub953_tpg_qmenu[] = {
+	"Disabled",
+	"1 vertical color bar",
+	"2 vertical color bars",
+	"4 vertical color bars",
+	"8 vertical color bars",
+};
+
+static void ub953_enable_tpg(struct ub953_data *priv, int tpg_num)
+{
+	struct v4l2_subdev *sd = &priv->sd;
+	struct v4l2_subdev_state *state;
+	struct v4l2_mbus_framefmt *fmt;
+	u8 vbp, vfp;
+	u16 blank_lines;
+	u16 width;
+	u16 height;
+
+	u16 bytespp = 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */
+	u8 cbars_idx = tpg_num - TEST_PATTERN_V_COLOR_BARS_1;
+	u8 num_cbars = 1 << cbars_idx;
+
+	u16 line_size; /* Line size [bytes] */
+	u16 bar_size; /* cbar size [bytes] */
+	u16 act_lpf; /* active lines/frame */
+	u16 tot_lpf; /* tot lines/frame */
+	u16 line_pd; /* Line period in 10-ns units */
+
+	u16 fps = 30;
+
+	vbp = 33;
+	vfp = 10;
+	blank_lines = vbp + vfp + 2; /* total blanking lines */
+
+	state = v4l2_subdev_get_locked_active_state(sd);
+
+	fmt = v4l2_subdev_state_get_stream_format(state, UB953_PAD_SOURCE, 0);
+
+	width = fmt->width;
+	height = fmt->height;
+
+	line_size = width * bytespp;
+	bar_size = line_size / num_cbars;
+	act_lpf = height;
+	tot_lpf = act_lpf + blank_lines;
+	line_pd = 100000000 / fps / tot_lpf;
+
+	ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CTL,
+			UB953_IND_PGEN_CTL_PGEN_ENABLE);
+
+	/* YUV422 8bit: 2 bytes/block, CSI-2 data type 0x1e */
+	ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CFG,
+			cbars_idx << 4 | 0x2);
+	ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CSI_DI,
+			0x1e);
+
+	ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN,
+			  UB953_IND_PGEN_LINE_SIZE1, line_size);
+	ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN,
+			  UB953_IND_PGEN_BAR_SIZE1, bar_size);
+	ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN,
+			  UB953_IND_PGEN_ACT_LPF1, act_lpf);
+	ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN,
+			  UB953_IND_PGEN_TOT_LPF1, tot_lpf);
+	ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN,
+			  UB953_IND_PGEN_LINE_PD1, line_pd);
+	ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_VBP,
+			vbp);
+	ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_VFP,
+			vfp);
+}
+
+static void ub953_disable_tpg(struct ub953_data *priv)
+{
+	ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CTL,
+			0x0);
+}
+
+static int ub953_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct ub953_data *priv =
+		container_of(ctrl->handler, struct ub953_data, ctrl_handler);
+
+	switch (ctrl->id) {
+	case V4L2_CID_TEST_PATTERN:
+		if (ctrl->val == 0)
+			ub953_disable_tpg(priv);
+		else
+			ub953_enable_tpg(priv, ctrl->val);
+		break;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops ub953_ctrl_ops = {
+	.s_ctrl = ub953_s_ctrl,
+};
+
+static int ub953_notify_bound(struct v4l2_async_notifier *notifier,
+			      struct v4l2_subdev *source_subdev,
+			      struct v4l2_async_subdev *asd)
+{
+	struct ub953_data *priv = sd_to_ub953(notifier->sd);
+	struct device *dev = &priv->client->dev;
+	unsigned int src_pad;
+	int ret;
+
+	dev_dbg(dev, "Bind %s\n", source_subdev->name);
+
+	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
+					  source_subdev->fwnode,
+					  MEDIA_PAD_FL_SOURCE);
+	if (ret < 0) {
+		dev_err(dev, "Failed to find pad for %s\n",
+			source_subdev->name);
+		return ret;
+	}
+
+	priv->source_sd = source_subdev;
+	src_pad = ret;
+
+	ret = media_create_pad_link(
+		&source_subdev->entity, src_pad, &priv->sd.entity, 0,
+		MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+	if (ret) {
+		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
+			source_subdev->name, src_pad, priv->sd.name);
+		return ret;
+	}
+
+	dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad);
+
+	dev_dbg(dev, "All subdevs bound\n");
+
+	return 0;
+}
+
+static void ub953_notify_unbind(struct v4l2_async_notifier *notifier,
+				struct v4l2_subdev *source_subdev,
+				struct v4l2_async_subdev *asd)
+{
+	struct ub953_data *priv = sd_to_ub953(notifier->sd);
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "Unbind %s\n", source_subdev->name);
+}
+
+static const struct v4l2_async_notifier_operations ub953_notify_ops = {
+	.bound = ub953_notify_bound,
+	.unbind = ub953_notify_unbind,
+};
+
+static int ub953_v4l2_notifier_register(struct ub953_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct v4l2_async_subdev *asd;
+	struct device_node *ep_node;
+	int ret;
+
+	dev_dbg(dev, "register async notif\n");
+
+	ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+	if (!ep_node) {
+		dev_err(dev, "No graph endpoint\n");
+		return -ENODEV;
+	}
+
+	v4l2_async_nf_init(&priv->notifier);
+
+	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier,
+					      of_fwnode_handle(ep_node),
+					      struct v4l2_async_subdev);
+
+	of_node_put(ep_node);
+
+	if (IS_ERR(asd)) {
+		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return PTR_ERR(asd);
+	}
+
+	priv->notifier.ops = &ub953_notify_ops;
+
+	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
+	if (ret) {
+		dev_err(dev, "Failed to register subdev_notifier");
+		v4l2_async_nf_cleanup(&priv->notifier);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void ub953_v4l2_notifier_unregister(struct ub953_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+
+	dev_dbg(dev, "Unregister async notif\n");
+
+	v4l2_async_nf_unregister(&priv->notifier);
+	v4l2_async_nf_cleanup(&priv->notifier);
+}
+
+/*
+ * Probing
+ */
+
+static int ub953_i2c_master_init(struct ub953_data *priv)
+{
+	/* i2c fast mode */
+	u32 ref = 26250000;
+	u32 scl_high = 915; /* ns */
+	u32 scl_low = 1641; /* ns */
+	int ret;
+
+	scl_high = div64_u64((u64)scl_high * ref, 1000000000) - 5;
+	scl_low = div64_u64((u64)scl_low * ref, 1000000000) - 5;
+
+	ret = ub953_write(priv, UB953_REG_SCL_HIGH_TIME, scl_high);
+	if (ret)
+		return ret;
+
+	ret = ub953_write(priv, UB953_REG_SCL_LOW_TIME, scl_low);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int ub953_parse_dt(struct ub953_data *priv)
+{
+	struct device_node *np = priv->client->dev.of_node;
+	struct device *dev = &priv->client->dev;
+	struct device_node *ep_np;
+	int ret;
+
+	if (!np) {
+		dev_err(dev, "OF: no device tree node!\n");
+		return -ENOENT;
+	}
+
+	ep_np = of_graph_get_endpoint_by_regs(np, 0, 0);
+	if (!ep_np) {
+		dev_err(dev, "OF: no endpoint\n");
+		return -ENOENT;
+	}
+
+	ret = of_property_count_u32_elems(ep_np, "data-lanes");
+	if (ret <= 0) {
+		dev_err(dev, "OF: failed to parse data-lanes: %d\n", ret);
+		return ret;
+	}
+
+	if (ret != 1 && ret != 2 && ret != 4) {
+		dev_err(dev, "OF: bad number of data-lanes: %d\n", ret);
+		return -EINVAL;
+	}
+
+	priv->num_data_lanes = ret;
+
+	return 0;
+}
+
+static const struct regmap_config ub953_regmap_config = {
+	.name = "ds90ub953",
+	.reg_bits = 8,
+	.val_bits = 8,
+	.reg_format_endian = REGMAP_ENDIAN_DEFAULT,
+	.val_format_endian = REGMAP_ENDIAN_DEFAULT,
+};
+
+static u64 ub953_get_fc_rate(struct ub953_data *priv)
+{
+	if (priv->hw_data->ub971) {
+		switch (priv->mode) {
+		case UB953_MODE_SYNC:
+			return priv->plat_data->bc_rate * 160ull;
+		default:
+			/* Not supported */
+			return 0;
+		}
+	} else {
+		switch (priv->mode) {
+		case UB953_MODE_SYNC:
+			return priv->plat_data->bc_rate / 2 * 160ull;
+		default:
+			/* Not supported */
+			return 0;
+		}
+	}
+}
+
+static unsigned long ub953_calc_clkout_ub953(struct ub953_data *priv,
+					     unsigned long target, u64 fc,
+					     u8 *hs_div, u8 *m, u8 *n)
+{
+	/*
+	 * We always use 4 as a pre-divider (HS_CLK_DIV = 2).
+	 *
+	 * According to the datasheet:
+	 * - "HS_CLK_DIV typically should be set to either 16, 8, or 4 (default)."
+	 * - "if it is not possible to have an integer ratio of N/M, it is best to
+	 *    select a smaller value for HS_CLK_DIV.
+	 *
+	 * For above reasons the default HS_CLK_DIV seems the best in the average
+	 * case. Use always that value to keep the code simple.
+	 */
+	static const unsigned long hs_clk_div = 4;
+
+	u64 fc_divided;
+	unsigned long mul, div;
+	unsigned long res;
+
+	/* clkout = fc / hs_clk_div * m / n */
+
+	fc_divided = div_u64(fc, hs_clk_div);
+
+	rational_best_approximation(target, fc_divided, (1 << 5) - 1,
+				    (1 << 8) - 1, &mul, &div);
+
+	res = div_u64(fc_divided * mul, div);
+
+	*hs_div = hs_clk_div;
+	*m = mul;
+	*n = div;
+
+	return res;
+}
+
+static unsigned long ub953_calc_clkout_ub971(struct ub953_data *priv,
+					     unsigned long target, u64 fc,
+					     u8 *m, u8 *n)
+{
+	u64 fc_divided;
+	unsigned long mul, div;
+	unsigned long res;
+
+	/* clkout = fc * m / (8 * n) */
+
+	fc_divided = div_u64(fc, 8);
+
+	rational_best_approximation(target, fc_divided, (1 << 5) - 1,
+				    (1 << 8) - 1, &mul, &div);
+
+	res = div_u64(fc_divided * mul, div);
+
+	*m = mul;
+	*n = div;
+
+	return res;
+}
+
+static unsigned long ub953_clkout_recalc_rate(struct clk_hw *hw,
+					      unsigned long parent_rate)
+{
+	struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw);
+	struct device *dev = &priv->client->dev;
+	u8 ctrl0, ctrl1;
+	u32 mul, div;
+	u64 fc_rate;
+	u32 hs_clk_div;
+	u64 rate;
+	int ret;
+
+	ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL0, &ctrl0);
+	if (ret) {
+		dev_err(dev, "Failed to read CLKOUT_CTRL0: %d\n", ret);
+		return 0;
+	}
+
+	ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL1, &ctrl1);
+	if (ret) {
+		dev_err(dev, "Failed to read CLKOUT_CTRL1: %d\n", ret);
+		return 0;
+	}
+
+	fc_rate = ub953_get_fc_rate(priv);
+
+	if (priv->hw_data->ub971) {
+		mul = ctrl0 & 0x1f;
+		div = ctrl1;
+
+		if (div == 0)
+			return 0;
+
+		rate = div_u64(fc_rate * mul, 8 * div);
+
+		dev_dbg(dev, "clkout: fc rate %llu, mul %u, div %u = %llu\n",
+			fc_rate, mul, div, rate);
+	} else {
+		mul = ctrl0 & 0x1f;
+		hs_clk_div = 1 << (ctrl0 >> 5);
+		div = ctrl1;
+
+		if (div == 0)
+			return 0;
+
+		rate = div_u64(div_u64(fc_rate, hs_clk_div) * mul, div);
+
+		dev_dbg(dev,
+			"clkout: fc rate %llu, hs_clk_div %u, mul %u, div %u = %llu\n",
+			fc_rate, hs_clk_div, mul, div, rate);
+	}
+
+	return rate;
+}
+
+static long ub953_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long *parent_rate)
+{
+	struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw);
+	struct device *dev = &priv->client->dev;
+	unsigned long res;
+	u64 fc_rate;
+	u8 hs_div, m, n;
+
+	fc_rate = ub953_get_fc_rate(priv);
+
+	if (priv->hw_data->ub971) {
+		res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n);
+
+		dev_dbg(dev,
+			"%s %llu * %u / (8 * %u) = %lu (requested %lu)",
+			__func__, fc_rate, m, n, res, rate);
+	} else {
+		res = ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n);
+
+		dev_dbg(dev,
+			"%s %llu / %u * %u / %u = %lu (requested %lu)",
+			__func__, fc_rate, hs_div, m, n, res, rate);
+	}
+
+	return res;
+}
+
+static int ub953_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
+				 unsigned long parent_rate)
+{
+	struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw);
+	u64 fc_rate;
+	u8 hs_div, m, n;
+	unsigned long res;
+
+	fc_rate = ub953_get_fc_rate(priv);
+
+	if (priv->hw_data->ub971) {
+		res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n);
+
+		ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m);
+		ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n);
+	} else {
+		res = ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n);
+
+		ub953_write(priv, UB953_REG_CLKOUT_CTRL0, (__ffs(hs_div) << 5) | m);
+		ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n);
+	}
+
+	dev_dbg(&priv->client->dev,
+		"%s %lu (requested %lu)\n", __func__, res, rate);
+
+	return 0;
+}
+
+static const struct clk_ops ub953_clkout_ops = {
+	.recalc_rate	= ub953_clkout_recalc_rate,
+	.round_rate	= ub953_clkout_round_rate,
+	.set_rate	= ub953_clkout_set_rate,
+};
+
+static void ub953_init_clkout_ub953(struct ub953_data *priv)
+{
+	u64 fc_rate;
+	u8 hs_div, m, n;
+
+	fc_rate = ub953_get_fc_rate(priv);
+
+	ub953_calc_clkout_ub953(priv, 25000000, fc_rate, &hs_div, &m, &n);
+
+	ub953_write(priv, UB953_REG_CLKOUT_CTRL0, (__ffs(hs_div) << 5) | m);
+	ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n);
+}
+
+static void ub953_init_clkout_ub971(struct ub953_data *priv)
+{
+	u64 fc_rate;
+	u8 m, n;
+
+	fc_rate = ub953_get_fc_rate(priv);
+
+	ub953_calc_clkout_ub971(priv, 25000000, fc_rate, &m, &n);
+
+	ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m);
+	ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n);
+}
+
+static int ub953_register_clkout(struct ub953_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	const struct clk_init_data init = {
+		.name = kasprintf(GFP_KERNEL, "ds90%s.%s.clk_out",
+				  priv->hw_data->model, dev_name(dev)),
+		.ops = &ub953_clkout_ops,
+	};
+	int ret;
+
+	/* Initialize clkout to 25MHz by default */
+	if (priv->hw_data->ub971)
+		ub953_init_clkout_ub971(priv);
+	else
+		ub953_init_clkout_ub953(priv);
+
+	priv->clkout_clk_hw.init = &init;
+
+	ret = devm_clk_hw_register(dev, &priv->clkout_clk_hw);
+	kfree(init.name);
+	if (ret)
+		return dev_err_probe(dev, ret, "Cannot register clock HW\n");
+
+	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
+					  &priv->clkout_clk_hw);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Cannot add OF clock provider\n");
+
+	return 0;
+}
+
+static int ub953_add_i2c_adapter(struct ub953_data *priv)
+{
+	struct device *dev = &priv->client->dev;
+	struct fwnode_handle *i2c_handle;
+	int ret;
+
+	i2c_handle = device_get_named_child_node(dev, "i2c");
+	if (!i2c_handle)
+		return 0;
+
+	ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port,
+				  i2c_handle);
+
+	fwnode_handle_put(i2c_handle);
+
+	if (ret)
+		return ret;
+
+	priv->has_i2c_adapter = true;
+
+	return 0;
+}
+
+static void ub953_remove_i2c_adapter(struct ub953_data *priv)
+{
+	if (priv->has_i2c_adapter)
+		i2c_atr_del_adapter(priv->plat_data->atr,
+				    priv->plat_data->port);
+}
+
+static int ub953_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct ub953_data *priv;
+	int ret;
+	u8 v;
+	bool mode_override;
+
+	dev_dbg(dev, "probing, addr 0x%02x\n", client->addr);
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENOMEM;
+
+	priv->client = client;
+
+	priv->hw_data = of_device_get_match_data(dev);
+	if (!priv->hw_data)
+		return -ENODEV;
+
+	priv->plat_data = dev_get_platdata(&client->dev);
+	if (!priv->plat_data) {
+		dev_err(dev, "Platform data missing\n");
+		return -ENODEV;
+	}
+
+	mutex_init(&priv->reg_lock);
+
+	/*
+	 * Initialize to invalid values so that the first reg writes will
+	 * configure the target.
+	 */
+	priv->current_indirect_target = 0xff;
+
+	priv->regmap = devm_regmap_init_i2c(client, &ub953_regmap_config);
+	if (IS_ERR(priv->regmap)) {
+		dev_err(dev, "Failed to init regmap\n");
+		ret = PTR_ERR(priv->regmap);
+		goto err_mutex_destroy;
+	}
+
+	ret = ub953_parse_dt(priv);
+	if (ret)
+		goto err_mutex_destroy;
+
+	ret = ub953_read(priv, UB953_REG_MODE_SEL, &v);
+	if (ret)
+		goto err_mutex_destroy;
+
+	if (!(v & BIT(3))) {
+		dev_err(dev, "Mode value not stabilized\n");
+		ret = -ENODEV;
+		goto err_mutex_destroy;
+	}
+
+	mode_override = v & BIT(4);
+
+	switch (v & 0x7) {
+	case 0:
+		priv->mode = UB953_MODE_SYNC;
+		break;
+	case 2:
+		priv->mode = UB953_MODE_NONSYNC_EXT;
+		break;
+	case 3:
+		priv->mode = UB953_MODE_NONSYNC_INT;
+		break;
+	case 5:
+		priv->mode = UB953_MODE_DVP;
+		break;
+	default:
+		dev_err(dev, "Illegal mode in mode register\n");
+		ret = -ENODEV;
+		goto err_mutex_destroy;
+	}
+
+	dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap",
+		priv->mode);
+
+	if (priv->mode != UB953_MODE_SYNC) {
+		dev_err(dev, "Only synchronous mode supported\n");
+		ret = -ENODEV;
+		goto err_mutex_destroy;
+	}
+
+	ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v);
+	if (ret) {
+		dev_err(dev, "Failed to read revision: %d", ret);
+		goto err_mutex_destroy;
+	}
+
+	dev_info(dev, "Found %s rev/mask %#04x\n", priv->hw_data->model, v);
+
+	ret = ub953_read(priv, UB953_REG_GENERAL_CFG, &v);
+	if (ret)
+		goto err_mutex_destroy;
+
+	dev_dbg(dev, "i2c strap setting %s V\n", (v & 1) ? "1.8" : "3.3");
+
+	ret = ub953_i2c_master_init(priv);
+	if (ret) {
+		dev_err(dev, "i2c init failed: %d\n", ret);
+		goto err_mutex_destroy;
+	}
+
+	ret = ub953_gpiochip_probe(priv);
+	if (ret) {
+		dev_err(dev, "Failed to init gpiochip\n");
+		goto err_mutex_destroy;
+	}
+
+	ub953_write(priv, UB953_REG_GENERAL_CFG,
+		    (1 << 6) | /* continuous clk */
+		    ((priv->num_data_lanes - 1) << 4) |
+		    (1 << 1)); /* CRC TX gen */
+
+	ret = ub953_register_clkout(priv);
+	if (ret) {
+		dev_err(dev, "Failed to register clkout\n");
+		goto err_gpiochip_remove;
+	}
+
+	v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops);
+
+	v4l2_ctrl_handler_init(&priv->ctrl_handler,
+			       ARRAY_SIZE(ub953_tpg_qmenu) - 1);
+	priv->sd.ctrl_handler = &priv->ctrl_handler;
+
+	v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub953_ctrl_ops,
+				     V4L2_CID_TEST_PATTERN,
+				     ARRAY_SIZE(ub953_tpg_qmenu) - 1, 0, 0,
+				     ub953_tpg_qmenu);
+
+	if (priv->ctrl_handler.error) {
+		ret = priv->ctrl_handler.error;
+		goto err_gpiochip_remove;
+	}
+
+	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
+		V4L2_SUBDEV_FL_STREAMS;
+	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+	priv->sd.entity.ops = &ub953_entity_ops;
+
+	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
+	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
+	if (ret) {
+		dev_err(dev, "Failed to init pads\n");
+		goto err_remove_ctrls;
+	}
+
+	priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0);
+	priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np);
+
+	priv->sd.state_lock = priv->sd.ctrl_handler->lock;
+
+	ret = v4l2_subdev_init_finalize(&priv->sd);
+	if (ret)
+		goto err_entity_cleanup;
+
+	ret = ub953_v4l2_notifier_register(priv);
+	if (ret) {
+		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
+		goto err_free_state;
+	}
+
+	ret = v4l2_async_register_subdev(&priv->sd);
+	if (ret) {
+		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
+		goto err_unreg_notif;
+	}
+
+	ret = ub953_add_i2c_adapter(priv);
+	if (ret) {
+		dev_err(dev, "failed to add remote i2c adapter\n");
+		goto err_unreg_async_subdev;
+	}
+
+	dev_dbg(dev, "Successfully probed\n");
+
+	return 0;
+
+err_unreg_async_subdev:
+	v4l2_async_unregister_subdev(&priv->sd);
+err_unreg_notif:
+	ub953_v4l2_notifier_unregister(priv);
+err_free_state:
+	v4l2_subdev_cleanup(&priv->sd);
+err_entity_cleanup:
+	if (priv->tx_ep_np)
+		of_node_put(priv->tx_ep_np);
+
+	media_entity_cleanup(&priv->sd.entity);
+err_remove_ctrls:
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+err_gpiochip_remove:
+	ub953_gpiochip_remove(priv);
+err_mutex_destroy:
+	mutex_destroy(&priv->reg_lock);
+
+	return ret;
+}
+
+static void ub953_remove(struct i2c_client *client)
+{
+	struct v4l2_subdev *sd = i2c_get_clientdata(client);
+	struct ub953_data *priv = sd_to_ub953(sd);
+
+	dev_dbg(&client->dev, "Removing\n");
+
+	ub953_remove_i2c_adapter(priv);
+
+	v4l2_async_unregister_subdev(&priv->sd);
+
+	ub953_v4l2_notifier_unregister(priv);
+
+	v4l2_subdev_cleanup(&priv->sd);
+
+	of_node_put(priv->tx_ep_np);
+
+	media_entity_cleanup(&priv->sd.entity);
+
+	v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+	ub953_gpiochip_remove(priv);
+	mutex_destroy(&priv->reg_lock);
+}
+
+static const struct ub953_hw_data ds90ub953_hw = {
+	.model = "ub953",
+};
+
+static const struct ub953_hw_data ds90ub971_hw = {
+	.model = "ub971",
+	.ub971 = true,
+};
+
+static const struct i2c_device_id ub953_id[] = {
+	{ "ds90ub953-q1", 0 },
+	{ "ds90ub971-q1", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, ub953_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id ub953_dt_ids[] = {
+	{ .compatible = "ti,ds90ub953-q1", .data = &ds90ub953_hw },
+	{ .compatible = "ti,ds90ub971-q1", .data = &ds90ub971_hw },
+	{}
+};
+MODULE_DEVICE_TABLE(of, ub953_dt_ids);
+#endif
+
+static struct i2c_driver ds90ub953_driver = {
+	.probe_new	= ub953_probe,
+	.remove		= ub953_remove,
+	.id_table	= ub953_id,
+	.driver = {
+		.name	= "ds90ub953",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(ub953_dt_ids),
+	},
+};
+
+module_i2c_driver(ds90ub953_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments DS90UB953 serializer driver");
+MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
+MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 0/8] i2c-atr and FPDLink
  2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
                   ` (7 preceding siblings ...)
  2022-12-08 10:40 ` [PATCH v5 8/8] media: i2c: add DS90UB953 driver Tomi Valkeinen
@ 2022-12-08 10:42 ` Tomi Valkeinen
  2022-12-08 12:26   ` Andy Shevchenko
  8 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 10:42 UTC (permalink / raw)
  To: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen
  Cc: Mauro Carvalho Chehab, Peter Rosin, Liam Girdwood, Mark Brown,
	Sakari Ailus, Laurent Pinchart, Michael Tretter, Shawn Tu,
	Hans Verkuil, Mike Pagano, Krzysztof Hałasa, Marek Vasut

On 08/12/2022 12:39, Tomi Valkeinen wrote:
> Hi,
> 
> You can find v4 of the series from:
> 
> https://lore.kernel.org/all/20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com/
> 
> You can find a longer introduction of the series in that version's cover
> letter.
> 
> There has been a lot of changes to the DT bindings and the i2c-atr code in this
> version, but they are all fixes and cleanups, no architectural changes. The
> FPDLink drivers have not been changed, except to reflect the changes in the
> DT.
> 
> I will send a diff between v4 and v5 to give a better idea of the changes.

diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
index 1e0d66704968..3a5b34c6bb64 100644
--- a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
@@ -9,7 +9,7 @@ title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
  maintainers:
    - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
  
-description: |
+description:
    The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
  
  properties:
@@ -38,23 +38,21 @@ properties:
      $ref: /schemas/graph.yaml#/properties/ports
  
      properties:
-
        port@0:
          $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
          description: CSI-2 input port
  
          properties:
            endpoint:
              $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
  
        port@1:
-        $ref: /schemas/graph.yaml#/$defs/port-base
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
          description: FPD-Link 3 output port
  
-        properties:
-          endpoint:
-            $ref: /schemas/media/video-interfaces.yaml#
-
    i2c:
      $ref: /schemas/i2c/i2c-controller.yaml#
      unevaluatedProperties: false
@@ -106,15 +104,11 @@ examples:
          #address-cells = <1>;
          #size-cells = <0>;
  
-        sensor@37 {
-          compatible = "ovti,ov10635";
-          reg = <0x37>;
+        sensor@48 {
+          compatible = "aptina,mt9v111";
+          reg = <0x48>;
  
            clocks = <&fixed_clock>;
-          clock-names = "xvclk";
-
-          reset-gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
-          powerdown-gpios = <&gpio5 11 GPIO_ACTIVE_HIGH>;
  
            port {
              sensor_out: endpoint {
diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
index 355f7d6a19fe..fd7d25d93e2c 100644
--- a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
@@ -9,7 +9,7 @@ title: Texas Instruments DS90UB953 FPD-Link 3 Serializer
  maintainers:
    - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
  
-description: |
+description:
    The TI DS90UB953 is an FPD-Link 3 video serializer for MIPI CSI-2.
  
  properties:
@@ -30,23 +30,21 @@ properties:
      $ref: /schemas/graph.yaml#/properties/ports
  
      properties:
-
        port@0:
          $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
          description: CSI-2 input port
  
          properties:
            endpoint:
              $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
  
        port@1:
-        $ref: /schemas/graph.yaml#/$defs/port-base
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
          description: FPD-Link 3 output port
  
-        properties:
-          endpoint:
-            $ref: /schemas/media/video-interfaces.yaml#
-
    i2c:
      $ref: /schemas/i2c/i2c-controller.yaml#
      unevaluatedProperties: false
@@ -97,17 +95,11 @@ examples:
          #address-cells = <1>;
          #size-cells = <0>;
  
-        sensor@21 {
-          compatible = "sony,imx390";
-          reg = <0x21>;
-
-          clocks = <&clk_cam_27M>;
-          clock-names = "inck";
+        sensor@1a {
+          compatible = "sony,imx274";
+          reg = <0x1a>;
  
-          xclr-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
-          error0-gpios = <&serializer 1 GPIO_ACTIVE_HIGH>;
-          error1-gpios = <&serializer 2 GPIO_ACTIVE_HIGH>;
-          comready-gpios = <&serializer 3 GPIO_ACTIVE_HIGH>;
+          reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
  
            port {
              sensor_out: endpoint {
diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
index 4456d9b3e2c7..d8b5e219d420 100644
--- a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
@@ -9,7 +9,7 @@ title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
  maintainers:
    - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
  
-description: |
+description:
    The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
    forwarding.
  
@@ -24,10 +24,6 @@ properties:
      description:
        i2c addresses for the deserializer and the serializers
  
-  reg-names:
-    items:
-      - const: main
-
    clocks:
      maxItems: 1
      description:
@@ -45,7 +41,12 @@ properties:
    i2c-alias-pool:
      $ref: /schemas/types.yaml#/definitions/uint16-array
      description:
-      i2c alias pool for remote devices.
+      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
+      used to access the remote peripherals. The addresses must be available,
+      not used by any other peripheral. Each remote peripheral is assigned an
+      alias from the pool, and transactions to that address will be forwarded
+      to the remote peripheral, with the address translated to the remote
+      peripheral's real address.
  
    links:
      type: object
@@ -58,7 +59,7 @@ properties:
        '#size-cells':
          const: 0
  
-      manual-strobe:
+      ti,manual-strobe:
          type: boolean
          description:
            Enable manual strobe position and EQ level
@@ -73,12 +74,12 @@ properties:
              maxItems: 1
  
            i2c-alias:
-            description: |
+            description:
                The i2c address used for the serializer. Transactions to this
                address on the i2c bus where the deserializer resides are
                forwarded to the serializer.
  
-          rx-mode:
+          ti,rx-mode:
              $ref: /schemas/types.yaml#/definitions/uint32
              enum:
                - 0 # RAW10
@@ -88,23 +89,23 @@ properties:
                - 4 # CSI2 NON-SYNC
              description: FPD-Link Input Mode
  
-          cdr-mode:
+          ti,cdr-mode:
              $ref: /schemas/types.yaml#/definitions/uint32
              enum:
                - 0 # FPD3
                - 1 # FPD4
              description: FPD-Link CDR Mode
  
-          strobe-pos:
+          ti,strobe-pos:
              $ref: /schemas/types.yaml#/definitions/int32
              minimum: -13
              maximum: 13
-            description: Manual strobe position, from -13 to 13
+            description: Manual strobe position
  
-          eq-level:
+          ti,eq-level:
              $ref: /schemas/types.yaml#/definitions/uint32
              maximum: 14
-            description: Manual EQ level, from 0 to 14
+            description: Manual EQ level
  
            serializer:
              type: object
@@ -113,7 +114,7 @@ properties:
          required:
            - reg
            - i2c-alias
-          - rx-mode
+          - ti,rx-mode
            - serializer
  
    ports:
@@ -121,65 +122,51 @@ properties:
  
      properties:
        port@0:
-        $ref: /schemas/graph.yaml#/$defs/port-base
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
          description: FPD-Link input 0
  
-        properties:
-          endpoint:
-            $ref: /schemas/media/video-interfaces.yaml#
-
        port@1:
-        $ref: /schemas/graph.yaml#/$defs/port-base
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
          description: FPD-Link input 1
  
-        properties:
-          endpoint:
-            $ref: /schemas/media/video-interfaces.yaml#
-
        port@2:
-        $ref: /schemas/graph.yaml#/$defs/port-base
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
          description: FPD-Link input 2
  
-        properties:
-          endpoint:
-            $ref: /schemas/media/video-interfaces.yaml#
-
        port@3:
-        $ref: /schemas/graph.yaml#/$defs/port-base
+        $ref: /schemas/graph.yaml#/properties/port
+        unevaluatedProperties: false
          description: FPD-Link input 3
  
-        properties:
-          endpoint:
-            $ref: /schemas/media/video-interfaces.yaml#
-
        port@4:
          $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
          description: CSI-2 Output 0
  
          properties:
            endpoint:
              $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
  
              properties:
-              clock-lanes:
-                maxItems: 1
-
                data-lanes:
                  minItems: 1
                  maxItems: 4
  
        port@5:
          $ref: /schemas/graph.yaml#/$defs/port-base
+        unevaluatedProperties: false
          description: CSI-2 Output 1
  
          properties:
            endpoint:
              $ref: /schemas/media/video-interfaces.yaml#
+            unevaluatedProperties: false
  
              properties:
-              clock-lanes:
-                maxItems: 1
-
                data-lanes:
                  minItems: 1
                  maxItems: 4
@@ -204,9 +191,7 @@ examples:
  
        deser@3d {
          compatible = "ti,ds90ub960-q1";
-
-        reg-names = "main";
-        reg       = <0x3d>;
+        reg = <0x3d>;
  
          clock-names = "refclk";
          clocks = <&fixed_clock>;
@@ -225,19 +210,15 @@ examples:
  
              ub960_fpd3_1_in: endpoint {
                remote-endpoint = <&ub953_1_out>;
-
-              rx-mode = <0>;
              };
            };
  
-          /* Port 0, Camera 1 */
+          /* Port 1, Camera 1 */
            port@1 {
              reg = <1>;
  
              ub960_fpd3_2_in: endpoint {
                remote-endpoint = <&ub913_2_out>;
-
-              rx-mode = <0>;
              };
            };
  
@@ -245,7 +226,6 @@ examples:
            port@4 {
              reg = <4>;
              ds90ub960_0_csi_out: endpoint {
-              clock-lanes = <0>;
                data-lanes = <1 2 3 4>;
                link-frequencies = /bits/ 64 <800000000>;
                remote-endpoint = <&csi2_phy0>;
@@ -257,13 +237,13 @@ examples:
            #address-cells = <1>;
            #size-cells = <0>;
  
-          /* Link 0 has DS90UB953 serializer and IMX390 sensor */
+          /* Link 0 has DS90UB953 serializer and IMX274 sensor */
  
            link@0 {
              reg = <0>;
-            i2c-alias = <68>;
+            i2c-alias = <0x44>;
  
-            rx-mode = <3>;
+            ti,rx-mode = <3>;
  
              serializer1: serializer {
                compatible = "ti,ds90ub953-q1";
@@ -280,7 +260,6 @@ examples:
                  port@0 {
                    reg = <0>;
                    ub953_1_in: endpoint {
-                    clock-lanes = <0>;
                      data-lanes = <1 2 3 4>;
                      remote-endpoint = <&sensor_1_out>;
                    };
@@ -299,17 +278,11 @@ examples:
                  #address-cells = <1>;
                  #size-cells = <0>;
  
-                sensor@21 {
-                  compatible = "sony,imx390";
-                  reg = <0x21>;
-
-                  clocks = <&clk_cam_27M>;
-                  clock-names = "inck";
+                sensor@1a {
+                  compatible = "sony,imx274";
+                  reg = <0x1a>;
  
-                  xclr-gpios = <&serializer1 0 GPIO_ACTIVE_LOW>;
-                  error0-gpios = <&serializer1 1 GPIO_ACTIVE_HIGH>;
-                  error1-gpios = <&serializer1 2 GPIO_ACTIVE_HIGH>;
-                  comready-gpios = <&serializer1 3 GPIO_ACTIVE_HIGH>;
+                  reset-gpios = <&serializer1 0 GPIO_ACTIVE_LOW>;
  
                    port {
                      sensor_1_out: endpoint {
@@ -321,13 +294,13 @@ examples:
              };
            };  /* End of link@0 */
  
-          /* Link 1 has DS90UB913 serializer and OV10635 sensor */
+          /* Link 1 has DS90UB913 serializer and MT9V111 sensor */
  
            link@1 {
              reg = <1>;
-            i2c-alias = <69>;
+            i2c-alias = <0x45>;
  
-            rx-mode = <0>;
+            ti,rx-mode = <0>;
  
              serializer2: serializer {
                compatible = "ti,ds90ub913a-q1";
@@ -364,22 +337,15 @@ examples:
                  #address-cells = <1>;
                  #size-cells = <0>;
  
-                sensor@30 {
-                  compatible = "ovti,ov10635";
-                  reg = <0x30>;
+                sensor@48 {
+                  compatible = "aptina,mt9v111";
+                  reg = <0x48>;
  
                    clocks = <&serializer2>;
-                  clock-names = "xvclk";
-
-                  powerdown-gpios = <&serializer2 0 GPIO_ACTIVE_HIGH>;
  
                    port {
                      sensor_2_out: endpoint {
                        remote-endpoint = <&ub913_2_in>;
-                      hsync-active = <1>;
-                      vsync-active = <1>;
-                      pclk-sample = <0>;
-                      bus-width = <10>;
                      };
                    };
                  };
diff --git a/Documentation/i2c/index.rst b/Documentation/i2c/index.rst
index 3d177d4ec1d2..aaf33d1315f4 100644
--- a/Documentation/i2c/index.rst
+++ b/Documentation/i2c/index.rst
@@ -16,9 +16,9 @@ Introduction
     instantiating-devices
     busses/index
     i2c-topology
+   muxes/i2c-atr
     muxes/i2c-mux-gpio
     i2c-sysfs
-   muxes/i2c-atr
  
  Writing device drivers
  ======================
diff --git a/MAINTAINERS b/MAINTAINERS
index 357de12cbca6..4c375e30f951 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9569,7 +9569,8 @@ S:	Maintained
  F:	drivers/i2c/i2c-core-acpi.c
  
  I2C ADDRESS TRANSLATOR (ATR)
-M:	Luca Ceresoli <luca@lucaceresoli.net>
+M:	Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
+R:	Luca Ceresoli <luca.ceresoli@bootlin.com>
  L:	linux-i2c@vger.kernel.org
  S:	Maintained
  F:	drivers/i2c/i2c-atr.c
diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c
index 5636bbd03b09..1d3b25a6550f 100644
--- a/drivers/i2c/i2c-atr.c
+++ b/drivers/i2c/i2c-atr.c
@@ -7,14 +7,17 @@
   * Originally based on i2c-mux.c
   */
  
+#include <linux/fwnode.h>
  #include <linux/i2c-atr.h>
  #include <linux/i2c.h>
  #include <linux/kernel.h>
  #include <linux/module.h>
  #include <linux/mutex.h>
-#include <linux/of.h>
  #include <linux/slab.h>
  
+#define ATR_MAX_ADAPTERS 99	/* Just a sanity limit */
+#define ATR_MAX_SYMLINK_LEN 16	/* Longest name is 10 chars: "channel-99" */
+
  /**
   * struct i2c_atr_cli2alias_pair - Hold the alias assigned to a client.
   * @node:   List node
@@ -85,9 +88,11 @@ static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
  
  	/* Ensure we have enough room to save the original addresses */
  	if (unlikely(chan->orig_addrs_size < num)) {
-		void *new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
-					      GFP_KERNEL);
-		if (new_buf == NULL)
+		u16 *new_buf;
+
+		new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
+					GFP_KERNEL);
+		if (!new_buf)
  			return -ENOMEM;
  
  		kfree(chan->orig_addrs);
@@ -118,7 +123,7 @@ static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
   *
   * @see i2c_atr_map_msgs()
   */
-static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg msgs[],
+static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
  			       int num)
  {
  	int i;
@@ -127,7 +132,7 @@ static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg msgs[],
  		msgs[i].addr = chan->orig_addrs[i];
  }
  
-static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
+static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
  			       int num)
  {
  	struct i2c_atr_chan *chan = adap->algo_data;
@@ -236,8 +241,8 @@ static int i2c_atr_attach_client(struct i2c_adapter *adapter,
  	struct i2c_atr_chan *chan = adapter->algo_data;
  	struct i2c_atr *atr = chan->atr;
  	struct i2c_atr_cli2alias_pair *c2a;
-	u16 alias_id = 0;
-	int ret = 0;
+	u16 alias_id;
+	int ret;
  
  	c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
  	if (!c2a)
@@ -311,7 +316,7 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
  	struct i2c_adapter *parent = atr->parent;
  	struct device *dev = atr->dev;
  	struct i2c_atr_chan *chan;
-	char *symlink_name;
+	char symlink_name[ATR_MAX_SYMLINK_LEN];
  	int ret;
  
  	if (chan_id >= atr->max_adapters) {
@@ -370,24 +375,26 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
  	if (ret) {
  		dev_err(dev, "failed to add atr-adapter %u (error=%d)\n",
  			chan_id, ret);
-		goto err_add_adapter;
+		goto err_mutex_destroy;
  	}
  
-	symlink_name = kasprintf(GFP_KERNEL, "channel-%u", chan_id);
-
-	WARN(sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device"),
-	     "can't create symlink to atr device\n");
-	WARN(sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name),
-	     "can't create symlink for channel %u\n", chan_id);
+	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
+		 chan->chan_id);
  
-	kfree(symlink_name);
+	ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device");
+	if (ret)
+		dev_warn(dev, "can't create symlink to atr device\n");
+	ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name);
+	if (ret)
+		dev_warn(dev, "can't create symlink for channel %u\n", chan_id);
  
  	dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap));
  
  	atr->adapter[chan_id] = &chan->adap;
  	return 0;
  
-err_add_adapter:
+err_mutex_destroy:
+	fwnode_handle_put(dev_fwnode(&chan->adap.dev));
  	mutex_destroy(&chan->orig_addrs_lock);
  	kfree(chan);
  	return ret;
@@ -402,14 +409,14 @@ EXPORT_SYMBOL_GPL(i2c_atr_add_adapter);
   */
  void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
  {
-	char symlink_name[20];
+	char symlink_name[ATR_MAX_SYMLINK_LEN];
  
  	struct i2c_adapter *adap = atr->adapter[chan_id];
  	struct i2c_atr_chan *chan = adap->algo_data;
-	struct fwnode_handle *fwnode = adap->dev.fwnode;
+	struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
  	struct device *dev = atr->dev;
  
-	if (atr->adapter[chan_id] == NULL) {
+	if (!atr->adapter[chan_id]) {
  		dev_err(dev, "Adapter %d does not exist\n", chan_id);
  		return;
  	}
@@ -418,8 +425,8 @@ void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
  
  	atr->adapter[chan_id] = NULL;
  
-	snprintf(symlink_name, sizeof(symlink_name),
-		 "channel-%u", chan->chan_id);
+	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
+		 chan->chan_id);
  	sysfs_remove_link(&dev->kobj, symlink_name);
  	sysfs_remove_link(&chan->adap.dev.kobj, "atr_device");
  
@@ -449,16 +456,15 @@ struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
  			    const struct i2c_atr_ops *ops, int max_adapters)
  {
  	struct i2c_atr *atr;
-	size_t atr_size;
  
-	if (!ops || !ops->attach_client || !ops->detach_client)
+	if (max_adapters > ATR_MAX_ADAPTERS)
  		return ERR_PTR(-EINVAL);
  
-	atr_size = struct_size(atr, adapter, max_adapters);
-	if (atr_size == SIZE_MAX)
-		return ERR_PTR(-EOVERFLOW);
+	if (!ops || !ops->attach_client || !ops->detach_client)
+		return ERR_PTR(-EINVAL);
  
-	atr = devm_kzalloc(dev, atr_size, GFP_KERNEL);
+	atr = devm_kzalloc(dev, struct_size(atr, adapter, max_adapters),
+			   GFP_KERNEL);
  	if (!atr)
  		return ERR_PTR(-ENOMEM);
  
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 1563fa0abf0e..b24f89f58807 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1603,7 +1603,12 @@ menu "Video serializers and deserializers"
  
  config VIDEO_DS90UB960
  	tristate "TI DS90UB960 Deserializer"
-	depends on OF_GPIO
+	depends on OF && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	select REGMAP_I2C
+	select OF_GPIO
  	select I2C_ATR
  	help
  	  Device driver for the Texas Instruments DS90UB960
@@ -1611,12 +1616,26 @@ config VIDEO_DS90UB960
  
  config VIDEO_DS90UB913
  	tristate "TI DS90UB913 Serializer"
+	depends on OF && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	select REGMAP_I2C
+	select OF_GPIO
+	select I2C_ATR
  	help
  	  Device driver for the Texas Instruments DS90UB913
  	  FPD-Link III Serializer.
  
  config VIDEO_DS90UB953
  	tristate "TI DS90UB953 Serializer"
+	depends on OF && I2C && VIDEO_DEV
+	select MEDIA_CONTROLLER
+	select VIDEO_V4L2_SUBDEV_API
+	select V4L2_FWNODE
+	select REGMAP_I2C
+	select OF_GPIO
+	select I2C_ATR
  	help
  	  Device driver for the Texas Instruments DS90UB953
  	  FPD-Link III Serializer.
diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c
index c653474b5efc..fef704ee5529 100644
--- a/drivers/media/i2c/ds90ub960.c
+++ b/drivers/media/i2c/ds90ub960.c
@@ -2253,7 +2253,6 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
  	unsigned int i;
  	unsigned int nport;
  	u8 fwd_ctl;
-	u32 active_tx_mask = 0;
  	struct {
  		u32 num_streams;
  		u8 pixel_dt;
@@ -2283,8 +2282,6 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
  		if (!txport)
  			return -EINVAL;
  
-		active_tx_mask |= BIT(ub960_pad_to_port(priv, route->source_pad));
-
  		rx_data[nport].tx_port = ub960_pad_to_port(priv, route->source_pad);
  
  		rx_data[nport].num_streams++;
@@ -3346,7 +3343,7 @@ static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
  	u32 eq_level;
  	unsigned int nport = rxport->nport;
  
-	ret = of_property_read_u32(np, "cdr-mode", &cdr_mode);
+	ret = of_property_read_u32(np, "ti,cdr-mode", &cdr_mode);
  	if (ret == -EINVAL) {
  		cdr_mode = RXPORT_CDR_FPD3;
  	} else if (ret < 0) {
@@ -3367,7 +3364,7 @@ static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
  
  	rxport->cdr_mode = cdr_mode;
  
-	ret = of_property_read_u32(np, "rx-mode", &rx_mode);
+	ret = of_property_read_u32(np, "ti,rx-mode", &rx_mode);
  	if (ret < 0) {
  		dev_err(dev, "Missing RX port RX mode: %d\n", ret);
  		return ret;
@@ -3387,10 +3384,10 @@ static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
  	rxport->eq.aeq.eq_level_min = UB960_MIN_EQ_LEVEL;
  	rxport->eq.aeq.eq_level_max = UB960_MAX_EQ_LEVEL;
  
-	ret = of_property_read_s32(np, "strobe-pos", &strobe_pos);
+	ret = of_property_read_s32(np, "ti,strobe-pos", &strobe_pos);
  	if (ret) {
  		if (ret != -EINVAL) {
-			dev_err(dev, "Failed to read 'strobe-pos': %d\n", ret);
+			dev_err(dev, "Failed to read 'ti,strobe-pos': %d\n", ret);
  			return ret;
  		}
  	} else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
@@ -3400,17 +3397,17 @@ static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
  		// NOTE: ignored unless global manual strobe pos is set
  		rxport->eq.strobe_pos = strobe_pos;
  		if (!priv->strobe.manual)
-			dev_warn(dev, "'strobe-pos' ignored as 'manual-strobe' not set\n");
+			dev_warn(dev, "'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n");
  	}
  
-	ret = of_property_read_u32(np, "eq-level", &eq_level);
+	ret = of_property_read_u32(np, "ti,eq-level", &eq_level);
  	if (ret) {
  		if (ret != -EINVAL) {
-			dev_err(dev, "Failed to read 'eq-level': %d\n", ret);
+			dev_err(dev, "Failed to read 'ti,eq-level': %d\n", ret);
  			return ret;
  		}
  	} else if (eq_level > UB960_MAX_EQ_LEVEL) {
-		dev_err(dev, "Illegal 'eq-level' value: %d\n", eq_level);
+		dev_err(dev, "Illegal 'ti,eq-level' value: %d\n", eq_level);
  	} else {
  		rxport->eq.manual_eq = true;
  		rxport->eq.manual.eq_level = eq_level;
@@ -3566,7 +3563,7 @@ static int ub960_parse_dt_rxports(struct ub960_data *priv)
  	priv->strobe.min = 2;
  	priv->strobe.max = 3;
  
-	priv->strobe.manual = of_property_read_bool(links_np, "manual-strobe");
+	priv->strobe.manual = of_property_read_bool(links_np, "ti,manual-strobe");
  
  	for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
  		struct device_node *link_np;
diff --git a/include/linux/i2c-atr.h b/include/linux/i2c-atr.h
index 19ac2f1db96b..044c87c5b336 100644
--- a/include/linux/i2c-atr.h
+++ b/include/linux/i2c-atr.h
@@ -16,6 +16,7 @@
  
  struct device;
  struct i2c_atr;
+struct fwnode_handle;
  
  /**
   * struct i2c_atr_ops - Callbacks from ATR to the device driver.
@@ -41,7 +42,7 @@ struct i2c_atr_ops {
  };
  
  /**
- * Helper to add I2C ATR features to a device driver.
+ * struct i2c_atr - Represents the I2C ATR instance
   */
  struct i2c_atr {
  	/* private: internal use only */
@@ -53,10 +54,11 @@ struct i2c_atr {
  	void *priv;
  
  	struct i2c_algorithm algo;
+	/* lock for the I2C bus segment (see struct i2c_lock_operations) */
  	struct mutex lock;
  	int max_adapters;
  
-	struct i2c_adapter *adapter[0];
+	struct i2c_adapter *adapter[];
  };
  
  struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,


^ permalink raw reply related	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 0/8] i2c-atr and FPDLink
  2022-12-08 10:42 ` [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
@ 2022-12-08 12:26   ` Andy Shevchenko
  2022-12-08 14:40     ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-08 12:26 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On Thu, Dec 08, 2022 at 12:42:13PM +0200, Tomi Valkeinen wrote:
> On 08/12/2022 12:39, Tomi Valkeinen wrote:

...

> +#include <linux/fwnode.h>
>  #include <linux/i2c-atr.h>
>  #include <linux/i2c.h>
>  #include <linux/kernel.h>
>  #include <linux/module.h>
>  #include <linux/mutex.h>
> -#include <linux/of.h>
>  #include <linux/slab.h>

+ Blank line here?

> +#define ATR_MAX_ADAPTERS 99	/* Just a sanity limit */
> +#define ATR_MAX_SYMLINK_LEN 16	/* Longest name is 10 chars: "channel-99" */

...

> +		u16 *new_buf;
> +
> +		new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
> +					GFP_KERNEL);

		new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);

?

> +		if (!new_buf)
>  			return -ENOMEM;

...

>  	struct i2c_atr_cli2alias_pair *c2a;
> -	u16 alias_id = 0;
> -	int ret = 0;
> +	u16 alias_id;
> +	int ret;

Is it mangled or it's missing blank line here?

>  	c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
>  	if (!c2a)

...

>  struct device;
>  struct i2c_atr;
> +struct fwnode_handle;

Order?

...

>  /**
> - * Helper to add I2C ATR features to a device driver.
> + * struct i2c_atr - Represents the I2C ATR instance
>   */

This is incomplete. Have you run kernel doc validator against this file?

>  struct i2c_atr {

>  };

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-08 10:39 ` [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach Tomi Valkeinen
@ 2022-12-08 12:30   ` Andy Shevchenko
  2022-12-08 16:10     ` Tomi Valkeinen
  2022-12-11 16:55   ` Laurent Pinchart
  1 sibling, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-08 12:30 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:
> From: Luca Ceresoli <luca@lucaceresoli.net>
> 
> An adapter might need to know when a new device is about to be
> added. This will soon bee needed to implement an "I2C address
> translator" (ATR for short), a device that propagates I2C transactions
> with a different slave address (an "alias" address). An ATR driver
> needs to know when a slave is being added to find a suitable alias and
> program the device translation map.
> 
> Add an attach/detach callback pair to allow adapter drivers to be
> notified of clients being added and removed.

...

> +	if (adap->attach_ops &&
> +	    adap->attach_ops->attach_client &&
> +	    adap->attach_ops->attach_client(adap, info, client) != 0)
> +		goto out_remove_swnode;

With a temporary variable it becomes better

	... *ops = adap->attach_ops;

	if (ops && ops->attach_client && ops->attach_client(adap, info, client))


Also notice drop of unneeded ' != 0' part.


>  	status = device_register(&client->dev);
>  	if (status)
> -		goto out_remove_swnode;
> +		goto out_detach_client;
>  
>  	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
>  		client->name, dev_name(&client->dev));
>  
>  	return client;
>  
> +out_detach_client:
> +	if (adap->attach_ops && adap->attach_ops->detach_client)
> +		adap->attach_ops->detach_client(adap, client);

In the similar way.

...

> +	if (adap->attach_ops &&
> +	    adap->attach_ops->detach_client)
> +		adap->attach_ops->detach_client(adap, client);

In the similar way.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support
  2022-12-08 10:40 ` [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support Tomi Valkeinen
@ 2022-12-08 12:53   ` Andy Shevchenko
  2022-12-08 16:01     ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-08 12:53 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On Thu, Dec 08, 2022 at 12:40:00PM +0200, Tomi Valkeinen wrote:
> From: Luca Ceresoli <luca@lucaceresoli.net>
> 
> An ATR is a device that looks similar to an i2c-mux: it has an I2C
> slave "upstream" port and N master "downstream" ports, and forwards
> transactions from upstream to the appropriate downstream port. But is
> is different in that the forwarded transaction has a different slave
> address. The address used on the upstream bus is called the "alias"
> and is (potentially) different from the physical slave address of the
> downstream chip.
> 
> Add a helper file (just like i2c-mux.c for a mux or switch) to allow
> implementing ATR features in a device driver. The helper takes care or
> adapter creation/destruction and translates addresses at each transaction.

Besides comments given against diff between series versions, see below.

...

> +static int i2c_atr_attach_client(struct i2c_adapter *adapter,
> +				 const struct i2c_board_info *info,
> +				 const struct i2c_client *client)
> +{
> +	struct i2c_atr_chan *chan = adapter->algo_data;
> +	struct i2c_atr *atr = chan->atr;
> +	struct i2c_atr_cli2alias_pair *c2a;
> +	u16 alias_id;
> +	int ret;
> +
> +	c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
> +	if (!c2a)
> +		return -ENOMEM;
> +
> +	ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
> +				      &alias_id);
> +	if (ret)
> +		goto err_free;

> +	if (alias_id == 0) {
> +		ret = -EINVAL;

I'm wondering why attach_client can't return this error and provide a guarantee
that if no error, the alias_id is never be 0?

> +		goto err_free;
> +	}
> +
> +	c2a->client = client;
> +	c2a->alias = alias_id;
> +	list_add(&c2a->node, &chan->alias_list);
> +
> +	return 0;
> +
> +err_free:
> +	kfree(c2a);
> +	return ret;
> +}

...

> +	if (bus_handle) {
> +		device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle));

I believe the correct way, while above still works, is

		device_set_node(&chan->adap.dev, bus_handle);
		fwnode_handle_get(dev_fwnode(&chan->adap.dev));

But I agree that this looks a bit verbose. And...

> +	} else {
> +		struct fwnode_handle *atr_node;
> +		struct fwnode_handle *child;
> +		u32 reg;
> +
> +		atr_node = device_get_named_child_node(dev, "i2c-atr");
> +
> +		fwnode_for_each_child_node(atr_node, child) {
> +			ret = fwnode_property_read_u32(child, "reg", &reg);
> +			if (ret)
> +				continue;
> +			if (chan_id == reg)
> +				break;
> +		}
> +
> +		device_set_node(&chan->adap.dev, child);

...OTOH, you set node with bumped reference here. So I leave all this to
the maintainers.

> +		fwnode_handle_put(atr_node);
> +	}

> +	ret = i2c_add_adapter(&chan->adap);
> +	if (ret) {
> +		dev_err(dev, "failed to add atr-adapter %u (error=%d)\n",
> +			chan_id, ret);
> +		goto err_mutex_destroy;
> +	}
> +
> +	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
> +		 chan->chan_id);
> +
> +	ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device");
> +	if (ret)
> +		dev_warn(dev, "can't create symlink to atr device\n");
> +	ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name);
> +	if (ret)
> +		dev_warn(dev, "can't create symlink for channel %u\n", chan_id);
> +
> +	dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap));
> +
> +	atr->adapter[chan_id] = &chan->adap;
> +	return 0;
> +
> +err_mutex_destroy:

Now it's a bit misleading, wouldn't be better

err_put_fwnode:

?

> +	fwnode_handle_put(dev_fwnode(&chan->adap.dev));
> +	mutex_destroy(&chan->orig_addrs_lock);
> +	kfree(chan);
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(i2c_atr_add_adapter);

Wondering if we may put this into namespace from day 1.

...

> +/**
> + * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by
> + * i2c_atr_del_adapter().
> + * @atr:     The I2C ATR
> + * @chan_id: Index of the `adapter to be removed (0 .. max_adapters-1)
> + */
> +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
> +{
> +	char symlink_name[ATR_MAX_SYMLINK_LEN];
> +
> +	struct i2c_adapter *adap = atr->adapter[chan_id];
> +	struct i2c_atr_chan *chan = adap->algo_data;
> +	struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
> +	struct device *dev = atr->dev;

> +	if (!atr->adapter[chan_id]) {

Isn't it the same as

	if (!adap)

?

> +		dev_err(dev, "Adapter %d does not exist\n", chan_id);
> +		return;
> +	}
> +
> +	dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap));
> +
> +	atr->adapter[chan_id] = NULL;
> +
> +	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
> +		 chan->chan_id);
> +	sysfs_remove_link(&dev->kobj, symlink_name);
> +	sysfs_remove_link(&chan->adap.dev.kobj, "atr_device");
> +
> +	i2c_del_adapter(adap);
> +	fwnode_handle_put(fwnode);
> +	mutex_destroy(&chan->orig_addrs_lock);
> +	kfree(chan->orig_addrs);
> +	kfree(chan);
> +}

...

> +struct i2c_atr {
> +	/* private: internal use only */

What is private? The entire structure? Then why it's defined in
the include/linux/? Can't you make it opaque?

> +	struct i2c_adapter *parent;
> +	struct device *dev;
> +	const struct i2c_atr_ops *ops;
> +
> +	void *priv;
> +
> +	struct i2c_algorithm algo;
> +	/* lock for the I2C bus segment (see struct i2c_lock_operations) */
> +	struct mutex lock;
> +	int max_adapters;
> +
> +	struct i2c_adapter *adapter[];
> +};

...

> +static inline void i2c_atr_set_clientdata(struct i2c_atr *atr, void *data)
> +{
> +	atr->priv = data;
> +}
> +
> +static inline void *i2c_atr_get_clientdata(struct i2c_atr *atr)
> +{
> +	return atr->priv;
> +}

The function names are misleading, because I would think this is about driver
data that has been set.

I would rather use name like

	i2c_atr_get_priv()
	i2c_atr_set_priv()

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 0/8] i2c-atr and FPDLink
  2022-12-08 12:26   ` Andy Shevchenko
@ 2022-12-08 14:40     ` Tomi Valkeinen
  2022-12-08 15:57       ` Andy Shevchenko
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 14:40 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Andy,

On 08/12/2022 14:26, Andy Shevchenko wrote:
> On Thu, Dec 08, 2022 at 12:42:13PM +0200, Tomi Valkeinen wrote:
>> On 08/12/2022 12:39, Tomi Valkeinen wrote:
> 
> ...
> 
>> +#include <linux/fwnode.h>
>>   #include <linux/i2c-atr.h>
>>   #include <linux/i2c.h>
>>   #include <linux/kernel.h>
>>   #include <linux/module.h>
>>   #include <linux/mutex.h>
>> -#include <linux/of.h>
>>   #include <linux/slab.h>
> 
> + Blank line here?
> 

There is a blank line there.

>> +#define ATR_MAX_ADAPTERS 99	/* Just a sanity limit */
>> +#define ATR_MAX_SYMLINK_LEN 16	/* Longest name is 10 chars: "channel-99" */
> 
> ...
> 
>> +		u16 *new_buf;
>> +
>> +		new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
>> +					GFP_KERNEL);
> 
> 		new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);
> 
> ?

Yes, I think that looks better here.

>> +		if (!new_buf)
>>   			return -ENOMEM;
> 
> ...
> 
>>   	struct i2c_atr_cli2alias_pair *c2a;
>> -	u16 alias_id = 0;
>> -	int ret = 0;
>> +	u16 alias_id;
>> +	int ret;
> 
> Is it mangled or it's missing blank line here?

Also here there is a blank line. Is the mail somehow mangled on your 
side? On lore it looks fine:

https://lore.kernel.org/all/c5eac6a6-f44b-ddd0-d27b-ccbe01498ae9@ideasonboard.com/

>>   	c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
>>   	if (!c2a)
> 
> ...
> 
>>   struct device;
>>   struct i2c_atr;
>> +struct fwnode_handle;
> 
> Order?

Yep, I'll fix.

> ...
> 
>>   /**
>> - * Helper to add I2C ATR features to a device driver.
>> + * struct i2c_atr - Represents the I2C ATR instance
>>    */
> 
> This is incomplete. Have you run kernel doc validator against this file?

What's kernel doc validator? Do you mean that it's incomplete and kernel 
doc doesn't work correctly, or that there should be more information here?

I don't get any errors/warnings from any tool I have used. But I agree 
it looks a bit odd with only the name of the struct in the doc.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 0/8] i2c-atr and FPDLink
  2022-12-08 14:40     ` Tomi Valkeinen
@ 2022-12-08 15:57       ` Andy Shevchenko
  2022-12-08 15:58         ` Andy Shevchenko
  0 siblings, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-08 15:57 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On Thu, Dec 08, 2022 at 04:40:58PM +0200, Tomi Valkeinen wrote:
> On 08/12/2022 14:26, Andy Shevchenko wrote:
> > On Thu, Dec 08, 2022 at 12:42:13PM +0200, Tomi Valkeinen wrote:
> > > On 08/12/2022 12:39, Tomi Valkeinen wrote:

...

> > >   /**
> > > - * Helper to add I2C ATR features to a device driver.
> > > + * struct i2c_atr - Represents the I2C ATR instance
> > >    */
> > 
> > This is incomplete. Have you run kernel doc validator against this file?
> 
> What's kernel doc validator? Do you mean that it's incomplete and kernel doc
> doesn't work correctly, or that there should be more information here?
> 
> I don't get any errors/warnings from any tool I have used. But I agree it
> looks a bit odd with only the name of the struct in the doc.

...

$ scripts/kernel-doc -none -v include/linux/i2c.h
include/linux/i2c.h:79: warning: No description found for return value of 'i2c_master_recv'
include/linux/i2c.h:94: warning: No description found for return value of 'i2c_master_recv_dmasafe'
include/linux/i2c.h:109: warning: No description found for return value of 'i2c_master_send'
include/linux/i2c.h:124: warning: No description found for return value of 'i2c_master_send_dmasafe'
4 warnings

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 0/8] i2c-atr and FPDLink
  2022-12-08 15:57       ` Andy Shevchenko
@ 2022-12-08 15:58         ` Andy Shevchenko
  2022-12-08 16:05           ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-08 15:58 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On Thu, Dec 08, 2022 at 05:57:23PM +0200, Andy Shevchenko wrote:
> On Thu, Dec 08, 2022 at 04:40:58PM +0200, Tomi Valkeinen wrote:
> > On 08/12/2022 14:26, Andy Shevchenko wrote:
> > > On Thu, Dec 08, 2022 at 12:42:13PM +0200, Tomi Valkeinen wrote:
> > > > On 08/12/2022 12:39, Tomi Valkeinen wrote:

...

> > > >   /**
> > > > - * Helper to add I2C ATR features to a device driver.
> > > > + * struct i2c_atr - Represents the I2C ATR instance
> > > >    */
> > > 
> > > This is incomplete. Have you run kernel doc validator against this file?
> > 
> > What's kernel doc validator? Do you mean that it's incomplete and kernel doc
> > doesn't work correctly, or that there should be more information here?
> > 
> > I don't get any errors/warnings from any tool I have used. But I agree it
> > looks a bit odd with only the name of the struct in the doc.
> 
> ...
> 
> $ scripts/kernel-doc -none -v include/linux/i2c.h
> include/linux/i2c.h:79: warning: No description found for return value of 'i2c_master_recv'
> include/linux/i2c.h:94: warning: No description found for return value of 'i2c_master_recv_dmasafe'
> include/linux/i2c.h:109: warning: No description found for return value of 'i2c_master_send'
> include/linux/i2c.h:124: warning: No description found for return value of 'i2c_master_send_dmasafe'
> 4 warnings

Note, this is just example against existing file, not your case.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support
  2022-12-08 12:53   ` Andy Shevchenko
@ 2022-12-08 16:01     ` Tomi Valkeinen
  2022-12-08 18:09       ` Andy Shevchenko
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 16:01 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

Hi Andy,

On 08/12/2022 14:53, Andy Shevchenko wrote:
> On Thu, Dec 08, 2022 at 12:40:00PM +0200, Tomi Valkeinen wrote:
>> From: Luca Ceresoli <luca@lucaceresoli.net>
>>
>> An ATR is a device that looks similar to an i2c-mux: it has an I2C
>> slave "upstream" port and N master "downstream" ports, and forwards
>> transactions from upstream to the appropriate downstream port. But is
>> is different in that the forwarded transaction has a different slave
>> address. The address used on the upstream bus is called the "alias"
>> and is (potentially) different from the physical slave address of the
>> downstream chip.
>>
>> Add a helper file (just like i2c-mux.c for a mux or switch) to allow
>> implementing ATR features in a device driver. The helper takes care or
>> adapter creation/destruction and translates addresses at each transaction.
> 
> Besides comments given against diff between series versions, see below.
> 
> ...
> 
>> +static int i2c_atr_attach_client(struct i2c_adapter *adapter,
>> +				 const struct i2c_board_info *info,
>> +				 const struct i2c_client *client)
>> +{
>> +	struct i2c_atr_chan *chan = adapter->algo_data;
>> +	struct i2c_atr *atr = chan->atr;
>> +	struct i2c_atr_cli2alias_pair *c2a;
>> +	u16 alias_id;
>> +	int ret;
>> +
>> +	c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
>> +	if (!c2a)
>> +		return -ENOMEM;
>> +
>> +	ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
>> +				      &alias_id);
>> +	if (ret)
>> +		goto err_free;
> 
>> +	if (alias_id == 0) {
>> +		ret = -EINVAL;
> 
> I'm wondering why attach_client can't return this error and provide a guarantee
> that if no error, the alias_id is never be 0?

I think that's a valid point. I see no reason to check for alias_id == 0 
here.

>> +		goto err_free;
>> +	}
>> +
>> +	c2a->client = client;
>> +	c2a->alias = alias_id;
>> +	list_add(&c2a->node, &chan->alias_list);
>> +
>> +	return 0;
>> +
>> +err_free:
>> +	kfree(c2a);
>> +	return ret;
>> +}
> 
> ...
> 
>> +	if (bus_handle) {
>> +		device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle));
> 
> I believe the correct way, while above still works, is
> 
> 		device_set_node(&chan->adap.dev, bus_handle);
> 		fwnode_handle_get(dev_fwnode(&chan->adap.dev));

Hmm, why is that correct? Shouldn't you give device_set_node() an fwnode 
that has been referenced?

> But I agree that this looks a bit verbose. And...
> 
>> +	} else {
>> +		struct fwnode_handle *atr_node;
>> +		struct fwnode_handle *child;
>> +		u32 reg;
>> +
>> +		atr_node = device_get_named_child_node(dev, "i2c-atr");
>> +
>> +		fwnode_for_each_child_node(atr_node, child) {
>> +			ret = fwnode_property_read_u32(child, "reg", &reg);
>> +			if (ret)
>> +				continue;
>> +			if (chan_id == reg)
>> +				break;
>> +		}
>> +
>> +		device_set_node(&chan->adap.dev, child);
> 
> ...OTOH, you set node with bumped reference here. So I leave all this to
> the maintainers.
> 
>> +		fwnode_handle_put(atr_node);
>> +	}
> 
>> +	ret = i2c_add_adapter(&chan->adap);
>> +	if (ret) {
>> +		dev_err(dev, "failed to add atr-adapter %u (error=%d)\n",
>> +			chan_id, ret);
>> +		goto err_mutex_destroy;
>> +	}
>> +
>> +	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
>> +		 chan->chan_id);
>> +
>> +	ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device");
>> +	if (ret)
>> +		dev_warn(dev, "can't create symlink to atr device\n");
>> +	ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name);
>> +	if (ret)
>> +		dev_warn(dev, "can't create symlink for channel %u\n", chan_id);
>> +
>> +	dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap));
>> +
>> +	atr->adapter[chan_id] = &chan->adap;
>> +	return 0;
>> +
>> +err_mutex_destroy:
> 
> Now it's a bit misleading, wouldn't be better
> 
> err_put_fwnode:
> 
> ?

Yes.

>> +	fwnode_handle_put(dev_fwnode(&chan->adap.dev));
>> +	mutex_destroy(&chan->orig_addrs_lock);
>> +	kfree(chan);
>> +	return ret;
>> +}
>> +EXPORT_SYMBOL_GPL(i2c_atr_add_adapter);
> 
> Wondering if we may put this into namespace from day 1.

Right, that's something I didn't look at all for v5. I have not heard 
anyone else commenting about the NS, though. I could have a look at it 
anyway, just to learn about NSes.

> ...
> 
>> +/**
>> + * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by
>> + * i2c_atr_del_adapter().
>> + * @atr:     The I2C ATR
>> + * @chan_id: Index of the `adapter to be removed (0 .. max_adapters-1)
>> + */
>> +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
>> +{
>> +	char symlink_name[ATR_MAX_SYMLINK_LEN];
>> +
>> +	struct i2c_adapter *adap = atr->adapter[chan_id];
>> +	struct i2c_atr_chan *chan = adap->algo_data;
>> +	struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
>> +	struct device *dev = atr->dev;
> 
>> +	if (!atr->adapter[chan_id]) {
> 
> Isn't it the same as
> 
> 	if (!adap)
> 
> ?

Yes.

> 
>> +		dev_err(dev, "Adapter %d does not exist\n", chan_id);
>> +		return;
>> +	}
>> +
>> +	dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap));
>> +
>> +	atr->adapter[chan_id] = NULL;
>> +
>> +	snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
>> +		 chan->chan_id);
>> +	sysfs_remove_link(&dev->kobj, symlink_name);
>> +	sysfs_remove_link(&chan->adap.dev.kobj, "atr_device");
>> +
>> +	i2c_del_adapter(adap);
>> +	fwnode_handle_put(fwnode);
>> +	mutex_destroy(&chan->orig_addrs_lock);
>> +	kfree(chan->orig_addrs);
>> +	kfree(chan);
>> +}
> 
> ...
> 
>> +struct i2c_atr {
>> +	/* private: internal use only */
> 
> What is private? The entire structure? Then why it's defined in
> the include/linux/? Can't you make it opaque?

Good point, I see no reason to keep this in the public header. 
i2c_atr_set/get_clientdata used it, but I can move their implementations 
into the .c file.

>> +	struct i2c_adapter *parent;
>> +	struct device *dev;
>> +	const struct i2c_atr_ops *ops;
>> +
>> +	void *priv;
>> +
>> +	struct i2c_algorithm algo;
>> +	/* lock for the I2C bus segment (see struct i2c_lock_operations) */
>> +	struct mutex lock;
>> +	int max_adapters;
>> +
>> +	struct i2c_adapter *adapter[];
>> +};
> 
> ...
> 
>> +static inline void i2c_atr_set_clientdata(struct i2c_atr *atr, void *data)
>> +{
>> +	atr->priv = data;
>> +}
>> +
>> +static inline void *i2c_atr_get_clientdata(struct i2c_atr *atr)
>> +{
>> +	return atr->priv;
>> +}
> 
> The function names are misleading, because I would think this is about driver
> data that has been set.
> 
> I would rather use name like
> 
> 	i2c_atr_get_priv()
> 	i2c_atr_set_priv()

Indeed, set_clientdata is probably wrong. But i2c_atr_set_priv() sounds 
like it's private to the i2c-atr itself. Maybe i2c_atr_set_driver_data?

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 0/8] i2c-atr and FPDLink
  2022-12-08 15:58         ` Andy Shevchenko
@ 2022-12-08 16:05           ` Tomi Valkeinen
  0 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 16:05 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 08/12/2022 17:58, Andy Shevchenko wrote:
> On Thu, Dec 08, 2022 at 05:57:23PM +0200, Andy Shevchenko wrote:
>> On Thu, Dec 08, 2022 at 04:40:58PM +0200, Tomi Valkeinen wrote:
>>> On 08/12/2022 14:26, Andy Shevchenko wrote:
>>>> On Thu, Dec 08, 2022 at 12:42:13PM +0200, Tomi Valkeinen wrote:
>>>>> On 08/12/2022 12:39, Tomi Valkeinen wrote:
> 
> ...
> 
>>>>>    /**
>>>>> - * Helper to add I2C ATR features to a device driver.
>>>>> + * struct i2c_atr - Represents the I2C ATR instance
>>>>>     */
>>>>
>>>> This is incomplete. Have you run kernel doc validator against this file?
>>>
>>> What's kernel doc validator? Do you mean that it's incomplete and kernel doc
>>> doesn't work correctly, or that there should be more information here?
>>>
>>> I don't get any errors/warnings from any tool I have used. But I agree it
>>> looks a bit odd with only the name of the struct in the doc.
>>
>> ...
>>
>> $ scripts/kernel-doc -none -v include/linux/i2c.h
>> include/linux/i2c.h:79: warning: No description found for return value of 'i2c_master_recv'
>> include/linux/i2c.h:94: warning: No description found for return value of 'i2c_master_recv_dmasafe'
>> include/linux/i2c.h:109: warning: No description found for return value of 'i2c_master_send'
>> include/linux/i2c.h:124: warning: No description found for return value of 'i2c_master_send_dmasafe'
>> 4 warnings
> 
> Note, this is just example against existing file, not your case.

Yes, I tested with scripts/kernel-doc, it doesn't give any warnings 
about i2c-atr.h.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-08 12:30   ` Andy Shevchenko
@ 2022-12-08 16:10     ` Tomi Valkeinen
  0 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-08 16:10 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On 08/12/2022 14:30, Andy Shevchenko wrote:
> On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:
>> From: Luca Ceresoli <luca@lucaceresoli.net>
>>
>> An adapter might need to know when a new device is about to be
>> added. This will soon bee needed to implement an "I2C address
>> translator" (ATR for short), a device that propagates I2C transactions
>> with a different slave address (an "alias" address). An ATR driver
>> needs to know when a slave is being added to find a suitable alias and
>> program the device translation map.
>>
>> Add an attach/detach callback pair to allow adapter drivers to be
>> notified of clients being added and removed.
> 
> ...
> 
>> +	if (adap->attach_ops &&
>> +	    adap->attach_ops->attach_client &&
>> +	    adap->attach_ops->attach_client(adap, info, client) != 0)
>> +		goto out_remove_swnode;
> 
> With a temporary variable it becomes better
>

Ok.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support
  2022-12-08 16:01     ` Tomi Valkeinen
@ 2022-12-08 18:09       ` Andy Shevchenko
  0 siblings, 0 replies; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-08 18:09 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Laurent Pinchart,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On Thu, Dec 08, 2022 at 06:01:19PM +0200, Tomi Valkeinen wrote:
> On 08/12/2022 14:53, Andy Shevchenko wrote:
> > On Thu, Dec 08, 2022 at 12:40:00PM +0200, Tomi Valkeinen wrote:

...

> > > +	if (bus_handle) {
> > > +		device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle));
> > 
> > I believe the correct way, while above still works, is
> > 
> > 		device_set_node(&chan->adap.dev, bus_handle);
> > 		fwnode_handle_get(dev_fwnode(&chan->adap.dev));
> 
> Hmm, why is that correct? Shouldn't you give device_set_node() an fwnode
> that has been referenced?

You take a reference on the adap->dev and not on input. It's just a logical,
But as I said your variant still works.

> > But I agree that this looks a bit verbose. And...
> > 
> > > +	} else {
> > > +		struct fwnode_handle *atr_node;
> > > +		struct fwnode_handle *child;
> > > +		u32 reg;
> > > +
> > > +		atr_node = device_get_named_child_node(dev, "i2c-atr");
> > > +
> > > +		fwnode_for_each_child_node(atr_node, child) {
> > > +			ret = fwnode_property_read_u32(child, "reg", &reg);
> > > +			if (ret)
> > > +				continue;
> > > +			if (chan_id == reg)
> > > +				break;
> > > +		}
> > > +
> > > +		device_set_node(&chan->adap.dev, child);
> > 
> > ...OTOH, you set node with bumped reference here. So I leave all this to
> > the maintainers.
> > 
> > > +		fwnode_handle_put(atr_node);
> > > +	}

...

> > > +static inline void i2c_atr_set_clientdata(struct i2c_atr *atr, void *data)
> > > +{
> > > +	atr->priv = data;
> > > +}
> > > +
> > > +static inline void *i2c_atr_get_clientdata(struct i2c_atr *atr)
> > > +{
> > > +	return atr->priv;
> > > +}
> > 
> > The function names are misleading, because I would think this is about driver
> > data that has been set.
> > 
> > I would rather use name like
> > 
> > 	i2c_atr_get_priv()
> > 	i2c_atr_set_priv()
> 
> Indeed, set_clientdata is probably wrong. But i2c_atr_set_priv() sounds like
> it's private to the i2c-atr itself. Maybe i2c_atr_set_driver_data?

Works for me.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953
  2022-12-08 10:40 ` [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953 Tomi Valkeinen
@ 2022-12-09 21:27   ` Rob Herring
  2023-01-04  8:26     ` Tomi Valkeinen
  2022-12-11 17:34   ` Laurent Pinchart
  1 sibling, 1 reply; 57+ messages in thread
From: Rob Herring @ 2022-12-09 21:27 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Laurent Pinchart, Michael Tretter, Shawn Tu, Hans Verkuil,
	Mike Pagano, Krzysztof Hałasa, Marek Vasut

On Thu, Dec 08, 2022 at 12:40:02PM +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.

Seems like this and DS90UB913 binding could be combined. I couldn't spot 
a difference.

In the subjects, drop 'binding for'. The prefix says this is a binding. 
Maybe add 'Serializer'.

> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  .../bindings/media/i2c/ti,ds90ub953.yaml      | 112 ++++++++++++++++++
>  1 file changed, 112 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
> new file mode 100644
> index 000000000000..fd7d25d93e2c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
> @@ -0,0 +1,112 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub953.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments DS90UB953 FPD-Link 3 Serializer
> +
> +maintainers:
> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +
> +description:
> +  The TI DS90UB953 is an FPD-Link 3 video serializer for MIPI CSI-2.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - ti,ds90ub953-q1
> +      - ti,ds90ub971-q1
> +
> +  '#gpio-cells':
> +    const: 2
> +
> +  gpio-controller: true
> +
> +  '#clock-cells':
> +    const: 0
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 input port
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link 3 output port
> +
> +  i2c:
> +    $ref: /schemas/i2c/i2c-controller.yaml#
> +    unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - '#gpio-cells'
> +  - gpio-controller
> +  - '#clock-cells'
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    serializer {
> +      compatible = "ti,ds90ub953-q1";
> +
> +      gpio-controller;
> +      #gpio-cells = <2>;
> +
> +      #clock-cells = <0>;
> +
> +      ports {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        port@0 {
> +          reg = <0>;
> +          ub953_in: endpoint {
> +            clock-lanes = <0>;
> +            data-lanes = <1 2 3 4>;
> +            remote-endpoint = <&sensor_out>;
> +          };
> +        };
> +
> +        port@1 {
> +          reg = <1>;
> +          endpoint {
> +            remote-endpoint = <&deser_fpd_in>;
> +          };
> +        };
> +      };
> +
> +      i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        sensor@1a {
> +          compatible = "sony,imx274";
> +          reg = <0x1a>;
> +
> +          reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
> +
> +          port {
> +            sensor_out: endpoint {
> +              remote-endpoint = <&ub953_in>;
> +            };
> +          };
> +        };
> +      };
> +    };
> +...
> -- 
> 2.34.1
> 
> 

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2022-12-08 10:40 ` [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960 Tomi Valkeinen
@ 2022-12-09 21:30   ` Rob Herring
  2022-12-11 17:58   ` Laurent Pinchart
  1 sibling, 0 replies; 57+ messages in thread
From: Rob Herring @ 2022-12-09 21:30 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, linux-i2c, Matti Vaittinen, Marek Vasut,
	Michael Tretter, Mauro Carvalho Chehab, Laurent Pinchart,
	Luca Ceresoli, Peter Rosin, devicetree, Wolfram Sang,
	Hans Verkuil, Krzysztof Hałasa, Andy Shevchenko, Mark Brown,
	Krzysztof Kozlowski, linux-kernel, Liam Girdwood, Shawn Tu,
	Mike Pagano, Sakari Ailus, Rob Herring


On Thu, 08 Dec 2022 12:40:03 +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
>  1 file changed, 358 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> 

Reviewed-by: Rob Herring <robh@kernel.org>

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-08 10:39 ` [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach Tomi Valkeinen
  2022-12-08 12:30   ` Andy Shevchenko
@ 2022-12-11 16:55   ` Laurent Pinchart
  2022-12-19  8:51     ` Luca Ceresoli
  1 sibling, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-11 16:55 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

Hi Tomi and Luca,

Thank you for the patch.

On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:
> From: Luca Ceresoli <luca@lucaceresoli.net>
> 
> An adapter might need to know when a new device is about to be
> added. This will soon bee needed to implement an "I2C address
> translator" (ATR for short), a device that propagates I2C transactions
> with a different slave address (an "alias" address). An ATR driver
> needs to know when a slave is being added to find a suitable alias and
> program the device translation map.
> 
> Add an attach/detach callback pair to allow adapter drivers to be
> notified of clients being added and removed.

This may be a stupid question, but couldn't you instead use the
BUS_NOTIFY_ADD_DEVICE and BUS_NOTIFY_DEL_DEVICE bus notifiers ?

> Signed-off-by: Luca Ceresoli <luca@lucaceresoli.net>
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  drivers/i2c/i2c-core-base.c | 18 +++++++++++++++++-
>  include/linux/i2c.h         | 16 ++++++++++++++++
>  2 files changed, 33 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
> index b4edf10e8fd0..c8bc71b1db82 100644
> --- a/drivers/i2c/i2c-core-base.c
> +++ b/drivers/i2c/i2c-core-base.c
> @@ -966,15 +966,23 @@ i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *inf
>  		}
>  	}
>  
> +	if (adap->attach_ops &&
> +	    adap->attach_ops->attach_client &&
> +	    adap->attach_ops->attach_client(adap, info, client) != 0)
> +		goto out_remove_swnode;
> +
>  	status = device_register(&client->dev);
>  	if (status)
> -		goto out_remove_swnode;
> +		goto out_detach_client;
>  
>  	dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
>  		client->name, dev_name(&client->dev));
>  
>  	return client;
>  
> +out_detach_client:
> +	if (adap->attach_ops && adap->attach_ops->detach_client)
> +		adap->attach_ops->detach_client(adap, client);
>  out_remove_swnode:
>  	device_remove_software_node(&client->dev);
>  out_err_put_of_node:
> @@ -996,9 +1004,17 @@ EXPORT_SYMBOL_GPL(i2c_new_client_device);
>   */
>  void i2c_unregister_device(struct i2c_client *client)
>  {
> +	struct i2c_adapter *adap;
> +
>  	if (IS_ERR_OR_NULL(client))
>  		return;
>  
> +	adap = client->adapter;
> +
> +	if (adap->attach_ops &&
> +	    adap->attach_ops->detach_client)
> +		adap->attach_ops->detach_client(adap, client);
> +
>  	if (client->dev.of_node) {
>  		of_node_clear_flag(client->dev.of_node, OF_POPULATED);
>  		of_node_put(client->dev.of_node);
> diff --git a/include/linux/i2c.h b/include/linux/i2c.h
> index f7c49bbdb8a1..9a385b6de388 100644
> --- a/include/linux/i2c.h
> +++ b/include/linux/i2c.h
> @@ -584,6 +584,21 @@ struct i2c_lock_operations {
>  	void (*unlock_bus)(struct i2c_adapter *adapter, unsigned int flags);
>  };
>  
> +/**
> + * struct i2c_attach_operations - callbacks to notify client attach/detach
> + * @attach_client: Notify of new client being attached
> + * @detach_client: Notify of new client being detached
> + *
> + * Both ops are optional.
> + */
> +struct i2c_attach_operations {
> +	int  (*attach_client)(struct i2c_adapter *adapter,
> +			      const struct i2c_board_info *info,
> +			      const struct i2c_client *client);
> +	void (*detach_client)(struct i2c_adapter *adapter,
> +			      const struct i2c_client *client);
> +};
> +
>  /**
>   * struct i2c_timings - I2C timing information
>   * @bus_freq_hz: the bus frequency in Hz
> @@ -726,6 +741,7 @@ struct i2c_adapter {
>  
>  	/* data fields that are valid for all devices	*/
>  	const struct i2c_lock_operations *lock_ops;
> +	const struct i2c_attach_operations *attach_ops;
>  	struct rt_mutex bus_lock;
>  	struct rt_mutex mux_lock;
>  

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-08 10:40 ` [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913 Tomi Valkeinen
@ 2022-12-11 17:13   ` Laurent Pinchart
  2022-12-11 17:21     ` Laurent Pinchart
  2022-12-13 13:21     ` Tomi Valkeinen
  0 siblings, 2 replies; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-11 17:13 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

Thank you for the patch.

On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
>  1 file changed, 121 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> new file mode 100644
> index 000000000000..3a5b34c6bb64
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> @@ -0,0 +1,121 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer

I think TI consistently writes it "FPD-Link III". If you rename it,
please do so through the whole series.

> +
> +maintainers:
> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +
> +description:
> +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - ti,ds90ub913a-q1

Is the -q1 suffix needed, are there other variants ?

> +
> +  '#gpio-cells':
> +    const: 2
> +
> +  gpio-controller: true
> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      Reference clock connected to the CLKIN pin.
> +
> +  clock-names:
> +    items:
> +      - const: clkin
> +
> +  '#clock-cells':
> +    const: 0
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 input port
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link 3 output port

I'd add

    required:
      - port@0
      - port@1

Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> +
> +  i2c:
> +    $ref: /schemas/i2c/i2c-controller.yaml#
> +    unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - '#gpio-cells'
> +  - gpio-controller
> +  - '#clock-cells'
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    serializer {
> +      compatible = "ti,ds90ub913a-q1";
> +
> +      gpio-controller;
> +      #gpio-cells = <2>;
> +
> +      clocks = <&clk_cam_48M>;
> +      clock-names = "clkin";
> +
> +      #clock-cells = <0>;
> +
> +      ports {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        port@0 {
> +          reg = <0>;
> +          ub913_in: endpoint {
> +            remote-endpoint = <&sensor_out>;
> +          };
> +        };
> +
> +        port@1 {
> +          reg = <1>;
> +          endpoint {
> +            remote-endpoint = <&deser_fpd_in>;
> +          };
> +        };
> +      };
> +
> +      i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        sensor@48 {
> +          compatible = "aptina,mt9v111";
> +          reg = <0x48>;
> +
> +          clocks = <&fixed_clock>;
> +
> +          port {
> +            sensor_out: endpoint {
> +              remote-endpoint = <&ub913_in>;
> +            };
> +          };
> +        };
> +      };
> +    };
> +...

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-11 17:13   ` Laurent Pinchart
@ 2022-12-11 17:21     ` Laurent Pinchart
  2022-12-13 13:36       ` Tomi Valkeinen
  2022-12-13 13:21     ` Tomi Valkeinen
  1 sibling, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-11 17:21 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

I missed one issue.

On Sun, Dec 11, 2022 at 07:13:10PM +0200, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
> > Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
> > 
> > Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> > ---
> >  .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
> >  1 file changed, 121 insertions(+)
> >  create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> > 
> > diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> > new file mode 100644
> > index 000000000000..3a5b34c6bb64
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> > @@ -0,0 +1,121 @@
> > +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> > +%YAML 1.2
> > +---
> > +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
> > +$schema: http://devicetree.org/meta-schemas/core.yaml#
> > +
> > +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
> 
> I think TI consistently writes it "FPD-Link III". If you rename it,
> please do so through the whole series.
> 
> > +
> > +maintainers:
> > +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> > +
> > +description:
> > +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
> > +
> > +properties:
> > +  compatible:
> > +    enum:
> > +      - ti,ds90ub913a-q1
> 
> Is the -q1 suffix needed, are there other variants ?
> 
> > +
> > +  '#gpio-cells':
> > +    const: 2
> > +
> > +  gpio-controller: true
> > +
> > +  clocks:
> > +    maxItems: 1
> > +    description:
> > +      Reference clock connected to the CLKIN pin.
> > +
> > +  clock-names:
> > +    items:
> > +      - const: clkin
> > +
> > +  '#clock-cells':
> > +    const: 0
> > +
> > +  ports:
> > +    $ref: /schemas/graph.yaml#/properties/ports
> > +
> > +    properties:
> > +      port@0:
> > +        $ref: /schemas/graph.yaml#/$defs/port-base
> > +        unevaluatedProperties: false
> > +        description: CSI-2 input port

This should be "Parallel input port".

> > +
> > +        properties:
> > +          endpoint:
> > +            $ref: /schemas/media/video-interfaces.yaml#
> > +            unevaluatedProperties: false

Should at least the bus-width property be mandatory, as the device
supports both 10- and 12-bit inputs ?

> > +
> > +      port@1:
> > +        $ref: /schemas/graph.yaml#/properties/port
> > +        unevaluatedProperties: false
> > +        description: FPD-Link 3 output port
> 
> I'd add
> 
>     required:
>       - port@0
>       - port@1
> 
> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> 
> > +
> > +  i2c:
> > +    $ref: /schemas/i2c/i2c-controller.yaml#
> > +    unevaluatedProperties: false
> > +
> > +required:
> > +  - compatible
> > +  - '#gpio-cells'
> > +  - gpio-controller
> > +  - '#clock-cells'
> > +  - ports
> > +
> > +additionalProperties: false
> > +
> > +examples:
> > +  - |
> > +    #include <dt-bindings/gpio/gpio.h>
> > +
> > +    serializer {
> > +      compatible = "ti,ds90ub913a-q1";
> > +
> > +      gpio-controller;
> > +      #gpio-cells = <2>;
> > +
> > +      clocks = <&clk_cam_48M>;
> > +      clock-names = "clkin";
> > +
> > +      #clock-cells = <0>;
> > +
> > +      ports {
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +
> > +        port@0 {
> > +          reg = <0>;
> > +          ub913_in: endpoint {
> > +            remote-endpoint = <&sensor_out>;
> > +          };
> > +        };
> > +
> > +        port@1 {
> > +          reg = <1>;
> > +          endpoint {
> > +            remote-endpoint = <&deser_fpd_in>;
> > +          };
> > +        };
> > +      };
> > +
> > +      i2c {
> > +        #address-cells = <1>;
> > +        #size-cells = <0>;
> > +
> > +        sensor@48 {
> > +          compatible = "aptina,mt9v111";
> > +          reg = <0x48>;
> > +
> > +          clocks = <&fixed_clock>;
> > +
> > +          port {
> > +            sensor_out: endpoint {
> > +              remote-endpoint = <&ub913_in>;
> > +            };
> > +          };
> > +        };
> > +      };
> > +    };
> > +...

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953
  2022-12-08 10:40 ` [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953 Tomi Valkeinen
  2022-12-09 21:27   ` Rob Herring
@ 2022-12-11 17:34   ` Laurent Pinchart
  2022-12-13 14:06     ` Tomi Valkeinen
  1 sibling, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-11 17:34 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

Thank you for the patch.

On Thu, Dec 08, 2022 at 12:40:02PM +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  .../bindings/media/i2c/ti,ds90ub953.yaml      | 112 ++++++++++++++++++
>  1 file changed, 112 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
> new file mode 100644
> index 000000000000..fd7d25d93e2c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
> @@ -0,0 +1,112 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub953.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments DS90UB953 FPD-Link 3 Serializer
> +
> +maintainers:
> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +
> +description:
> +  The TI DS90UB953 is an FPD-Link 3 video serializer for MIPI CSI-2.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - ti,ds90ub953-q1
> +      - ti,ds90ub971-q1
> +
> +  '#gpio-cells':
> +    const: 2

I would add a description here, to tell what the cells correspond to. In
particular, the first cell selects the GPIO_* pin number, it would be
nice to document that its value should be in the range [0, 3].

Same comment for patch 3/8 (DS90UB913 bindings). There you could also
mention that GPO2 and the output clock are mutually exclusive.

> +
> +  gpio-controller: true
> +

No need for clocks and clock-names for the reference input clock ? Or is
this because you support sync mode only for now ?

> +  '#clock-cells':
> +    const: 0
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 input port
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false

Should the data-lanes property be required for the CSI-2 input ?

> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link 3 output port
> +
> +  i2c:
> +    $ref: /schemas/i2c/i2c-controller.yaml#
> +    unevaluatedProperties: false
> +
> +required:
> +  - compatible
> +  - '#gpio-cells'
> +  - gpio-controller
> +  - '#clock-cells'
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    serializer {
> +      compatible = "ti,ds90ub953-q1";
> +
> +      gpio-controller;
> +      #gpio-cells = <2>;
> +
> +      #clock-cells = <0>;
> +
> +      ports {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        port@0 {
> +          reg = <0>;
> +          ub953_in: endpoint {
> +            clock-lanes = <0>;
> +            data-lanes = <1 2 3 4>;
> +            remote-endpoint = <&sensor_out>;
> +          };
> +        };
> +
> +        port@1 {
> +          reg = <1>;
> +          endpoint {
> +            remote-endpoint = <&deser_fpd_in>;
> +          };
> +        };
> +      };
> +
> +      i2c {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        sensor@1a {
> +          compatible = "sony,imx274";
> +          reg = <0x1a>;
> +
> +          reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;

Maybe add

          clocks = <&serializer>;
	  clock-names = "inck";

to showcase the clock connection ?

> +
> +          port {
> +            sensor_out: endpoint {
> +              remote-endpoint = <&ub953_in>;
> +            };
> +          };
> +        };
> +      };
> +    };
> +...

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2022-12-08 10:40 ` [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960 Tomi Valkeinen
  2022-12-09 21:30   ` Rob Herring
@ 2022-12-11 17:58   ` Laurent Pinchart
  2022-12-13 14:25     ` Tomi Valkeinen
  1 sibling, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-11 17:58 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

Thank you for the patch.

On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
>  1 file changed, 358 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> 
> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> new file mode 100644
> index 000000000000..d8b5e219d420
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> @@ -0,0 +1,358 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
> +
> +maintainers:
> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> +
> +description:
> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
> +  forwarding.
> +
> +properties:
> +  compatible:
> +    enum:
> +      - ti,ds90ub960-q1
> +      - ti,ds90ub9702-q1
> +
> +  reg:
> +    maxItems: 1
> +    description:
> +      i2c addresses for the deserializer and the serializers

s/i2c/I2C/

Same below.

A bit more details would be nice, for instance the order in which
addresses should be specified should be documented. The example below
has one address only, so it's quite unclear. Or is this a left-over,
from before the i2c-alias-pool ?

> +
> +  clocks:
> +    maxItems: 1
> +    description:
> +      Reference clock connected to the REFCLK pin.
> +
> +  clock-names:
> +    items:
> +      - const: refclk
> +
> +  powerdown-gpios:
> +    maxItems: 1
> +    description:
> +      Specifier for the GPIO connected to the PDB pin.
> +
> +  i2c-alias-pool:
> +    $ref: /schemas/types.yaml#/definitions/uint16-array
> +    description:
> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
> +      used to access the remote peripherals. The addresses must be available,
> +      not used by any other peripheral. Each remote peripheral is assigned an
> +      alias from the pool, and transactions to that address will be forwarded
> +      to the remote peripheral, with the address translated to the remote
> +      peripheral's real address.

As this property is optional, should you describe what happens when it's
not specified ?

I would also indicate that the pool doesn't cover the serializers, only
the devices behind them.

> +
> +  links:
> +    type: object
> +    additionalProperties: false
> +
> +    properties:
> +      '#address-cells':
> +        const: 1
> +
> +      '#size-cells':
> +        const: 0
> +
> +      ti,manual-strobe:
> +        type: boolean
> +        description:
> +          Enable manual strobe position and EQ level
> +
> +    patternProperties:
> +      '^link@[0-9a-f]+$':

There can be up to 4 links only, right ? I would then use

      '^link@[0-3]$':

> +        type: object
> +        additionalProperties: false
> +        properties:
> +          reg:
> +            description: The link number
> +            maxItems: 1
> +
> +          i2c-alias:
> +            description:
> +              The i2c address used for the serializer. Transactions to this
> +              address on the i2c bus where the deserializer resides are
> +              forwarded to the serializer.
> +
> +          ti,rx-mode:
> +            $ref: /schemas/types.yaml#/definitions/uint32
> +            enum:
> +              - 0 # RAW10
> +              - 1 # RAW12 HF
> +              - 2 # RAW12 LF
> +              - 3 # CSI2 SYNC
> +              - 4 # CSI2 NON-SYNC
> +            description: FPD-Link Input Mode

Are there use cases for controlling this dynamically (in particular the
sync/non-sync modes) ? Is there anything that could be queried at
runtime from the serializers instead of being specified in DT ?

Same question for the parameters below. Additionally, are there any
parameters that need to be identical for all links ?

> +
> +          ti,cdr-mode:
> +            $ref: /schemas/types.yaml#/definitions/uint32
> +            enum:
> +              - 0 # FPD3
> +              - 1 # FPD4
> +            description: FPD-Link CDR Mode
> +
> +          ti,strobe-pos:
> +            $ref: /schemas/types.yaml#/definitions/int32
> +            minimum: -13
> +            maximum: 13
> +            description: Manual strobe position
> +
> +          ti,eq-level:
> +            $ref: /schemas/types.yaml#/definitions/uint32
> +            maximum: 14
> +            description: Manual EQ level
> +
> +          serializer:
> +            type: object
> +            description: FPD-Link Serializer node
> +
> +        required:
> +          - reg
> +          - i2c-alias
> +          - ti,rx-mode
> +          - serializer
> +
> +  ports:
> +    $ref: /schemas/graph.yaml#/properties/ports
> +
> +    properties:
> +      port@0:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link input 0
> +
> +      port@1:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link input 1
> +
> +      port@2:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link input 2
> +
> +      port@3:
> +        $ref: /schemas/graph.yaml#/properties/port
> +        unevaluatedProperties: false
> +        description: FPD-Link input 3
> +
> +      port@4:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 Output 0
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +            properties:
> +              data-lanes:
> +                minItems: 1
> +                maxItems: 4
> +
> +      port@5:
> +        $ref: /schemas/graph.yaml#/$defs/port-base
> +        unevaluatedProperties: false
> +        description: CSI-2 Output 1
> +
> +        properties:
> +          endpoint:
> +            $ref: /schemas/media/video-interfaces.yaml#
> +            unevaluatedProperties: false
> +
> +            properties:
> +              data-lanes:
> +                minItems: 1
> +                maxItems: 4

The ports should be mandatory, shouldn't they ?

> +
> +required:
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-names
> +  - ports
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    i2c {
> +      clock-frequency = <400000>;
> +      #address-cells = <1>;
> +      #size-cells = <0>;
> +
> +      deser@3d {
> +        compatible = "ti,ds90ub960-q1";
> +        reg = <0x3d>;
> +
> +        clock-names = "refclk";
> +        clocks = <&fixed_clock>;
> +
> +        powerdown-gpios = <&pca9555 7 GPIO_ACTIVE_LOW>;
> +
> +        i2c-alias-pool = /bits/ 16 <0x4a 0x4b 0x4c 0x4d 0x4e 0x4f>;
> +
> +        ports {
> +          #address-cells = <1>;
> +          #size-cells = <0>;
> +
> +          /* Port 0, Camera 0 */
> +          port@0 {
> +            reg = <0>;
> +
> +            ub960_fpd3_1_in: endpoint {
> +              remote-endpoint = <&ub953_1_out>;
> +            };
> +          };
> +
> +          /* Port 1, Camera 1 */
> +          port@1 {
> +            reg = <1>;
> +
> +            ub960_fpd3_2_in: endpoint {
> +              remote-endpoint = <&ub913_2_out>;
> +            };
> +          };
> +
> +          /* Port 4, CSI-2 TX */
> +          port@4 {
> +            reg = <4>;
> +            ds90ub960_0_csi_out: endpoint {
> +              data-lanes = <1 2 3 4>;
> +              link-frequencies = /bits/ 64 <800000000>;
> +              remote-endpoint = <&csi2_phy0>;
> +            };
> +          };
> +        };
> +
> +        links {
> +          #address-cells = <1>;
> +          #size-cells = <0>;
> +
> +          /* Link 0 has DS90UB953 serializer and IMX274 sensor */
> +
> +          link@0 {
> +            reg = <0>;
> +            i2c-alias = <0x44>;
> +
> +            ti,rx-mode = <3>;
> +
> +            serializer1: serializer {
> +              compatible = "ti,ds90ub953-q1";
> +
> +              gpio-controller;
> +              #gpio-cells = <2>;
> +
> +              #clock-cells = <0>;
> +
> +              ports {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                port@0 {
> +                  reg = <0>;
> +                  ub953_1_in: endpoint {
> +                    data-lanes = <1 2 3 4>;
> +                    remote-endpoint = <&sensor_1_out>;
> +                  };
> +                };
> +
> +                port@1 {
> +                  reg = <1>;
> +
> +                  ub953_1_out: endpoint {
> +                    remote-endpoint = <&ub960_fpd3_1_in>;
> +                  };
> +                };
> +              };
> +
> +              i2c {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                sensor@1a {
> +                  compatible = "sony,imx274";
> +                  reg = <0x1a>;
> +
> +                  reset-gpios = <&serializer1 0 GPIO_ACTIVE_LOW>;
> +
> +                  port {
> +                    sensor_1_out: endpoint {
> +                      remote-endpoint = <&ub953_1_in>;
> +                    };
> +                  };
> +                };
> +              };
> +            };
> +          };  /* End of link@0 */
> +
> +          /* Link 1 has DS90UB913 serializer and MT9V111 sensor */
> +
> +          link@1 {
> +            reg = <1>;
> +            i2c-alias = <0x45>;
> +
> +            ti,rx-mode = <0>;
> +
> +            serializer2: serializer {
> +              compatible = "ti,ds90ub913a-q1";
> +
> +              gpio-controller;
> +              #gpio-cells = <2>;
> +
> +              clocks = <&clk_cam_48M>;
> +              clock-names = "clkin";
> +
> +              #clock-cells = <0>;
> +
> +              ports {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                port@0 {
> +                  reg = <0>;
> +                  ub913_2_in: endpoint {
> +                    remote-endpoint = <&sensor_2_out>;
> +                  };
> +                };
> +
> +                port@1 {
> +                  reg = <1>;
> +
> +                  ub913_2_out: endpoint {
> +                    remote-endpoint = <&ub960_fpd3_2_in>;
> +                  };
> +                };
> +              };
> +
> +              i2c {
> +                #address-cells = <1>;
> +                #size-cells = <0>;
> +
> +                sensor@48 {
> +                  compatible = "aptina,mt9v111";
> +                  reg = <0x48>;
> +
> +                  clocks = <&serializer2>;
> +
> +                  port {
> +                    sensor_2_out: endpoint {
> +                      remote-endpoint = <&ub913_2_in>;
> +                    };
> +                  };
> +                };
> +              };
> +            };
> +          }; /* End of link@1 */
> +        };
> +      };
> +    };
> +...

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-08 10:40 ` [PATCH v5 7/8] media: i2c: add DS90UB913 driver Tomi Valkeinen
@ 2022-12-11 18:33   ` Laurent Pinchart
  2022-12-14  6:29     ` Tomi Valkeinen
  2022-12-14  6:48     ` Tomi Valkeinen
  0 siblings, 2 replies; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-11 18:33 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

Thank you for the patch.

On Thu, Dec 08, 2022 at 12:40:05PM +0200, Tomi Valkeinen wrote:
> Add driver for TI DS90UB913 FPDLink-3 Serializer.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  drivers/media/i2c/Kconfig     |  13 +
>  drivers/media/i2c/Makefile    |   2 +-
>  drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++
>  3 files changed, 906 insertions(+), 1 deletion(-)
>  create mode 100644 drivers/media/i2c/ds90ub913.c
> 
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index a23f723b89b5..ff5847aed5ae 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1614,6 +1614,19 @@ config VIDEO_DS90UB960
>  	  Device driver for the Texas Instruments DS90UB960
>  	  FPD-Link III Deserializer
>  
> +config VIDEO_DS90UB913
> +	tristate "TI DS90UB913 Serializer"
> +	depends on OF && I2C && VIDEO_DEV
> +	select MEDIA_CONTROLLER
> +	select VIDEO_V4L2_SUBDEV_API
> +	select V4L2_FWNODE
> +	select REGMAP_I2C
> +	select OF_GPIO
> +	select I2C_ATR
> +	help
> +	  Device driver for the Texas Instruments DS90UB913
> +	  FPD-Link III Serializer.
> +
>  endmenu
>  
>  endif # VIDEO_DEV
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 2735b00437bb..376886f2ded6 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -143,4 +143,4 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
>  obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
>  obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
>  obj-$(CONFIG_VIDEO_DS90UB960)	+= ds90ub960.o
> -
> +obj-$(CONFIG_VIDEO_DS90UB913)	+= ds90ub913.o

Alphabetical order please.

> diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
> new file mode 100644
> index 000000000000..6001a622e622
> --- /dev/null
> +++ b/drivers/media/i2c/ds90ub913.c
> @@ -0,0 +1,892 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Driver for the Texas Instruments DS90UB913 video serializer
> + *
> + * Based on a driver from Luca Ceresoli <luca@lucaceresoli.net>
> + *
> + * Copyright (c) 2019 Luca Ceresoli <luca@lucaceresoli.net>
> + * Copyright (c) 2022 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/gpio/driver.h>
> +#include <linux/i2c-atr.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_graph.h>
> +#include <linux/regmap.h>
> +
> +#include <media/i2c/ds90ub9xx.h>
> +#include <media/v4l2-subdev.h>
> +
> +#define UB913_PAD_SINK			0
> +#define UB913_PAD_SOURCE		1
> +
> +/*
> + * UB913 has 4 gpios, but gpios 3 and 4 are reserved for external oscillator
> + * mode. Thus we only support 2 gpios for now.
> + */
> +#define UB913_NUM_GPIOS			2
> +
> +#define UB913_REG_RESET_CTL			0x01
> +#define UB913_REG_RESET_CTL_DIGITAL_RESET_1	BIT(1)
> +#define UB913_REG_RESET_CTL_DIGITAL_RESET_0	BIT(0)
> +
> +#define UB913_REG_GENERAL_CFG			0x03
> +#define UB913_REG_MODE_SEL			0x05
> +
> +#define UB913_REG_CRC_ERRORS_LSB		0x0a
> +#define UB913_REG_CRC_ERRORS_MSB		0x0b
> +
> +#define UB913_REG_GENERAL_STATUS		0x0c
> +
> +#define UB913_REG_GPIO_CFG(n)			(0x0d + (n))
> +#define UB913_REG_GPIO_CFG_ENABLE(n)		BIT(0 + (n) * 4)
> +#define UB913_REG_GPIO_CFG_DIR_INPUT(n)		BIT(1 + (n) * 4)
> +#define UB913_REG_GPIO_CFG_REMOTE_EN(n)		BIT(2 + (n) * 4)
> +#define UB913_REG_GPIO_CFG_OUT_VAL(n)		BIT(3 + (n) * 4)
> +#define UB913_REG_GPIO_CFG_MASK(n)		(0xf << ((n) * 4))
> +
> +#define UB913_REG_SCL_HIGH_TIME			0x11
> +#define UB913_REG_SCL_LOW_TIME			0x12
> +
> +#define UB913_REG_PLL_OVR			0x35
> +
> +struct ub913_data {
> +	struct i2c_client	*client;
> +	struct regmap		*regmap;
> +	struct clk		*clkin;
> +
> +	u32			gpio_func[UB913_NUM_GPIOS];
> +
> +	struct gpio_chip	gpio_chip;
> +	char			gpio_chip_name[64];
> +
> +	struct v4l2_subdev	sd;
> +	struct media_pad	pads[2];
> +
> +	struct v4l2_async_notifier	notifier;
> +
> +	struct v4l2_subdev	*source_sd;
> +
> +	u64			enabled_source_streams;
> +
> +	struct device_node	*tx_ep_np;
> +
> +	struct clk_hw		*clkout_clk_hw;
> +
> +	struct ds90ub9xx_platform_data *plat_data;
> +
> +	/* Have we succefully called i2c_atr_add_adapter() */
> +	bool			has_i2c_adapter;
> +};
> +
> +static inline struct ub913_data *sd_to_ub913(struct v4l2_subdev *sd)
> +{
> +	return container_of(sd, struct ub913_data, sd);
> +}
> +
> +static int ub913_read(const struct ub913_data *priv, u8 reg, u8 *val)
> +{
> +	unsigned int v;
> +	int ret;
> +
> +	ret = regmap_read(priv->regmap, reg, &v);
> +	if (ret < 0) {
> +		dev_err(&priv->client->dev,
> +			"Cannot read register 0x%02x: %d!\n", reg, ret);
> +		return ret;
> +	}
> +
> +	*val = v;
> +	return 0;
> +}
> +
> +static int ub913_write(const struct ub913_data *priv, u8 reg, u8 val)
> +{
> +	int ret;
> +
> +	ret = regmap_write(priv->regmap, reg, val);
> +	if (ret < 0)
> +		dev_err(&priv->client->dev,
> +			"Cannot write register 0x%02x: %d!\n", reg, ret);
> +
> +	return ret;
> +}
> +
> +/*
> + * GPIO chip
> + */
> +static int ub913_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
> +{
> +	return GPIO_LINE_DIRECTION_OUT;
> +}
> +
> +static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int offset,
> +				    int value)
> +{
> +	struct ub913_data *priv = gpiochip_get_data(gc);
> +	unsigned int reg_idx;
> +	unsigned int field_idx;
> +	int ret;
> +
> +	reg_idx = offset / 2;
> +	field_idx = offset % 2;
> +
> +	ret = regmap_update_bits(
> +		priv->regmap, UB913_REG_GPIO_CFG(reg_idx),
> +		UB913_REG_GPIO_CFG_MASK(field_idx),
> +		UB913_REG_GPIO_CFG_ENABLE(field_idx) |
> +			(value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : 0));
> +
> +	return ret;
> +}
> +
> +static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
> +{
> +	ub913_gpio_direction_out(gc, offset, value);
> +}
> +
> +static int ub913_gpio_of_xlate(struct gpio_chip *gc,
> +			       const struct of_phandle_args *gpiospec,
> +			       u32 *flags)
> +{
> +	if (flags)
> +		*flags = gpiospec->args[1];
> +
> +	return gpiospec->args[0];
> +}
> +
> +static int ub913_gpiochip_probe(struct ub913_data *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct gpio_chip *gc = &priv->gpio_chip;
> +	int ret;
> +
> +	/* Initialize GPIOs 0 and 1 to local control, tri-state */
> +	ub913_write(priv, UB913_REG_GPIO_CFG(0), 0);
> +
> +	scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s",
> +		  dev_name(dev));
> +
> +	gc->label = priv->gpio_chip_name;
> +	gc->parent = dev;
> +	gc->owner = THIS_MODULE;
> +	gc->base = -1;
> +	gc->can_sleep = 1;
> +	gc->ngpio = UB913_NUM_GPIOS;
> +	gc->get_direction = ub913_gpio_get_direction;
> +	gc->direction_output = ub913_gpio_direction_out;
> +	gc->set = ub913_gpio_set;
> +	gc->of_xlate = ub913_gpio_of_xlate;
> +	gc->of_node = priv->client->dev.of_node;
> +	gc->of_gpio_n_cells = 2;
> +
> +	ret = gpiochip_add_data(gc, priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to add GPIOs: %d\n", ret);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ub913_gpiochip_remove(struct ub913_data *priv)
> +{
> +	gpiochip_remove(&priv->gpio_chip);
> +}
> +
> +static int ub913_parse_dt(struct ub913_data *priv)

I would have moved this just before ub913_probe().

> +{
> +	struct device_node *np = priv->client->dev.of_node;
> +	struct device *dev = &priv->client->dev;
> +	int ret;
> +
> +	if (!np) {
> +		dev_err(dev, "OF: no device tree node!\n");
> +		return -ENOENT;
> +	}
> +
> +	/* optional, if absent all GPIO pins are unused */
> +	ret = of_property_read_u32_array(np, "gpio-functions", priv->gpio_func,
> +					 ARRAY_SIZE(priv->gpio_func));
> +	if (ret && ret != -EINVAL)
> +		dev_err(dev, "DT: invalid gpio-functions property (%d)", ret);
> +
> +	return 0;
> +}
> +
> +static const struct regmap_config ub913_regmap_config = {
> +	.name = "ds90ub913",
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.reg_format_endian = REGMAP_ENDIAN_DEFAULT,
> +	.val_format_endian = REGMAP_ENDIAN_DEFAULT,
> +};
> +
> +/*
> + * V4L2
> + */
> +
> +static int ub913_enable_streams(struct v4l2_subdev *sd,
> +				struct v4l2_subdev_state *state, u32 pad,
> +				u64 streams_mask)
> +{
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +	struct media_pad *remote_pad;
> +	u64 sink_streams;
> +	int ret;
> +
> +	if (streams_mask & priv->enabled_source_streams)
> +		return -EALREADY;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(
> +		state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask);
> +
> +	remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
> +
> +	ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index,
> +					 sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	priv->enabled_source_streams |= streams_mask;
> +
> +	return 0;
> +}
> +
> +static int ub913_disable_streams(struct v4l2_subdev *sd,
> +				 struct v4l2_subdev_state *state, u32 pad,
> +				 u64 streams_mask)
> +{
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +	struct media_pad *remote_pad;
> +	int ret;
> +	u64 sink_streams;
> +
> +	if ((streams_mask & priv->enabled_source_streams) != streams_mask)
> +		return -EALREADY;
> +
> +	sink_streams = v4l2_subdev_state_xlate_streams(
> +		state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask);
> +
> +	remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
> +
> +	ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index,
> +					  sink_streams);
> +	if (ret)
> +		return ret;
> +
> +	priv->enabled_source_streams &= ~streams_mask;
> +
> +	return 0;
> +}
> +
> +static int _ub913_set_routing(struct v4l2_subdev *sd,
> +			      struct v4l2_subdev_state *state,
> +			      struct v4l2_subdev_krouting *routing)
> +{
> +	const struct v4l2_mbus_framefmt format = {

static

> +		.width = 640,
> +		.height = 480,
> +		.code = MEDIA_BUS_FMT_UYVY8_2X8,
> +		.field = V4L2_FIELD_NONE,
> +		.colorspace = V4L2_COLORSPACE_SRGB,
> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
> +	};
> +	int ret;
> +
> +	/*
> +	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
> +	 * frame desc is made dynamically allocated.
> +	 */
> +
> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
> +		return -EINVAL;
> +
> +	ret = v4l2_subdev_routing_validate(sd, routing,
> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
> +	if (ret)
> +		return ret;
> +
> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ub913_set_routing(struct v4l2_subdev *sd,
> +			     struct v4l2_subdev_state *state,
> +			     enum v4l2_subdev_format_whence which,
> +			     struct v4l2_subdev_krouting *routing)
> +{
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +
> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
> +		return -EBUSY;
> +
> +	return _ub913_set_routing(sd, state, routing);
> +}
> +
> +static int ub913_get_source_frame_desc(struct ub913_data *priv,
> +				       struct v4l2_mbus_frame_desc *desc)
> +{
> +	struct media_pad *pad;
> +	int ret;
> +
> +	pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
> +	if (!pad)
> +		return -EPIPE;
> +
> +	ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index,
> +			       desc);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

I would inline this in the caller.

> +}
> +
> +static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
> +				struct v4l2_mbus_frame_desc *fd)
> +{
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +	const struct v4l2_subdev_krouting *routing;
> +	struct v4l2_mbus_frame_desc source_fd;
> +	struct v4l2_subdev_route *route;
> +	struct v4l2_subdev_state *state;
> +	int ret = 0;

No need to initialize this to 0.

> +
> +	if (pad != 1) /* first tx pad */

	if (pad != UB913_PAD_SOURCE)

and drop the comment.

> +		return -EINVAL;
> +
> +	ret = ub913_get_source_frame_desc(priv, &source_fd);
> +	if (ret)
> +		return ret;
> +
> +	state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> +	routing = &state->routing;
> +
> +	memset(fd, 0, sizeof(*fd));
> +
> +	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
> +
> +	for_each_active_route(routing, route) {
> +		unsigned int j;

Anything wrong with 'i' ?

> +
> +		if (route->source_pad != pad)
> +			continue;
> +
> +		for (j = 0; j < source_fd.num_entries; ++j)
> +			if (source_fd.entry[j].stream == route->sink_stream)
> +				break;
> +
> +		if (j == source_fd.num_entries) {
> +			dev_err(&priv->client->dev,
> +				"Failed to find stream from source frame desc\n");
> +			ret = -EPIPE;
> +			goto out;
> +		}
> +
> +		fd->entry[fd->num_entries].stream = route->source_stream;
> +
> +		fd->entry[fd->num_entries].flags =
> +			V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;

Shouldn't this be set only if set in the source frame descriptor ?

> +		fd->entry[fd->num_entries].length = source_fd.entry[j].length;
> +		fd->entry[fd->num_entries].pixelcode =
> +			source_fd.entry[j].pixelcode;
> +
> +		fd->num_entries++;
> +	}
> +
> +out:
> +	v4l2_subdev_unlock_state(state);
> +
> +	return ret;
> +}
> +
> +static int ub913_set_fmt(struct v4l2_subdev *sd,
> +			 struct v4l2_subdev_state *state,
> +			 struct v4l2_subdev_format *format)
> +{
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +	struct v4l2_mbus_framefmt *fmt;
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> +	    priv->enabled_source_streams)
> +		return -EBUSY;
> +
> +	/* No transcoding, source and sink formats must match. */
> +	if (format->pad == 1)

	if (format->pad == UB913_PAD_SOURCE)

> +		return v4l2_subdev_get_fmt(sd, state, format);
> +
> +	/* Set sink format */
> +	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
> +						  format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	/* Propagate to source format */
> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> +							   format->stream);
> +	if (!fmt)
> +		return -EINVAL;
> +
> +	*fmt = format->format;
> +
> +	return 0;
> +}
> +
> +static int ub913_init_cfg(struct v4l2_subdev *sd,
> +			  struct v4l2_subdev_state *state)
> +{
> +	struct v4l2_subdev_route routes[] = {
> +		{
> +			.sink_pad = 0,

			.sink_pad = UB913_PAD_SINK,

> +			.sink_stream = 0,
> +			.source_pad = 1,

			.source_pad = UB913_PAD_SOURCE,

> +			.source_stream = 0,
> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> +		},
> +	};
> +
> +	struct v4l2_subdev_krouting routing = {
> +		.num_routes = ARRAY_SIZE(routes),
> +		.routes = routes,
> +	};
> +
> +	return _ub913_set_routing(sd, state, &routing);
> +}
> +
> +static int ub913_log_status(struct v4l2_subdev *sd)
> +{
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +	struct device *dev = &priv->client->dev;
> +	u8 v, v1, v2;
> +
> +	ub913_read(priv, UB913_REG_MODE_SEL, &v);
> +	dev_info(dev, "MODE_SEL %#x\n", v);
> +
> +	ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1);
> +	ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2);
> +	dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8));
> +
> +	ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
> +	dev_info(dev, "GENERAL_STATUS %#x\n", v);
> +
> +	ub913_read(priv, UB913_REG_PLL_OVR, &v);
> +	dev_info(dev, "PLL_OVR %#x\n", v);
> +
> +	/* clear CRC errors */
> +	ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
> +	ub913_write(priv, UB913_REG_GENERAL_CFG, v | BIT(5));
> +	ub913_write(priv, UB913_REG_GENERAL_CFG, v);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_core_ops ub913_subdev_core_ops = {
> +	.log_status = ub913_log_status,
> +};
> +
> +static const struct v4l2_subdev_pad_ops ub913_pad_ops = {
> +	.enable_streams = ub913_enable_streams,
> +	.disable_streams = ub913_disable_streams,
> +	.set_routing = ub913_set_routing,
> +	.get_frame_desc	= ub913_get_frame_desc,
> +	.get_fmt = v4l2_subdev_get_fmt,
> +	.set_fmt = ub913_set_fmt,
> +	.init_cfg = ub913_init_cfg,
> +};
> +
> +static const struct v4l2_subdev_ops ub913_subdev_ops = {
> +	.core = &ub913_subdev_core_ops,
> +	.pad = &ub913_pad_ops,
> +};
> +
> +static const struct media_entity_operations ub913_entity_ops = {
> +	.link_validate = v4l2_subdev_link_validate,
> +};
> +
> +static int ub913_notify_bound(struct v4l2_async_notifier *notifier,
> +			      struct v4l2_subdev *source_subdev,
> +			      struct v4l2_async_subdev *asd)
> +{
> +	struct ub913_data *priv = sd_to_ub913(notifier->sd);
> +	struct device *dev = &priv->client->dev;
> +	unsigned int src_pad;
> +	int ret;
> +
> +	dev_dbg(dev, "Bind %s\n", source_subdev->name);

I'd drop this message.

> +
> +	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
> +					  source_subdev->fwnode,
> +					  MEDIA_PAD_FL_SOURCE);
> +	if (ret < 0) {
> +		dev_err(dev, "Failed to find pad for %s\n",
> +			source_subdev->name);
> +		return ret;
> +	}
> +
> +	priv->source_sd = source_subdev;
> +	src_pad = ret;
> +
> +	ret = media_create_pad_link(&source_subdev->entity, src_pad,
> +				    &priv->sd.entity, 0,

				    &priv->sd.entity, UB913_PAD_SINK,

> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret) {
> +		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
> +			source_subdev->name, src_pad, priv->sd.name);
> +		return ret;
> +	}
> +
> +	dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad);
> +
> +	dev_dbg(dev, "All subdevs bound\n");

I'd drop this message.

> +
> +	return 0;
> +}
> +
> +static void ub913_notify_unbind(struct v4l2_async_notifier *notifier,
> +				struct v4l2_subdev *source_subdev,
> +				struct v4l2_async_subdev *asd)
> +{
> +	struct ub913_data *priv = sd_to_ub913(notifier->sd);
> +	struct device *dev = &priv->client->dev;
> +
> +	dev_dbg(dev, "Unbind %s\n", source_subdev->name);
> +}

This is a no-op so you can drop it.

> +
> +static const struct v4l2_async_notifier_operations ub913_notify_ops = {
> +	.bound = ub913_notify_bound,
> +	.unbind = ub913_notify_unbind,
> +};
> +
> +static int ub913_v4l2_notifier_register(struct ub913_data *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct v4l2_async_subdev *asd;
> +	struct device_node *ep_node;
> +	int ret;
> +
> +	dev_dbg(dev, "register async notif\n");
> +
> +	ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
> +	if (!ep_node) {
> +		dev_err(dev, "No graph endpoint\n");
> +		return -ENODEV;
> +	}
> +
> +	v4l2_async_nf_init(&priv->notifier);
> +
> +	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier,
> +					      of_fwnode_handle(ep_node),
> +					      struct v4l2_async_subdev);
> +
> +	of_node_put(ep_node);
> +
> +	if (IS_ERR(asd)) {
> +		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
> +		v4l2_async_nf_cleanup(&priv->notifier);
> +		return PTR_ERR(asd);
> +	}
> +
> +	priv->notifier.ops = &ub913_notify_ops;
> +
> +	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
> +	if (ret) {
> +		dev_err(dev, "Failed to register subdev_notifier");
> +		v4l2_async_nf_cleanup(&priv->notifier);
> +		return ret;
> +	}
> +
> +	return 0;
> +}
> +
> +static void ub913_v4l2_nf_unregister(struct ub913_data *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +
> +	dev_dbg(dev, "Unregister async notif\n");
> +
> +	v4l2_async_nf_unregister(&priv->notifier);
> +	v4l2_async_nf_cleanup(&priv->notifier);
> +}
> +
> +static int ub913_register_clkout(struct ub913_data *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	const char *name;
> +	int ret;
> +
> +	name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev));
> +
> +	priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name,
> +		__clk_get_name(priv->clkin), 0, 1, 2);
> +
> +	kfree(name);
> +
> +	if (IS_ERR(priv->clkout_clk_hw))
> +		return dev_err_probe(dev, PTR_ERR(priv->clkout_clk_hw),
> +				     "Cannot register clkout hw\n");
> +
> +	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
> +					  priv->clkout_clk_hw);
> +	if (ret)
> +		return dev_err_probe(dev, ret,
> +				     "Cannot add OF clock provider\n");
> +
> +	return 0;
> +}
> +
> +static int ub913_i2c_master_init(struct ub913_data *priv)
> +{
> +	/* i2c fast mode */
> +	u32 scl_high = 600 + 300; /* high period + rise time, ns */
> +	u32 scl_low = 1300 + 300; /* low period + fall time, ns */
> +	unsigned long ref;
> +	int ret;
> +
> +	ref = clk_get_rate(priv->clkin) / 2;
> +
> +	scl_high = div64_u64((u64)scl_high * ref, 1000000000);
> +	scl_low = div64_u64((u64)scl_low * ref, 1000000000);
> +
> +	ret = ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high);
> +	if (ret)
> +		return ret;
> +
> +	ret = ub913_write(priv, UB913_REG_SCL_LOW_TIME, scl_low);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static int ub913_add_i2c_adapter(struct ub913_data *priv)
> +{
> +	struct device *dev = &priv->client->dev;
> +	struct fwnode_handle *i2c_handle;
> +	int ret;
> +
> +	i2c_handle = device_get_named_child_node(dev, "i2c");
> +	if (!i2c_handle)
> +		return 0;
> +
> +	ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port,
> +				  i2c_handle);
> +
> +	fwnode_handle_put(i2c_handle);
> +
> +	if (ret)
> +		return ret;
> +
> +	priv->has_i2c_adapter = true;
> +
> +	return 0;
> +}
> +
> +static void ub913_remove_i2c_adapter(struct ub913_data *priv)
> +{
> +	if (priv->has_i2c_adapter)
> +		i2c_atr_del_adapter(priv->plat_data->atr,
> +				    priv->plat_data->port);

Could i2c_atr_del_adapter() be a no-op if no adapter for the port has
been added ? You could then drop the has_i2c_adapter field. I'm also
wondering if the struct device of the DS90UB913 could be passed instead
of the port, to avoid passing the port throught
ds90ub9xx_platform_data.

> +}
> +
> +static int ub913_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct ub913_data *priv;
> +	int ret;
> +	u8 v;
> +	bool mode_override;
> +	u8 mode;
> +
> +	dev_dbg(dev, "probing, addr 0x%02x\n", client->addr);
> +
> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +	if (!priv)
> +		return -ENOMEM;
> +
> +	priv->client = client;
> +
> +	priv->plat_data = dev_get_platdata(&client->dev);
> +	if (!priv->plat_data) {
> +		dev_err(dev, "Platform data missing\n");
> +		return -ENODEV;
> +	}
> +
> +	priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
> +	if (IS_ERR(priv->regmap)) {
> +		dev_err(dev, "Failed to init regmap\n");
> +		return PTR_ERR(priv->regmap);
> +	}
> +
> +	/* ub913 can also work without ext clock, but that is not supported */
> +	priv->clkin = devm_clk_get(dev, "clkin");
> +	if (IS_ERR(priv->clkin)) {
> +		ret = PTR_ERR(priv->clkin);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(dev, "Cannot get CLKIN (%d)", ret);
> +		return ret;
> +	}
> +
> +	ret = ub913_parse_dt(priv);
> +	if (ret)
> +		return ret;
> +
> +	ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
> +	if (ret)
> +		return ret;
> +
> +	if (!(v & BIT(4))) {

Please add a mcro for this. Same for other magic bits in the driver.

> +		dev_err(dev, "Mode value not stabilized\n");
> +		return -ENODEV;
> +	}
> +
> +	mode_override = v & BIT(5);
> +	mode = v & 0xf;
> +
> +	dev_dbg(dev, "mode from %s: %#x\n",
> +		mode_override ? "reg" : "deserializer", mode);
> +
> +	ret = ub913_i2c_master_init(priv);
> +	if (ret) {
> +		dev_err(dev, "i2c master init failed: %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = ub913_gpiochip_probe(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to init gpiochip\n");
> +		return ret;
> +	}
> +
> +	ret = ub913_register_clkout(priv);
> +	if (ret) {
> +		dev_err(dev, "Failed to register clkout\n");
> +		goto err_gpiochip_remove;
> +	}
> +
> +	v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops);
> +	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
> +	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
> +	priv->sd.entity.ops = &ub913_entity_ops;
> +
> +	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
> +	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;
> +
> +	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
> +	if (ret) {
> +		dev_err(dev, "Failed to init pads\n");
> +		goto err_gpiochip_remove;
> +	}
> +
> +	priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0);
> +	if (priv->tx_ep_np)
> +		priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np);
> +
> +	ret = v4l2_subdev_init_finalize(&priv->sd);
> +	if (ret)
> +		goto err_entity_cleanup;
> +
> +	ret = ub913_v4l2_notifier_register(priv);
> +	if (ret) {
> +		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
> +		goto err_free_state;
> +	}
> +
> +	ret = v4l2_async_register_subdev(&priv->sd);
> +	if (ret) {
> +		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
> +		goto err_unreg_notif;
> +	}
> +
> +	ret = ub913_add_i2c_adapter(priv);
> +	if (ret) {
> +		dev_err(dev, "failed to add remote i2c adapter\n");
> +		goto err_unreg_async_subdev;
> +	}
> +
> +	dev_dbg(dev, "Successfully probed\n");
> +
> +	return 0;
> +
> +err_unreg_async_subdev:
> +	v4l2_async_unregister_subdev(&priv->sd);
> +err_unreg_notif:
> +	ub913_v4l2_nf_unregister(priv);
> +err_free_state:
> +	v4l2_subdev_cleanup(&priv->sd);
> +err_entity_cleanup:
> +	if (priv->tx_ep_np)
> +		of_node_put(priv->tx_ep_np);
> +
> +	media_entity_cleanup(&priv->sd.entity);
> +err_gpiochip_remove:
> +	ub913_gpiochip_remove(priv);
> +
> +	return ret;
> +}
> +
> +static void ub913_remove(struct i2c_client *client)
> +{
> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
> +	struct ub913_data *priv = sd_to_ub913(sd);
> +
> +	dev_dbg(&client->dev, "Removing\n");
> +
> +	ub913_remove_i2c_adapter(priv);
> +
> +	v4l2_async_unregister_subdev(&priv->sd);
> +
> +	ub913_v4l2_nf_unregister(priv);
> +
> +	v4l2_subdev_cleanup(&priv->sd);
> +
> +	if (priv->tx_ep_np)
> +		of_node_put(priv->tx_ep_np);
> +
> +	media_entity_cleanup(&priv->sd.entity);
> +
> +	ub913_gpiochip_remove(priv);
> +}
> +
> +static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} };
> +MODULE_DEVICE_TABLE(i2c, ub913_id);
> +
> +#ifdef CONFIG_OF
> +static const struct of_device_id ub913_dt_ids[] = {
> +	{ .compatible = "ti,ds90ub913a-q1", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, ub913_dt_ids);
> +#endif
> +
> +static struct i2c_driver ds90ub913_driver = {
> +	.probe_new	= ub913_probe,
> +	.remove		= ub913_remove,
> +	.id_table	= ub913_id,
> +	.driver = {
> +		.name	= "ds90ub913a",
> +		.owner = THIS_MODULE,
> +		.of_match_table = of_match_ptr(ub913_dt_ids),
> +	},
> +};
> +
> +module_i2c_driver(ds90ub913_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Texas Instruments DS90UB913 serializer driver");
> +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
> +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>");
> -- 
> 2.34.1
> 

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-11 17:13   ` Laurent Pinchart
  2022-12-11 17:21     ` Laurent Pinchart
@ 2022-12-13 13:21     ` Tomi Valkeinen
  1 sibling, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-13 13:21 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 11/12/2022 19:13, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
>> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
>>   1 file changed, 121 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>> new file mode 100644
>> index 000000000000..3a5b34c6bb64
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>> @@ -0,0 +1,121 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
> 
> I think TI consistently writes it "FPD-Link III". If you rename it,
> please do so through the whole series.

Indeed, good point.

>> +
>> +maintainers:
>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> +
>> +description:
>> +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
>> +
>> +properties:
>> +  compatible:
>> +    enum:
>> +      - ti,ds90ub913a-q1
> 
> Is the -q1 suffix needed, are there other variants ?

I'm not sure, but I couldn't see at a quick glance. I don't know what 
the -q1 means. Even if many of the FPD-Link devices have -q1, there are 
models without -q1, like DS90CF366. Oh, all those non-q1s seem to be 
from Catalog, and -q1 versions from Automotive. But the -q1 seems to be 
part of the model name, so... Maybe it's better to keep it.

https://www.ti.com/interface/high-speed-serdes/fpd-link-serdes/products.html

>> +
>> +  '#gpio-cells':
>> +    const: 2
>> +
>> +  gpio-controller: true
>> +
>> +  clocks:
>> +    maxItems: 1
>> +    description:
>> +      Reference clock connected to the CLKIN pin.
>> +
>> +  clock-names:
>> +    items:
>> +      - const: clkin
>> +
>> +  '#clock-cells':
>> +    const: 0
>> +
>> +  ports:
>> +    $ref: /schemas/graph.yaml#/properties/ports
>> +
>> +    properties:
>> +      port@0:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: CSI-2 input port
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +
>> +      port@1:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: FPD-Link 3 output port
> 
> I'd add
> 
>      required:
>        - port@0
>        - port@1

Ok.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-11 17:21     ` Laurent Pinchart
@ 2022-12-13 13:36       ` Tomi Valkeinen
  2022-12-26 16:46         ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-13 13:36 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 11/12/2022 19:21, Laurent Pinchart wrote:
> I missed one issue.
> 
> On Sun, Dec 11, 2022 at 07:13:10PM +0200, Laurent Pinchart wrote:
>> Hi Tomi,
>>
>> Thank you for the patch.
>>
>> On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
>>> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
>>>
>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>> ---
>>>   .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
>>>   1 file changed, 121 insertions(+)
>>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>>
>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>> new file mode 100644
>>> index 000000000000..3a5b34c6bb64
>>> --- /dev/null
>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>> @@ -0,0 +1,121 @@
>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>> +%YAML 1.2
>>> +---
>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>> +
>>> +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
>>
>> I think TI consistently writes it "FPD-Link III". If you rename it,
>> please do so through the whole series.
>>
>>> +
>>> +maintainers:
>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>> +
>>> +description:
>>> +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
>>> +
>>> +properties:
>>> +  compatible:
>>> +    enum:
>>> +      - ti,ds90ub913a-q1
>>
>> Is the -q1 suffix needed, are there other variants ?
>>
>>> +
>>> +  '#gpio-cells':
>>> +    const: 2
>>> +
>>> +  gpio-controller: true
>>> +
>>> +  clocks:
>>> +    maxItems: 1
>>> +    description:
>>> +      Reference clock connected to the CLKIN pin.
>>> +
>>> +  clock-names:
>>> +    items:
>>> +      - const: clkin
>>> +
>>> +  '#clock-cells':
>>> +    const: 0
>>> +
>>> +  ports:
>>> +    $ref: /schemas/graph.yaml#/properties/ports
>>> +
>>> +    properties:
>>> +      port@0:
>>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>>> +        unevaluatedProperties: false
>>> +        description: CSI-2 input port
> 
> This should be "Parallel input port".

Oops...

>>> +
>>> +        properties:
>>> +          endpoint:
>>> +            $ref: /schemas/media/video-interfaces.yaml#
>>> +            unevaluatedProperties: false
> 
> Should at least the bus-width property be mandatory, as the device
> supports both 10- and 12-bit inputs ?

Hmm... It supports 10-bit, 12-bit HF and 12-bit LF modes. If we need to 
configure the mode based on DT, we need one more property for the HF/LF. 
Then again, the HF/LF is separate from the input port, it's more about 
internal operation and the link to the deserializer.

However, this (the mode) should always be set in the HW via the MODE 
pins. And the driver can read the HW's MODE from the registers. Only in 
some very odd circumstances should the mode be configured by hand (and 
then carefully, as the link to the deserializer will drop).

So the bus-width is not something that the driver would normally use. If 
we would need to define the bus-width and HF/LF in the DT for some 
reason in the future, I think an "old" DT without those specified should 
continue working fine, as the mode can be read from a register.

That said, to complicate matters, the deserializer needs to know the 
serializer's mode before it can communicate with it (and thus, before we 
can read the mode). This is set with the deserializer's "ti,rx-mode" 
property, where you find RAW10, RAW12LF and RAW12HF modes (and for 
ub953, CSI-2 sync and non-sync modes).

So if we would define the bus-width and HF/LF in ub913's properties, the 
deserializer could go peeking the mode from there. But is that a good 
idea... I'm not so sure.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953
  2022-12-11 17:34   ` Laurent Pinchart
@ 2022-12-13 14:06     ` Tomi Valkeinen
  0 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-13 14:06 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 11/12/2022 19:34, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:40:02PM +0200, Tomi Valkeinen wrote:
>> Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   .../bindings/media/i2c/ti,ds90ub953.yaml      | 112 ++++++++++++++++++
>>   1 file changed, 112 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
>> new file mode 100644
>> index 000000000000..fd7d25d93e2c
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
>> @@ -0,0 +1,112 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub953.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Texas Instruments DS90UB953 FPD-Link 3 Serializer
>> +
>> +maintainers:
>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> +
>> +description:
>> +  The TI DS90UB953 is an FPD-Link 3 video serializer for MIPI CSI-2.
>> +
>> +properties:
>> +  compatible:
>> +    enum:
>> +      - ti,ds90ub953-q1
>> +      - ti,ds90ub971-q1
>> +
>> +  '#gpio-cells':
>> +    const: 2
> 
> I would add a description here, to tell what the cells correspond to. In
> particular, the first cell selects the GPIO_* pin number, it would be
> nice to document that its value should be in the range [0, 3].
> 
> Same comment for patch 3/8 (DS90UB913 bindings). There you could also
> mention that GPO2 and the output clock are mutually exclusive.

Yep. I have added this for ub913:
       First cell is the GPO pin number, second cell is the flags. The 
GPO pin
       number must be in range of [0, 3]. Note that GPOs 2 and 3 are not
       available in external oscillator mode.

and this for ub953:
       First cell is the GPIO pin number, second cell is the flags. The 
GPIO pin
       number must be in range of [0, 3].

>> +
>> +  gpio-controller: true
>> +
> 
> No need for clocks and clock-names for the reference input clock ? Or is
> this because you support sync mode only for now ?

Right, I don't have the clock on my hw, but it's probably better to add 
it to the binding already.

>> +  '#clock-cells':
>> +    const: 0
>> +
>> +  ports:
>> +    $ref: /schemas/graph.yaml#/properties/ports
>> +
>> +    properties:
>> +      port@0:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: CSI-2 input port
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
> 
> Should the data-lanes property be required for the CSI-2 input ?

Yes.

>> +
>> +      port@1:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: FPD-Link 3 output port
>> +
>> +  i2c:
>> +    $ref: /schemas/i2c/i2c-controller.yaml#
>> +    unevaluatedProperties: false
>> +
>> +required:
>> +  - compatible
>> +  - '#gpio-cells'
>> +  - gpio-controller
>> +  - '#clock-cells'
>> +  - ports
>> +
>> +additionalProperties: false
>> +
>> +examples:
>> +  - |
>> +    #include <dt-bindings/gpio/gpio.h>
>> +
>> +    serializer {
>> +      compatible = "ti,ds90ub953-q1";
>> +
>> +      gpio-controller;
>> +      #gpio-cells = <2>;
>> +
>> +      #clock-cells = <0>;
>> +
>> +      ports {
>> +        #address-cells = <1>;
>> +        #size-cells = <0>;
>> +
>> +        port@0 {
>> +          reg = <0>;
>> +          ub953_in: endpoint {
>> +            clock-lanes = <0>;
>> +            data-lanes = <1 2 3 4>;
>> +            remote-endpoint = <&sensor_out>;
>> +          };
>> +        };
>> +
>> +        port@1 {
>> +          reg = <1>;
>> +          endpoint {
>> +            remote-endpoint = <&deser_fpd_in>;
>> +          };
>> +        };
>> +      };
>> +
>> +      i2c {
>> +        #address-cells = <1>;
>> +        #size-cells = <0>;
>> +
>> +        sensor@1a {
>> +          compatible = "sony,imx274";
>> +          reg = <0x1a>;
>> +
>> +          reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
> 
> Maybe add
> 
>            clocks = <&serializer>;
> 	  clock-names = "inck";
> 
> to showcase the clock connection ?

Yes, that's a good idea.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2022-12-11 17:58   ` Laurent Pinchart
@ 2022-12-13 14:25     ` Tomi Valkeinen
  2022-12-26 16:52       ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-13 14:25 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 11/12/2022 19:58, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
>> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
>>   1 file changed, 358 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>> new file mode 100644
>> index 000000000000..d8b5e219d420
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>> @@ -0,0 +1,358 @@
>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
>> +
>> +maintainers:
>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> +
>> +description:
>> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
>> +  forwarding.
>> +
>> +properties:
>> +  compatible:
>> +    enum:
>> +      - ti,ds90ub960-q1
>> +      - ti,ds90ub9702-q1
>> +
>> +  reg:
>> +    maxItems: 1
>> +    description:
>> +      i2c addresses for the deserializer and the serializers
> 
> s/i2c/I2C/
> 
> Same below.
> 
> A bit more details would be nice, for instance the order in which
> addresses should be specified should be documented. The example below
> has one address only, so it's quite unclear. Or is this a left-over,
> from before the i2c-alias-pool ?

That's a left over, but not related to i2c-alias-pool but the i2c-alias 
for the serializers. It already says above 'maxItems: 1', so now it only 
contains the deserializer address. I'll drop the desc.

>> +
>> +  clocks:
>> +    maxItems: 1
>> +    description:
>> +      Reference clock connected to the REFCLK pin.
>> +
>> +  clock-names:
>> +    items:
>> +      - const: refclk
>> +
>> +  powerdown-gpios:
>> +    maxItems: 1
>> +    description:
>> +      Specifier for the GPIO connected to the PDB pin.
>> +
>> +  i2c-alias-pool:
>> +    $ref: /schemas/types.yaml#/definitions/uint16-array
>> +    description:
>> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
>> +      used to access the remote peripherals. The addresses must be available,
>> +      not used by any other peripheral. Each remote peripheral is assigned an
>> +      alias from the pool, and transactions to that address will be forwarded
>> +      to the remote peripheral, with the address translated to the remote
>> +      peripheral's real address.
> 
> As this property is optional, should you describe what happens when it's
> not specified ?
> 
> I would also indicate that the pool doesn't cover the serializers, only
> the devices behind them.

Yep, I'll clarify these.

>> +
>> +  links:
>> +    type: object
>> +    additionalProperties: false
>> +
>> +    properties:
>> +      '#address-cells':
>> +        const: 1
>> +
>> +      '#size-cells':
>> +        const: 0
>> +
>> +      ti,manual-strobe:
>> +        type: boolean
>> +        description:
>> +          Enable manual strobe position and EQ level
>> +
>> +    patternProperties:
>> +      '^link@[0-9a-f]+$':
> 
> There can be up to 4 links only, right ? I would then use
> 
>        '^link@[0-3]$':

Yes, I'll change that.

>> +        type: object
>> +        additionalProperties: false
>> +        properties:
>> +          reg:
>> +            description: The link number
>> +            maxItems: 1
>> +
>> +          i2c-alias:
>> +            description:
>> +              The i2c address used for the serializer. Transactions to this
>> +              address on the i2c bus where the deserializer resides are
>> +              forwarded to the serializer.
>> +
>> +          ti,rx-mode:
>> +            $ref: /schemas/types.yaml#/definitions/uint32
>> +            enum:
>> +              - 0 # RAW10
>> +              - 1 # RAW12 HF
>> +              - 2 # RAW12 LF
>> +              - 3 # CSI2 SYNC
>> +              - 4 # CSI2 NON-SYNC
>> +            description: FPD-Link Input Mode
> 
> Are there use cases for controlling this dynamically (in particular the
> sync/non-sync modes) ? Is there anything that could be queried at
> runtime from the serializers instead of being specified in DT ?

We need a link to the serializer before we can query anything from the 
serializer. To have a link, we need the mode... So, as I mentioned in 
the other reply, we could define these in some way in the serializer's 
properties instead of here, but I'm not sure if that's a good change.

The driver can change the mode at runtime (say, from sync to non-sync 
mode, if the HW supports that). But I think this property should reflect 
the HW strapped configuration of the serializer.

> Same question for the parameters below. Additionally, are there any
> parameters that need to be identical for all links ?

The same answer to the cdr-mode. No need to be identical.

The strobe-pos and eq-level are unrelated to this topic.

>> +
>> +          ti,cdr-mode:
>> +            $ref: /schemas/types.yaml#/definitions/uint32
>> +            enum:
>> +              - 0 # FPD3
>> +              - 1 # FPD4
>> +            description: FPD-Link CDR Mode
>> +
>> +          ti,strobe-pos:
>> +            $ref: /schemas/types.yaml#/definitions/int32
>> +            minimum: -13
>> +            maximum: 13
>> +            description: Manual strobe position
>> +
>> +          ti,eq-level:
>> +            $ref: /schemas/types.yaml#/definitions/uint32
>> +            maximum: 14
>> +            description: Manual EQ level
>> +
>> +          serializer:
>> +            type: object
>> +            description: FPD-Link Serializer node
>> +
>> +        required:
>> +          - reg
>> +          - i2c-alias
>> +          - ti,rx-mode
>> +          - serializer
>> +
>> +  ports:
>> +    $ref: /schemas/graph.yaml#/properties/ports
>> +
>> +    properties:
>> +      port@0:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: FPD-Link input 0
>> +
>> +      port@1:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: FPD-Link input 1
>> +
>> +      port@2:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: FPD-Link input 2
>> +
>> +      port@3:
>> +        $ref: /schemas/graph.yaml#/properties/port
>> +        unevaluatedProperties: false
>> +        description: FPD-Link input 3
>> +
>> +      port@4:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: CSI-2 Output 0
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +
>> +            properties:
>> +              data-lanes:
>> +                minItems: 1
>> +                maxItems: 4
>> +
>> +      port@5:
>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>> +        unevaluatedProperties: false
>> +        description: CSI-2 Output 1
>> +
>> +        properties:
>> +          endpoint:
>> +            $ref: /schemas/media/video-interfaces.yaml#
>> +            unevaluatedProperties: false
>> +
>> +            properties:
>> +              data-lanes:
>> +                minItems: 1
>> +                maxItems: 4
> 
> The ports should be mandatory, shouldn't they ?

Did you mean data-lanes? Yes, data-lanes should be mandatory.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-11 18:33   ` Laurent Pinchart
@ 2022-12-14  6:29     ` Tomi Valkeinen
  2022-12-14  6:36       ` Tomi Valkeinen
  2022-12-26 17:01       ` Laurent Pinchart
  2022-12-14  6:48     ` Tomi Valkeinen
  1 sibling, 2 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-14  6:29 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Laurent,

On 11/12/2022 20:33, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:40:05PM +0200, Tomi Valkeinen wrote:
>> Add driver for TI DS90UB913 FPDLink-3 Serializer.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   drivers/media/i2c/Kconfig     |  13 +
>>   drivers/media/i2c/Makefile    |   2 +-
>>   drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++
>>   3 files changed, 906 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/media/i2c/ds90ub913.c
>>
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index a23f723b89b5..ff5847aed5ae 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -1614,6 +1614,19 @@ config VIDEO_DS90UB960
>>   	  Device driver for the Texas Instruments DS90UB960
>>   	  FPD-Link III Deserializer
>>   
>> +config VIDEO_DS90UB913
>> +	tristate "TI DS90UB913 Serializer"
>> +	depends on OF && I2C && VIDEO_DEV
>> +	select MEDIA_CONTROLLER
>> +	select VIDEO_V4L2_SUBDEV_API
>> +	select V4L2_FWNODE
>> +	select REGMAP_I2C
>> +	select OF_GPIO
>> +	select I2C_ATR
>> +	help
>> +	  Device driver for the Texas Instruments DS90UB913
>> +	  FPD-Link III Serializer.
>> +
>>   endmenu
>>   
>>   endif # VIDEO_DEV
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 2735b00437bb..376886f2ded6 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -143,4 +143,4 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
>>   obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
>>   obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
>>   obj-$(CONFIG_VIDEO_DS90UB960)	+= ds90ub960.o
>> -
>> +obj-$(CONFIG_VIDEO_DS90UB913)	+= ds90ub913.o
> 
> Alphabetical order please.

Ok.

>> diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
>> new file mode 100644
>> index 000000000000..6001a622e622
>> --- /dev/null
>> +++ b/drivers/media/i2c/ds90ub913.c
>> @@ -0,0 +1,892 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Driver for the Texas Instruments DS90UB913 video serializer
>> + *
>> + * Based on a driver from Luca Ceresoli <luca@lucaceresoli.net>
>> + *
>> + * Copyright (c) 2019 Luca Ceresoli <luca@lucaceresoli.net>
>> + * Copyright (c) 2022 Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/gpio/driver.h>
>> +#include <linux/i2c-atr.h>
>> +#include <linux/i2c.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_graph.h>
>> +#include <linux/regmap.h>
>> +
>> +#include <media/i2c/ds90ub9xx.h>
>> +#include <media/v4l2-subdev.h>
>> +
>> +#define UB913_PAD_SINK			0
>> +#define UB913_PAD_SOURCE		1
>> +
>> +/*
>> + * UB913 has 4 gpios, but gpios 3 and 4 are reserved for external oscillator
>> + * mode. Thus we only support 2 gpios for now.
>> + */
>> +#define UB913_NUM_GPIOS			2
>> +
>> +#define UB913_REG_RESET_CTL			0x01
>> +#define UB913_REG_RESET_CTL_DIGITAL_RESET_1	BIT(1)
>> +#define UB913_REG_RESET_CTL_DIGITAL_RESET_0	BIT(0)
>> +
>> +#define UB913_REG_GENERAL_CFG			0x03
>> +#define UB913_REG_MODE_SEL			0x05
>> +
>> +#define UB913_REG_CRC_ERRORS_LSB		0x0a
>> +#define UB913_REG_CRC_ERRORS_MSB		0x0b
>> +
>> +#define UB913_REG_GENERAL_STATUS		0x0c
>> +
>> +#define UB913_REG_GPIO_CFG(n)			(0x0d + (n))
>> +#define UB913_REG_GPIO_CFG_ENABLE(n)		BIT(0 + (n) * 4)
>> +#define UB913_REG_GPIO_CFG_DIR_INPUT(n)		BIT(1 + (n) * 4)
>> +#define UB913_REG_GPIO_CFG_REMOTE_EN(n)		BIT(2 + (n) * 4)
>> +#define UB913_REG_GPIO_CFG_OUT_VAL(n)		BIT(3 + (n) * 4)
>> +#define UB913_REG_GPIO_CFG_MASK(n)		(0xf << ((n) * 4))
>> +
>> +#define UB913_REG_SCL_HIGH_TIME			0x11
>> +#define UB913_REG_SCL_LOW_TIME			0x12
>> +
>> +#define UB913_REG_PLL_OVR			0x35
>> +
>> +struct ub913_data {
>> +	struct i2c_client	*client;
>> +	struct regmap		*regmap;
>> +	struct clk		*clkin;
>> +
>> +	u32			gpio_func[UB913_NUM_GPIOS];
>> +
>> +	struct gpio_chip	gpio_chip;
>> +	char			gpio_chip_name[64];
>> +
>> +	struct v4l2_subdev	sd;
>> +	struct media_pad	pads[2];
>> +
>> +	struct v4l2_async_notifier	notifier;
>> +
>> +	struct v4l2_subdev	*source_sd;
>> +
>> +	u64			enabled_source_streams;
>> +
>> +	struct device_node	*tx_ep_np;
>> +
>> +	struct clk_hw		*clkout_clk_hw;
>> +
>> +	struct ds90ub9xx_platform_data *plat_data;
>> +
>> +	/* Have we succefully called i2c_atr_add_adapter() */
>> +	bool			has_i2c_adapter;
>> +};
>> +
>> +static inline struct ub913_data *sd_to_ub913(struct v4l2_subdev *sd)
>> +{
>> +	return container_of(sd, struct ub913_data, sd);
>> +}
>> +
>> +static int ub913_read(const struct ub913_data *priv, u8 reg, u8 *val)
>> +{
>> +	unsigned int v;
>> +	int ret;
>> +
>> +	ret = regmap_read(priv->regmap, reg, &v);
>> +	if (ret < 0) {
>> +		dev_err(&priv->client->dev,
>> +			"Cannot read register 0x%02x: %d!\n", reg, ret);
>> +		return ret;
>> +	}
>> +
>> +	*val = v;
>> +	return 0;
>> +}
>> +
>> +static int ub913_write(const struct ub913_data *priv, u8 reg, u8 val)
>> +{
>> +	int ret;
>> +
>> +	ret = regmap_write(priv->regmap, reg, val);
>> +	if (ret < 0)
>> +		dev_err(&priv->client->dev,
>> +			"Cannot write register 0x%02x: %d!\n", reg, ret);
>> +
>> +	return ret;
>> +}
>> +
>> +/*
>> + * GPIO chip
>> + */
>> +static int ub913_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
>> +{
>> +	return GPIO_LINE_DIRECTION_OUT;
>> +}
>> +
>> +static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int offset,
>> +				    int value)
>> +{
>> +	struct ub913_data *priv = gpiochip_get_data(gc);
>> +	unsigned int reg_idx;
>> +	unsigned int field_idx;
>> +	int ret;
>> +
>> +	reg_idx = offset / 2;
>> +	field_idx = offset % 2;
>> +
>> +	ret = regmap_update_bits(
>> +		priv->regmap, UB913_REG_GPIO_CFG(reg_idx),
>> +		UB913_REG_GPIO_CFG_MASK(field_idx),
>> +		UB913_REG_GPIO_CFG_ENABLE(field_idx) |
>> +			(value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : 0));
>> +
>> +	return ret;
>> +}
>> +
>> +static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
>> +{
>> +	ub913_gpio_direction_out(gc, offset, value);
>> +}
>> +
>> +static int ub913_gpio_of_xlate(struct gpio_chip *gc,
>> +			       const struct of_phandle_args *gpiospec,
>> +			       u32 *flags)
>> +{
>> +	if (flags)
>> +		*flags = gpiospec->args[1];
>> +
>> +	return gpiospec->args[0];
>> +}
>> +
>> +static int ub913_gpiochip_probe(struct ub913_data *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct gpio_chip *gc = &priv->gpio_chip;
>> +	int ret;
>> +
>> +	/* Initialize GPIOs 0 and 1 to local control, tri-state */
>> +	ub913_write(priv, UB913_REG_GPIO_CFG(0), 0);
>> +
>> +	scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s",
>> +		  dev_name(dev));
>> +
>> +	gc->label = priv->gpio_chip_name;
>> +	gc->parent = dev;
>> +	gc->owner = THIS_MODULE;
>> +	gc->base = -1;
>> +	gc->can_sleep = 1;
>> +	gc->ngpio = UB913_NUM_GPIOS;
>> +	gc->get_direction = ub913_gpio_get_direction;
>> +	gc->direction_output = ub913_gpio_direction_out;
>> +	gc->set = ub913_gpio_set;
>> +	gc->of_xlate = ub913_gpio_of_xlate;
>> +	gc->of_node = priv->client->dev.of_node;
>> +	gc->of_gpio_n_cells = 2;
>> +
>> +	ret = gpiochip_add_data(gc, priv);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to add GPIOs: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void ub913_gpiochip_remove(struct ub913_data *priv)
>> +{
>> +	gpiochip_remove(&priv->gpio_chip);
>> +}
>> +
>> +static int ub913_parse_dt(struct ub913_data *priv)
> 
> I would have moved this just before ub913_probe().

Sure.

>> +{
>> +	struct device_node *np = priv->client->dev.of_node;
>> +	struct device *dev = &priv->client->dev;
>> +	int ret;
>> +
>> +	if (!np) {
>> +		dev_err(dev, "OF: no device tree node!\n");
>> +		return -ENOENT;
>> +	}
>> +
>> +	/* optional, if absent all GPIO pins are unused */
>> +	ret = of_property_read_u32_array(np, "gpio-functions", priv->gpio_func,
>> +					 ARRAY_SIZE(priv->gpio_func));
>> +	if (ret && ret != -EINVAL)
>> +		dev_err(dev, "DT: invalid gpio-functions property (%d)", ret);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct regmap_config ub913_regmap_config = {
>> +	.name = "ds90ub913",
>> +	.reg_bits = 8,
>> +	.val_bits = 8,
>> +	.reg_format_endian = REGMAP_ENDIAN_DEFAULT,
>> +	.val_format_endian = REGMAP_ENDIAN_DEFAULT,
>> +};
>> +
>> +/*
>> + * V4L2
>> + */
>> +
>> +static int ub913_enable_streams(struct v4l2_subdev *sd,
>> +				struct v4l2_subdev_state *state, u32 pad,
>> +				u64 streams_mask)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +	struct media_pad *remote_pad;
>> +	u64 sink_streams;
>> +	int ret;
>> +
>> +	if (streams_mask & priv->enabled_source_streams)
>> +		return -EALREADY;
>> +
>> +	sink_streams = v4l2_subdev_state_xlate_streams(
>> +		state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask);
>> +
>> +	remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
>> +
>> +	ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index,
>> +					 sink_streams);
>> +	if (ret)
>> +		return ret;
>> +
>> +	priv->enabled_source_streams |= streams_mask;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ub913_disable_streams(struct v4l2_subdev *sd,
>> +				 struct v4l2_subdev_state *state, u32 pad,
>> +				 u64 streams_mask)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +	struct media_pad *remote_pad;
>> +	int ret;
>> +	u64 sink_streams;
>> +
>> +	if ((streams_mask & priv->enabled_source_streams) != streams_mask)
>> +		return -EALREADY;
>> +
>> +	sink_streams = v4l2_subdev_state_xlate_streams(
>> +		state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask);
>> +
>> +	remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
>> +
>> +	ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index,
>> +					  sink_streams);
>> +	if (ret)
>> +		return ret;
>> +
>> +	priv->enabled_source_streams &= ~streams_mask;
>> +
>> +	return 0;
>> +}
>> +
>> +static int _ub913_set_routing(struct v4l2_subdev *sd,
>> +			      struct v4l2_subdev_state *state,
>> +			      struct v4l2_subdev_krouting *routing)
>> +{
>> +	const struct v4l2_mbus_framefmt format = {
> 
> static

Yep.

>> +		.width = 640,
>> +		.height = 480,
>> +		.code = MEDIA_BUS_FMT_UYVY8_2X8,
>> +		.field = V4L2_FIELD_NONE,
>> +		.colorspace = V4L2_COLORSPACE_SRGB,
>> +		.ycbcr_enc = V4L2_YCBCR_ENC_601,
>> +		.quantization = V4L2_QUANTIZATION_LIM_RANGE,
>> +		.xfer_func = V4L2_XFER_FUNC_SRGB,
>> +	};
>> +	int ret;
>> +
>> +	/*
>> +	 * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until
>> +	 * frame desc is made dynamically allocated.
>> +	 */
>> +
>> +	if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX)
>> +		return -EINVAL;
>> +
>> +	ret = v4l2_subdev_routing_validate(sd, routing,
>> +					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ub913_set_routing(struct v4l2_subdev *sd,
>> +			     struct v4l2_subdev_state *state,
>> +			     enum v4l2_subdev_format_whence which,
>> +			     struct v4l2_subdev_krouting *routing)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +
>> +	if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams)
>> +		return -EBUSY;
>> +
>> +	return _ub913_set_routing(sd, state, routing);
>> +}
>> +
>> +static int ub913_get_source_frame_desc(struct ub913_data *priv,
>> +				       struct v4l2_mbus_frame_desc *desc)
>> +{
>> +	struct media_pad *pad;
>> +	int ret;
>> +
>> +	pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]);
>> +	if (!pad)
>> +		return -EPIPE;
>> +
>> +	ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index,
>> +			       desc);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
> 
> I would inline this in the caller.

Well... Having it separate doesn't provide too much benefit, but it does 
make it a bit cleaner as we don't need to play with the 'unsigned int 
pad' and the 'struct media_pad *pad' in the same function.

>> +}
>> +
>> +static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
>> +				struct v4l2_mbus_frame_desc *fd)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +	const struct v4l2_subdev_krouting *routing;
>> +	struct v4l2_mbus_frame_desc source_fd;
>> +	struct v4l2_subdev_route *route;
>> +	struct v4l2_subdev_state *state;
>> +	int ret = 0;
> 
> No need to initialize this to 0.

Yep.

>> +
>> +	if (pad != 1) /* first tx pad */
> 
> 	if (pad != UB913_PAD_SOURCE)
> 
> and drop the comment.

Yep.

>> +		return -EINVAL;
>> +
>> +	ret = ub913_get_source_frame_desc(priv, &source_fd);
>> +	if (ret)
>> +		return ret;
>> +
>> +	state = v4l2_subdev_lock_and_get_active_state(sd);
>> +
>> +	routing = &state->routing;
>> +
>> +	memset(fd, 0, sizeof(*fd));
>> +
>> +	fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
>> +
>> +	for_each_active_route(routing, route) {
>> +		unsigned int j;
> 
> Anything wrong with 'i' ?

I'm pretty sure it was reserved for the route iteration, before we had 
for_each_active_route() =).

>> +
>> +		if (route->source_pad != pad)
>> +			continue;
>> +
>> +		for (j = 0; j < source_fd.num_entries; ++j)
>> +			if (source_fd.entry[j].stream == route->sink_stream)
>> +				break;
>> +
>> +		if (j == source_fd.num_entries) {
>> +			dev_err(&priv->client->dev,
>> +				"Failed to find stream from source frame desc\n");
>> +			ret = -EPIPE;
>> +			goto out;
>> +		}
>> +
>> +		fd->entry[fd->num_entries].stream = route->source_stream;
>> +
>> +		fd->entry[fd->num_entries].flags =
>> +			V4L2_MBUS_FRAME_DESC_FL_LEN_MAX;
> 
> Shouldn't this be set only if set in the source frame descriptor ?

Yes... I seem to be doing the same in the other ub9xx drivers. I'm not 
sure where this came originally.

>> +		fd->entry[fd->num_entries].length = source_fd.entry[j].length;
>> +		fd->entry[fd->num_entries].pixelcode =
>> +			source_fd.entry[j].pixelcode;
>> +
>> +		fd->num_entries++;
>> +	}
>> +
>> +out:
>> +	v4l2_subdev_unlock_state(state);
>> +
>> +	return ret;
>> +}
>> +
>> +static int ub913_set_fmt(struct v4l2_subdev *sd,
>> +			 struct v4l2_subdev_state *state,
>> +			 struct v4l2_subdev_format *format)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +	struct v4l2_mbus_framefmt *fmt;
>> +
>> +	if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE &&
>> +	    priv->enabled_source_streams)
>> +		return -EBUSY;
>> +
>> +	/* No transcoding, source and sink formats must match. */
>> +	if (format->pad == 1)
> 
> 	if (format->pad == UB913_PAD_SOURCE)

Yep.

>> +		return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> +	/* Set sink format */
>> +	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
>> +						  format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +
>> +	*fmt = format->format;
>> +
>> +	/* Propagate to source format */
>> +	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> +							   format->stream);
>> +	if (!fmt)
>> +		return -EINVAL;
>> +
>> +	*fmt = format->format;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ub913_init_cfg(struct v4l2_subdev *sd,
>> +			  struct v4l2_subdev_state *state)
>> +{
>> +	struct v4l2_subdev_route routes[] = {
>> +		{
>> +			.sink_pad = 0,
> 
> 			.sink_pad = UB913_PAD_SINK,
> 
>> +			.sink_stream = 0,
>> +			.source_pad = 1,
> 
> 			.source_pad = UB913_PAD_SOURCE,

Yep.

>> +			.source_stream = 0,
>> +			.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> +		},
>> +	};
>> +
>> +	struct v4l2_subdev_krouting routing = {
>> +		.num_routes = ARRAY_SIZE(routes),
>> +		.routes = routes,
>> +	};
>> +
>> +	return _ub913_set_routing(sd, state, &routing);
>> +}
>> +
>> +static int ub913_log_status(struct v4l2_subdev *sd)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +	struct device *dev = &priv->client->dev;
>> +	u8 v, v1, v2;
>> +
>> +	ub913_read(priv, UB913_REG_MODE_SEL, &v);
>> +	dev_info(dev, "MODE_SEL %#x\n", v);
>> +
>> +	ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1);
>> +	ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2);
>> +	dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8));
>> +
>> +	ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
>> +	dev_info(dev, "GENERAL_STATUS %#x\n", v);
>> +
>> +	ub913_read(priv, UB913_REG_PLL_OVR, &v);
>> +	dev_info(dev, "PLL_OVR %#x\n", v);
>> +
>> +	/* clear CRC errors */
>> +	ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
>> +	ub913_write(priv, UB913_REG_GENERAL_CFG, v | BIT(5));
>> +	ub913_write(priv, UB913_REG_GENERAL_CFG, v);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct v4l2_subdev_core_ops ub913_subdev_core_ops = {
>> +	.log_status = ub913_log_status,
>> +};
>> +
>> +static const struct v4l2_subdev_pad_ops ub913_pad_ops = {
>> +	.enable_streams = ub913_enable_streams,
>> +	.disable_streams = ub913_disable_streams,
>> +	.set_routing = ub913_set_routing,
>> +	.get_frame_desc	= ub913_get_frame_desc,
>> +	.get_fmt = v4l2_subdev_get_fmt,
>> +	.set_fmt = ub913_set_fmt,
>> +	.init_cfg = ub913_init_cfg,
>> +};
>> +
>> +static const struct v4l2_subdev_ops ub913_subdev_ops = {
>> +	.core = &ub913_subdev_core_ops,
>> +	.pad = &ub913_pad_ops,
>> +};
>> +
>> +static const struct media_entity_operations ub913_entity_ops = {
>> +	.link_validate = v4l2_subdev_link_validate,
>> +};
>> +
>> +static int ub913_notify_bound(struct v4l2_async_notifier *notifier,
>> +			      struct v4l2_subdev *source_subdev,
>> +			      struct v4l2_async_subdev *asd)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(notifier->sd);
>> +	struct device *dev = &priv->client->dev;
>> +	unsigned int src_pad;
>> +	int ret;
>> +
>> +	dev_dbg(dev, "Bind %s\n", source_subdev->name);
> 
> I'd drop this message.

Why is that? Do we get this easily from the v4l2 core? These debug 
prints in the bind/unbind process have been valuable for me.

>> +
>> +	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
>> +					  source_subdev->fwnode,
>> +					  MEDIA_PAD_FL_SOURCE);
>> +	if (ret < 0) {
>> +		dev_err(dev, "Failed to find pad for %s\n",
>> +			source_subdev->name);
>> +		return ret;
>> +	}
>> +
>> +	priv->source_sd = source_subdev;
>> +	src_pad = ret;
>> +
>> +	ret = media_create_pad_link(&source_subdev->entity, src_pad,
>> +				    &priv->sd.entity, 0,
> 
> 				    &priv->sd.entity, UB913_PAD_SINK,

Yep.

>> +				    MEDIA_LNK_FL_ENABLED |
>> +				    MEDIA_LNK_FL_IMMUTABLE);
>> +	if (ret) {
>> +		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
>> +			source_subdev->name, src_pad, priv->sd.name);
>> +		return ret;
>> +	}
>> +
>> +	dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad);
>> +
>> +	dev_dbg(dev, "All subdevs bound\n");
> 
> I'd drop this message.
> 
>> +
>> +	return 0;
>> +}
>> +
>> +static void ub913_notify_unbind(struct v4l2_async_notifier *notifier,
>> +				struct v4l2_subdev *source_subdev,
>> +				struct v4l2_async_subdev *asd)
>> +{
>> +	struct ub913_data *priv = sd_to_ub913(notifier->sd);
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	dev_dbg(dev, "Unbind %s\n", source_subdev->name);
>> +}
> 
> This is a no-op so you can drop it.

This has been useful for development, but, yes, perhaps it's time to 
drop it.

>> +
>> +static const struct v4l2_async_notifier_operations ub913_notify_ops = {
>> +	.bound = ub913_notify_bound,
>> +	.unbind = ub913_notify_unbind,
>> +};
>> +
>> +static int ub913_v4l2_notifier_register(struct ub913_data *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct v4l2_async_subdev *asd;
>> +	struct device_node *ep_node;
>> +	int ret;
>> +
>> +	dev_dbg(dev, "register async notif\n");
>> +
>> +	ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
>> +	if (!ep_node) {
>> +		dev_err(dev, "No graph endpoint\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	v4l2_async_nf_init(&priv->notifier);
>> +
>> +	asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier,
>> +					      of_fwnode_handle(ep_node),
>> +					      struct v4l2_async_subdev);
>> +
>> +	of_node_put(ep_node);
>> +
>> +	if (IS_ERR(asd)) {
>> +		dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
>> +		v4l2_async_nf_cleanup(&priv->notifier);
>> +		return PTR_ERR(asd);
>> +	}
>> +
>> +	priv->notifier.ops = &ub913_notify_ops;
>> +
>> +	ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to register subdev_notifier");
>> +		v4l2_async_nf_cleanup(&priv->notifier);
>> +		return ret;
>> +	}
>> +
>> +	return 0;
>> +}
>> +
>> +static void ub913_v4l2_nf_unregister(struct ub913_data *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +
>> +	dev_dbg(dev, "Unregister async notif\n");
>> +
>> +	v4l2_async_nf_unregister(&priv->notifier);
>> +	v4l2_async_nf_cleanup(&priv->notifier);
>> +}
>> +
>> +static int ub913_register_clkout(struct ub913_data *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	const char *name;
>> +	int ret;
>> +
>> +	name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev));
>> +
>> +	priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name,
>> +		__clk_get_name(priv->clkin), 0, 1, 2);
>> +
>> +	kfree(name);
>> +
>> +	if (IS_ERR(priv->clkout_clk_hw))
>> +		return dev_err_probe(dev, PTR_ERR(priv->clkout_clk_hw),
>> +				     "Cannot register clkout hw\n");
>> +
>> +	ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get,
>> +					  priv->clkout_clk_hw);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret,
>> +				     "Cannot add OF clock provider\n");
>> +
>> +	return 0;
>> +}
>> +
>> +static int ub913_i2c_master_init(struct ub913_data *priv)
>> +{
>> +	/* i2c fast mode */
>> +	u32 scl_high = 600 + 300; /* high period + rise time, ns */
>> +	u32 scl_low = 1300 + 300; /* low period + fall time, ns */
>> +	unsigned long ref;
>> +	int ret;
>> +
>> +	ref = clk_get_rate(priv->clkin) / 2;
>> +
>> +	scl_high = div64_u64((u64)scl_high * ref, 1000000000);
>> +	scl_low = div64_u64((u64)scl_low * ref, 1000000000);
>> +
>> +	ret = ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = ub913_write(priv, UB913_REG_SCL_LOW_TIME, scl_low);
>> +	if (ret)
>> +		return ret;
>> +
>> +	return 0;
>> +}
>> +
>> +static int ub913_add_i2c_adapter(struct ub913_data *priv)
>> +{
>> +	struct device *dev = &priv->client->dev;
>> +	struct fwnode_handle *i2c_handle;
>> +	int ret;
>> +
>> +	i2c_handle = device_get_named_child_node(dev, "i2c");
>> +	if (!i2c_handle)
>> +		return 0;
>> +
>> +	ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port,
>> +				  i2c_handle);
>> +
>> +	fwnode_handle_put(i2c_handle);
>> +
>> +	if (ret)
>> +		return ret;
>> +
>> +	priv->has_i2c_adapter = true;
>> +
>> +	return 0;
>> +}
>> +
>> +static void ub913_remove_i2c_adapter(struct ub913_data *priv)
>> +{
>> +	if (priv->has_i2c_adapter)
>> +		i2c_atr_del_adapter(priv->plat_data->atr,
>> +				    priv->plat_data->port);
> 
> Could i2c_atr_del_adapter() be a no-op if no adapter for the port has
> been added ? You could then drop the has_i2c_adapter field. I'm also

Yes, I think that'd be fine.

> wondering if the struct device of the DS90UB913 could be passed instead
> of the port, to avoid passing the port throught
> ds90ub9xx_platform_data.

Interesting thought. That would limit the number of remote i2c busses to 
one, though. Not a problem for FPD-Link, but I wonder if that's assuming 
too much for the future users. Then again, this is an in-kernel API so 
we could extend it later if needed. So I'll try this out and see if I 
hit any issues.

>> +}
>> +
>> +static int ub913_probe(struct i2c_client *client)
>> +{
>> +	struct device *dev = &client->dev;
>> +	struct ub913_data *priv;
>> +	int ret;
>> +	u8 v;
>> +	bool mode_override;
>> +	u8 mode;
>> +
>> +	dev_dbg(dev, "probing, addr 0x%02x\n", client->addr);
>> +
>> +	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +	if (!priv)
>> +		return -ENOMEM;
>> +
>> +	priv->client = client;
>> +
>> +	priv->plat_data = dev_get_platdata(&client->dev);
>> +	if (!priv->plat_data) {
>> +		dev_err(dev, "Platform data missing\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
>> +	if (IS_ERR(priv->regmap)) {
>> +		dev_err(dev, "Failed to init regmap\n");
>> +		return PTR_ERR(priv->regmap);
>> +	}
>> +
>> +	/* ub913 can also work without ext clock, but that is not supported */
>> +	priv->clkin = devm_clk_get(dev, "clkin");
>> +	if (IS_ERR(priv->clkin)) {
>> +		ret = PTR_ERR(priv->clkin);
>> +		if (ret != -EPROBE_DEFER)
>> +			dev_err(dev, "Cannot get CLKIN (%d)", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = ub913_parse_dt(priv);
>> +	if (ret)
>> +		return ret;
>> +
>> +	ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
>> +	if (ret)
>> +		return ret;
>> +
>> +	if (!(v & BIT(4))) {
> 
> Please add a mcro for this. Same for other magic bits in the driver.

Sure.

>> +		dev_err(dev, "Mode value not stabilized\n");
>> +		return -ENODEV;
>> +	}
>> +
>> +	mode_override = v & BIT(5);
>> +	mode = v & 0xf;
>> +
>> +	dev_dbg(dev, "mode from %s: %#x\n",
>> +		mode_override ? "reg" : "deserializer", mode);
>> +
>> +	ret = ub913_i2c_master_init(priv);
>> +	if (ret) {
>> +		dev_err(dev, "i2c master init failed: %d\n", ret);
>> +		return ret;
>> +	}
>> +
>> +	ret = ub913_gpiochip_probe(priv);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to init gpiochip\n");
>> +		return ret;
>> +	}
>> +
>> +	ret = ub913_register_clkout(priv);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to register clkout\n");
>> +		goto err_gpiochip_remove;
>> +	}
>> +
>> +	v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops);
>> +	priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS;
>> +	priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
>> +	priv->sd.entity.ops = &ub913_entity_ops;
>> +
>> +	priv->pads[0].flags = MEDIA_PAD_FL_SINK;
>> +	priv->pads[1].flags = MEDIA_PAD_FL_SOURCE;
>> +
>> +	ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
>> +	if (ret) {
>> +		dev_err(dev, "Failed to init pads\n");
>> +		goto err_gpiochip_remove;
>> +	}
>> +
>> +	priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0);
>> +	if (priv->tx_ep_np)
>> +		priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np);
>> +
>> +	ret = v4l2_subdev_init_finalize(&priv->sd);
>> +	if (ret)
>> +		goto err_entity_cleanup;
>> +
>> +	ret = ub913_v4l2_notifier_register(priv);
>> +	if (ret) {
>> +		dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
>> +		goto err_free_state;
>> +	}
>> +
>> +	ret = v4l2_async_register_subdev(&priv->sd);
>> +	if (ret) {
>> +		dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
>> +		goto err_unreg_notif;
>> +	}
>> +
>> +	ret = ub913_add_i2c_adapter(priv);
>> +	if (ret) {
>> +		dev_err(dev, "failed to add remote i2c adapter\n");
>> +		goto err_unreg_async_subdev;
>> +	}
>> +
>> +	dev_dbg(dev, "Successfully probed\n");
>> +
>> +	return 0;
>> +
>> +err_unreg_async_subdev:
>> +	v4l2_async_unregister_subdev(&priv->sd);
>> +err_unreg_notif:
>> +	ub913_v4l2_nf_unregister(priv);
>> +err_free_state:
>> +	v4l2_subdev_cleanup(&priv->sd);
>> +err_entity_cleanup:
>> +	if (priv->tx_ep_np)
>> +		of_node_put(priv->tx_ep_np);
>> +
>> +	media_entity_cleanup(&priv->sd.entity);
>> +err_gpiochip_remove:
>> +	ub913_gpiochip_remove(priv);
>> +
>> +	return ret;
>> +}
>> +
>> +static void ub913_remove(struct i2c_client *client)
>> +{
>> +	struct v4l2_subdev *sd = i2c_get_clientdata(client);
>> +	struct ub913_data *priv = sd_to_ub913(sd);
>> +
>> +	dev_dbg(&client->dev, "Removing\n");
>> +
>> +	ub913_remove_i2c_adapter(priv);
>> +
>> +	v4l2_async_unregister_subdev(&priv->sd);
>> +
>> +	ub913_v4l2_nf_unregister(priv);
>> +
>> +	v4l2_subdev_cleanup(&priv->sd);
>> +
>> +	if (priv->tx_ep_np)
>> +		of_node_put(priv->tx_ep_np);
>> +
>> +	media_entity_cleanup(&priv->sd.entity);
>> +
>> +	ub913_gpiochip_remove(priv);
>> +}
>> +
>> +static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} };
>> +MODULE_DEVICE_TABLE(i2c, ub913_id);
>> +
>> +#ifdef CONFIG_OF
>> +static const struct of_device_id ub913_dt_ids[] = {
>> +	{ .compatible = "ti,ds90ub913a-q1", },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, ub913_dt_ids);
>> +#endif
>> +
>> +static struct i2c_driver ds90ub913_driver = {
>> +	.probe_new	= ub913_probe,
>> +	.remove		= ub913_remove,
>> +	.id_table	= ub913_id,
>> +	.driver = {
>> +		.name	= "ds90ub913a",
>> +		.owner = THIS_MODULE,
>> +		.of_match_table = of_match_ptr(ub913_dt_ids),
>> +	},
>> +};
>> +
>> +module_i2c_driver(ds90ub913_driver);
>> +
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("Texas Instruments DS90UB913 serializer driver");
>> +MODULE_AUTHOR("Luca Ceresoli <luca@lucaceresoli.net>");
>> +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>");
>> -- 
>> 2.34.1
>>
> 


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-14  6:29     ` Tomi Valkeinen
@ 2022-12-14  6:36       ` Tomi Valkeinen
  2022-12-26 16:56         ` Laurent Pinchart
  2022-12-26 17:01       ` Laurent Pinchart
  1 sibling, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-14  6:36 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 14/12/2022 08:29, Tomi Valkeinen wrote:

>> wondering if the struct device of the DS90UB913 could be passed instead
>> of the port, to avoid passing the port throught
>> ds90ub9xx_platform_data.
> 
> Interesting thought. That would limit the number of remote i2c busses to 
> one, though. Not a problem for FPD-Link, but I wonder if that's assuming 
> too much for the future users. Then again, this is an in-kernel API so 
> we could extend it later if needed. So I'll try this out and see if I 
> hit any issues.

Right, so the issue with this one would be that it would prevent a 
single device uses. E.g. a single chip which acts as an ATR (similar to 
i2c-mux chips), i.e. it contains both the main and the remote i2c busses.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-11 18:33   ` Laurent Pinchart
  2022-12-14  6:29     ` Tomi Valkeinen
@ 2022-12-14  6:48     ` Tomi Valkeinen
  1 sibling, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-14  6:48 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Laurent,

On 11/12/2022 20:33, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:40:05PM +0200, Tomi Valkeinen wrote:
>> Add driver for TI DS90UB913 FPDLink-3 Serializer.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   drivers/media/i2c/Kconfig     |  13 +
>>   drivers/media/i2c/Makefile    |   2 +-
>>   drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++
>>   3 files changed, 906 insertions(+), 1 deletion(-)
>>   create mode 100644 drivers/media/i2c/ds90ub913.c

Many of your comments here also apply to the ub953 driver. I'll fix those.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-11 16:55   ` Laurent Pinchart
@ 2022-12-19  8:51     ` Luca Ceresoli
  2022-12-19  9:48       ` Andy Shevchenko
  0 siblings, 1 reply; 57+ messages in thread
From: Luca Ceresoli @ 2022-12-19  8:51 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Tomi Valkeinen, linux-media, devicetree, linux-kernel, linux-i2c,
	Rob Herring, Krzysztof Kozlowski, Wolfram Sang, Andy Shevchenko,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Michael Tretter,
	Shawn Tu, Hans Verkuil, Mike Pagano, Krzysztof Hałasa,
	Marek Vasut, Luca Ceresoli

Hello Laurent,

thanks for the feedback and apologies for the delayed reply.

On Sun, 11 Dec 2022 18:55:39 +0200
Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote:

> Hi Tomi and Luca,
> 
> Thank you for the patch.
> 
> On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:
> > From: Luca Ceresoli <luca@lucaceresoli.net>
> > 
> > An adapter might need to know when a new device is about to be
> > added. This will soon bee needed to implement an "I2C address
> > translator" (ATR for short), a device that propagates I2C transactions
> > with a different slave address (an "alias" address). An ATR driver
> > needs to know when a slave is being added to find a suitable alias and
> > program the device translation map.
> > 
> > Add an attach/detach callback pair to allow adapter drivers to be
> > notified of clients being added and removed.  
> 
> This may be a stupid question, but couldn't you instead use the
> BUS_NOTIFY_ADD_DEVICE and BUS_NOTIFY_DEL_DEVICE bus notifiers ?

I'm not sure they would be the correct tool for this task. Bus
notifiers inform about new events on the 'struct bus_type, i.e. any
event on the global i2c bus type. In the i2c world this means being
notified about new _adapters_, which is exactly what
drivers/i2c/i2c-dev.c does.

Here, however, we need to be informed about new _clients_ being added
under a specific adapter. I'm not sure whether the bus notifiers can
inform about new clients in addition of new adapters, but they at least
seem unable to provide per-adapter notification.

Does that seem correct?

Best regards,
-- 
Luca Ceresoli, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-19  8:51     ` Luca Ceresoli
@ 2022-12-19  9:48       ` Andy Shevchenko
  2022-12-26 16:54         ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-19  9:48 UTC (permalink / raw)
  To: Luca Ceresoli
  Cc: Laurent Pinchart, Tomi Valkeinen, linux-media, devicetree,
	linux-kernel, linux-i2c, Rob Herring, Krzysztof Kozlowski,
	Wolfram Sang, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On Mon, Dec 19, 2022 at 09:51:43AM +0100, Luca Ceresoli wrote:
> On Sun, 11 Dec 2022 18:55:39 +0200
> Laurent Pinchart <laurent.pinchart@ideasonboard.com> wrote:
> > On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:

...

> > This may be a stupid question, but couldn't you instead use the
> > BUS_NOTIFY_ADD_DEVICE and BUS_NOTIFY_DEL_DEVICE bus notifiers ?
> 
> I'm not sure they would be the correct tool for this task. Bus
> notifiers inform about new events on the 'struct bus_type, i.e. any
> event on the global i2c bus type. In the i2c world this means being
> notified about new _adapters_, which is exactly what
> drivers/i2c/i2c-dev.c does.
> 
> Here, however, we need to be informed about new _clients_ being added
> under a specific adapter.

This is for example exactly what ACPI integration in I2C framework does. But...

> I'm not sure whether the bus notifiers can
> inform about new clients in addition of new adapters, but they at least
> seem unable to provide per-adapter notification.

...personally I don't like notifiers, they looks like overkill for this task.

> Does that seem correct?

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-13 13:36       ` Tomi Valkeinen
@ 2022-12-26 16:46         ` Laurent Pinchart
  2023-01-04  8:12           ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-26 16:46 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Tue, Dec 13, 2022 at 03:36:49PM +0200, Tomi Valkeinen wrote:
> On 11/12/2022 19:21, Laurent Pinchart wrote:
> > On Sun, Dec 11, 2022 at 07:13:10PM +0200, Laurent Pinchart wrote:
> >> On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
> >>> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
> >>>
> >>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>> ---
> >>>   .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
> >>>   1 file changed, 121 insertions(+)
> >>>   create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> >>>
> >>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> >>> new file mode 100644
> >>> index 000000000000..3a5b34c6bb64
> >>> --- /dev/null
> >>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> >>> @@ -0,0 +1,121 @@
> >>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >>> +%YAML 1.2
> >>> +---
> >>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
> >>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >>> +
> >>> +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
> >>
> >> I think TI consistently writes it "FPD-Link III". If you rename it,
> >> please do so through the whole series.
> >>
> >>> +
> >>> +maintainers:
> >>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>> +
> >>> +description:
> >>> +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
> >>> +
> >>> +properties:
> >>> +  compatible:
> >>> +    enum:
> >>> +      - ti,ds90ub913a-q1
> >>
> >> Is the -q1 suffix needed, are there other variants ?
> >>
> >>> +
> >>> +  '#gpio-cells':
> >>> +    const: 2
> >>> +
> >>> +  gpio-controller: true
> >>> +
> >>> +  clocks:
> >>> +    maxItems: 1
> >>> +    description:
> >>> +      Reference clock connected to the CLKIN pin.
> >>> +
> >>> +  clock-names:
> >>> +    items:
> >>> +      - const: clkin
> >>> +
> >>> +  '#clock-cells':
> >>> +    const: 0
> >>> +
> >>> +  ports:
> >>> +    $ref: /schemas/graph.yaml#/properties/ports
> >>> +
> >>> +    properties:
> >>> +      port@0:
> >>> +        $ref: /schemas/graph.yaml#/$defs/port-base
> >>> +        unevaluatedProperties: false
> >>> +        description: CSI-2 input port
> > 
> > This should be "Parallel input port".
> 
> Oops...
> 
> >>> +
> >>> +        properties:
> >>> +          endpoint:
> >>> +            $ref: /schemas/media/video-interfaces.yaml#
> >>> +            unevaluatedProperties: false
> > 
> > Should at least the bus-width property be mandatory, as the device
> > supports both 10- and 12-bit inputs ?
> 
> Hmm... It supports 10-bit, 12-bit HF and 12-bit LF modes. If we need to 
> configure the mode based on DT, we need one more property for the HF/LF. 
> Then again, the HF/LF is separate from the input port, it's more about 
> internal operation and the link to the deserializer.
>
> However, this (the mode) should always be set in the HW via the MODE 
> pins. And the driver can read the HW's MODE from the registers. Only in 
> some very odd circumstances should the mode be configured by hand (and 
> then carefully, as the link to the deserializer will drop).

Both the DS90UB913A and DS90UB913Q datasheets state that the MODE pin on
the serializer only selects between PCLK and external oscillator modes.

The DS90UB913A datasheet seems to hint in documentation of the mode
select register (0x05) that the mode is selected on the deserializer and
transmitted to the serializer through the back-channel, as the
MODE_OVERRIDE bit is documented as "Allows overriding mode select bits
coming from back-channel" and the MODE_UP_TO_DATE bit as "Status of mode
select from Deserializer is up-to- date". Bits 2 and 3 are however named
"Pin_MODE_12-bit High Frequency" and "Pin_MODE_10-bit mode", which hint
that their value could come from a mode pin, but I see no trace of that
anywhere.

The DS90UB913Q datasheet is similar, with a notable difference in that
it documents bits 1 and 0 as reserved, where the DS90UB913A datasheet
documents them as mode override selection. In the same document, the
DS90UB914Q MODE pin is documented as selecting the 10-bit, 12-bit LF or
12-bit HF operation mode. The datasheet also states that "The
deserializer automatically configures the serializer to correct mode
through the back-channel".

Th DS90UB953 datasheet also hints in the documentation of the
BC_MODE_SELECT register (0x04) that the mode is configured automatically
for backward-compatible DVP mode. For CSI-2 mode, I assume the mode is
strapped from the MODE pin and not configured through the back-channel.

The DS90UB960 datasheet documents how to configure the mode on the
deserializer side, but doesn't state whether the serializer is then
automatically configured through the back-channel (in RAW/DVP mode). I
assume it is, do you have any information about this ?

> So the bus-width is not something that the driver would normally use. If 
> we would need to define the bus-width and HF/LF in the DT for some 
> reason in the future, I think an "old" DT without those specified should 
> continue working fine, as the mode can be read from a register.
> 
> That said, to complicate matters, the deserializer needs to know the 
> serializer's mode before it can communicate with it (and thus, before we 
> can read the mode). This is set with the deserializer's "ti,rx-mode" 
> property, where you find RAW10, RAW12LF and RAW12HF modes (and for 
> ub953, CSI-2 sync and non-sync modes).
> 
> So if we would define the bus-width and HF/LF in ub913's properties, the 
> deserializer could go peeking the mode from there. But is that a good 
> idea... I'm not so sure.

Peeking into another device's DT node isn't great. It looks like the
best option for the DS90UB913 is to specify the mode on the
deserializer's side (either through the MODE strap or with a software
override through DT). In case the serializer mode would need to be
manually overridden in the future, we could add an optional DT property.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2022-12-13 14:25     ` Tomi Valkeinen
@ 2022-12-26 16:52       ` Laurent Pinchart
  2023-01-04  8:59         ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-26 16:52 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Tue, Dec 13, 2022 at 04:25:46PM +0200, Tomi Valkeinen wrote:
> On 11/12/2022 19:58, Laurent Pinchart wrote:
> > On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
> >> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
> >>
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >> ---
> >>   .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
> >>   1 file changed, 358 insertions(+)
> >>   create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>
> >> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >> new file mode 100644
> >> index 000000000000..d8b5e219d420
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >> @@ -0,0 +1,358 @@
> >> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >> +%YAML 1.2
> >> +---
> >> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
> >> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >> +
> >> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
> >> +
> >> +maintainers:
> >> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >> +
> >> +description:
> >> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
> >> +  forwarding.
> >> +
> >> +properties:
> >> +  compatible:
> >> +    enum:
> >> +      - ti,ds90ub960-q1
> >> +      - ti,ds90ub9702-q1
> >> +
> >> +  reg:
> >> +    maxItems: 1
> >> +    description:
> >> +      i2c addresses for the deserializer and the serializers
> > 
> > s/i2c/I2C/
> > 
> > Same below.
> > 
> > A bit more details would be nice, for instance the order in which
> > addresses should be specified should be documented. The example below
> > has one address only, so it's quite unclear. Or is this a left-over,
> > from before the i2c-alias-pool ?
> 
> That's a left over, but not related to i2c-alias-pool but the i2c-alias 
> for the serializers. It already says above 'maxItems: 1', so now it only 
> contains the deserializer address. I'll drop the desc.

Looks good to me.

> >> +
> >> +  clocks:
> >> +    maxItems: 1
> >> +    description:
> >> +      Reference clock connected to the REFCLK pin.
> >> +
> >> +  clock-names:
> >> +    items:
> >> +      - const: refclk
> >> +
> >> +  powerdown-gpios:
> >> +    maxItems: 1
> >> +    description:
> >> +      Specifier for the GPIO connected to the PDB pin.
> >> +
> >> +  i2c-alias-pool:
> >> +    $ref: /schemas/types.yaml#/definitions/uint16-array
> >> +    description:
> >> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
> >> +      used to access the remote peripherals. The addresses must be available,
> >> +      not used by any other peripheral. Each remote peripheral is assigned an
> >> +      alias from the pool, and transactions to that address will be forwarded
> >> +      to the remote peripheral, with the address translated to the remote
> >> +      peripheral's real address.
> > 
> > As this property is optional, should you describe what happens when it's
> > not specified ?
> > 
> > I would also indicate that the pool doesn't cover the serializers, only
> > the devices behind them.
> 
> Yep, I'll clarify these.
> 
> >> +
> >> +  links:
> >> +    type: object
> >> +    additionalProperties: false
> >> +
> >> +    properties:
> >> +      '#address-cells':
> >> +        const: 1
> >> +
> >> +      '#size-cells':
> >> +        const: 0
> >> +
> >> +      ti,manual-strobe:
> >> +        type: boolean
> >> +        description:
> >> +          Enable manual strobe position and EQ level
> >> +
> >> +    patternProperties:
> >> +      '^link@[0-9a-f]+$':
> > 
> > There can be up to 4 links only, right ? I would then use
> > 
> >        '^link@[0-3]$':
> 
> Yes, I'll change that.
> 
> >> +        type: object
> >> +        additionalProperties: false
> >> +        properties:
> >> +          reg:
> >> +            description: The link number
> >> +            maxItems: 1
> >> +
> >> +          i2c-alias:
> >> +            description:
> >> +              The i2c address used for the serializer. Transactions to this
> >> +              address on the i2c bus where the deserializer resides are
> >> +              forwarded to the serializer.
> >> +
> >> +          ti,rx-mode:
> >> +            $ref: /schemas/types.yaml#/definitions/uint32
> >> +            enum:
> >> +              - 0 # RAW10
> >> +              - 1 # RAW12 HF
> >> +              - 2 # RAW12 LF
> >> +              - 3 # CSI2 SYNC
> >> +              - 4 # CSI2 NON-SYNC
> >> +            description: FPD-Link Input Mode
> > 
> > Are there use cases for controlling this dynamically (in particular the
> > sync/non-sync modes) ? Is there anything that could be queried at
> > runtime from the serializers instead of being specified in DT ?
> 
> We need a link to the serializer before we can query anything from the 
> serializer.

I meant querying it from the serializer driver, not the serializer
hardware. This being said, it would likely be difficult to do so, as the
serializer driver would need to probe first. I think I'm thus fine
selecting the mode in DT on the deserializer side.

> To have a link, we need the mode... So, as I mentioned in 
> the other reply, we could define these in some way in the serializer's 
> properties instead of here, but I'm not sure if that's a good change.
> 
> The driver can change the mode at runtime (say, from sync to non-sync 
> mode, if the HW supports that). But I think this property should reflect 
> the HW strapped configuration of the serializer.

That would possibly work for the DS90UB953, but the DS90UB913 has no
strapped mode selected at boot time but is instead configured
automatically through the back-channel (see my last reply to patch 3/8).

When connecting a DS90UB913 to a DS90UB914 deserializer, we can probably
start without mode selection in software, as the MODE pin is meant to
bootstrap that to a correct value which is then automatically
transmitted to the serializer (hardware designs where the mode would
need to be overridden should be rate). However, when connecting multiple
DS90UB913 to a DS90UB960, I can imagine connecting different types of
cameras on the four input ports, so the need to specify the mode
per-port in DT would be more common.

For these reasons, I don't think the ti,rx-mode property can be defined
as reflecting the hardware MODE strap with the DS90UB913. I also think
it would be quite confusing to define it as the desired runtime
configuration for the DS90UB913 and as the hardware MODE strap for the
DS90UB953. Could it be (explicitly) defined as the desired runtime
configuration in all cases ?

> > Same question for the parameters below. Additionally, are there any
> > parameters that need to be identical for all links ?
> 
> The same answer to the cdr-mode. No need to be identical.
> 
> The strobe-pos and eq-level are unrelated to this topic.
> 
> >> +
> >> +          ti,cdr-mode:
> >> +            $ref: /schemas/types.yaml#/definitions/uint32
> >> +            enum:
> >> +              - 0 # FPD3
> >> +              - 1 # FPD4
> >> +            description: FPD-Link CDR Mode
> >> +
> >> +          ti,strobe-pos:
> >> +            $ref: /schemas/types.yaml#/definitions/int32
> >> +            minimum: -13
> >> +            maximum: 13
> >> +            description: Manual strobe position
> >> +
> >> +          ti,eq-level:
> >> +            $ref: /schemas/types.yaml#/definitions/uint32
> >> +            maximum: 14
> >> +            description: Manual EQ level
> >> +
> >> +          serializer:
> >> +            type: object
> >> +            description: FPD-Link Serializer node
> >> +
> >> +        required:
> >> +          - reg
> >> +          - i2c-alias
> >> +          - ti,rx-mode
> >> +          - serializer
> >> +
> >> +  ports:
> >> +    $ref: /schemas/graph.yaml#/properties/ports
> >> +
> >> +    properties:
> >> +      port@0:
> >> +        $ref: /schemas/graph.yaml#/properties/port
> >> +        unevaluatedProperties: false
> >> +        description: FPD-Link input 0
> >> +
> >> +      port@1:
> >> +        $ref: /schemas/graph.yaml#/properties/port
> >> +        unevaluatedProperties: false
> >> +        description: FPD-Link input 1
> >> +
> >> +      port@2:
> >> +        $ref: /schemas/graph.yaml#/properties/port
> >> +        unevaluatedProperties: false
> >> +        description: FPD-Link input 2
> >> +
> >> +      port@3:
> >> +        $ref: /schemas/graph.yaml#/properties/port
> >> +        unevaluatedProperties: false
> >> +        description: FPD-Link input 3
> >> +
> >> +      port@4:
> >> +        $ref: /schemas/graph.yaml#/$defs/port-base
> >> +        unevaluatedProperties: false
> >> +        description: CSI-2 Output 0
> >> +
> >> +        properties:
> >> +          endpoint:
> >> +            $ref: /schemas/media/video-interfaces.yaml#
> >> +            unevaluatedProperties: false
> >> +
> >> +            properties:
> >> +              data-lanes:
> >> +                minItems: 1
> >> +                maxItems: 4
> >> +
> >> +      port@5:
> >> +        $ref: /schemas/graph.yaml#/$defs/port-base
> >> +        unevaluatedProperties: false
> >> +        description: CSI-2 Output 1
> >> +
> >> +        properties:
> >> +          endpoint:
> >> +            $ref: /schemas/media/video-interfaces.yaml#
> >> +            unevaluatedProperties: false
> >> +
> >> +            properties:
> >> +              data-lanes:
> >> +                minItems: 1
> >> +                maxItems: 4
> > 
> > The ports should be mandatory, shouldn't they ?
> 
> Did you mean data-lanes? Yes, data-lanes should be mandatory.

Yes that's what I meant, sorry.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-19  9:48       ` Andy Shevchenko
@ 2022-12-26 16:54         ` Laurent Pinchart
  2022-12-27 20:07           ` Andy Shevchenko
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-26 16:54 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Luca Ceresoli, Tomi Valkeinen, linux-media, devicetree,
	linux-kernel, linux-i2c, Rob Herring, Krzysztof Kozlowski,
	Wolfram Sang, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On Mon, Dec 19, 2022 at 11:48:51AM +0200, Andy Shevchenko wrote:
> On Mon, Dec 19, 2022 at 09:51:43AM +0100, Luca Ceresoli wrote:
> > On Sun, 11 Dec 2022 18:55:39 +0200 Laurent Pinchart wrote:
> > > On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:
> 
> ...
> 
> > > This may be a stupid question, but couldn't you instead use the
> > > BUS_NOTIFY_ADD_DEVICE and BUS_NOTIFY_DEL_DEVICE bus notifiers ?
> > 
> > I'm not sure they would be the correct tool for this task. Bus
> > notifiers inform about new events on the 'struct bus_type, i.e. any
> > event on the global i2c bus type. In the i2c world this means being
> > notified about new _adapters_, which is exactly what
> > drivers/i2c/i2c-dev.c does.
> > 
> > Here, however, we need to be informed about new _clients_ being added
> > under a specific adapter.
> 
> This is for example exactly what ACPI integration in I2C framework does. But...
> 
> > I'm not sure whether the bus notifiers can
> > inform about new clients in addition of new adapters, but they at least
> > seem unable to provide per-adapter notification.
> 
> ...personally I don't like notifiers, they looks like overkill for this task.

But isn't this patch essentially implementing a custom notification
system ? If we need notifiers, why would it be better to add an ad-hoc
API for I2C instead of using bus notifiers ?

> > Does that seem correct?

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-14  6:36       ` Tomi Valkeinen
@ 2022-12-26 16:56         ` Laurent Pinchart
  2022-12-26 19:25           ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-26 16:56 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Wed, Dec 14, 2022 at 08:36:47AM +0200, Tomi Valkeinen wrote:
> On 14/12/2022 08:29, Tomi Valkeinen wrote:
> 
> >> wondering if the struct device of the DS90UB913 could be passed instead
> >> of the port, to avoid passing the port throught
> >> ds90ub9xx_platform_data.
> > 
> > Interesting thought. That would limit the number of remote i2c busses to 
> > one, though. Not a problem for FPD-Link, but I wonder if that's assuming 
> > too much for the future users. Then again, this is an in-kernel API so 
> > we could extend it later if needed. So I'll try this out and see if I 
> > hit any issues.
> 
> Right, so the issue with this one would be that it would prevent a 
> single device uses. E.g. a single chip which acts as an ATR (similar to 
> i2c-mux chips), i.e. it contains both the main and the remote i2c busses.

I don't think I understand this, sorry.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-14  6:29     ` Tomi Valkeinen
  2022-12-14  6:36       ` Tomi Valkeinen
@ 2022-12-26 17:01       ` Laurent Pinchart
  2022-12-27 20:09         ` Andy Shevchenko
  1 sibling, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2022-12-26 17:01 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Wed, Dec 14, 2022 at 08:29:48AM +0200, Tomi Valkeinen wrote:
> On 11/12/2022 20:33, Laurent Pinchart wrote:
> > On Thu, Dec 08, 2022 at 12:40:05PM +0200, Tomi Valkeinen wrote:
> >> Add driver for TI DS90UB913 FPDLink-3 Serializer.
> >>
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >> ---
> >>   drivers/media/i2c/Kconfig     |  13 +
> >>   drivers/media/i2c/Makefile    |   2 +-
> >>   drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++
> >>   3 files changed, 906 insertions(+), 1 deletion(-)
> >>   create mode 100644 drivers/media/i2c/ds90ub913.c

[snip]

> >> diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
> >> new file mode 100644
> >> index 000000000000..6001a622e622
> >> --- /dev/null
> >> +++ b/drivers/media/i2c/ds90ub913.c
> >> @@ -0,0 +1,892 @@

[snip]

> >> +static int ub913_notify_bound(struct v4l2_async_notifier *notifier,
> >> +			      struct v4l2_subdev *source_subdev,
> >> +			      struct v4l2_async_subdev *asd)
> >> +{
> >> +	struct ub913_data *priv = sd_to_ub913(notifier->sd);
> >> +	struct device *dev = &priv->client->dev;
> >> +	unsigned int src_pad;
> >> +	int ret;
> >> +
> >> +	dev_dbg(dev, "Bind %s\n", source_subdev->name);
> > 
> > I'd drop this message.
> 
> Why is that? Do we get this easily from the v4l2 core? These debug 
> prints in the bind/unbind process have been valuable for me.

Because debug messages are not meant to be a tracing infrastructure, and
because, if we want to keep this message, it would be best handled in
the v4l2-async core instead of being duplicated across drivers. Same for
the messages at the end of the function.

> >> +
> >> +	ret = media_entity_get_fwnode_pad(&source_subdev->entity,
> >> +					  source_subdev->fwnode,
> >> +					  MEDIA_PAD_FL_SOURCE);
> >> +	if (ret < 0) {
> >> +		dev_err(dev, "Failed to find pad for %s\n",
> >> +			source_subdev->name);
> >> +		return ret;
> >> +	}
> >> +
> >> +	priv->source_sd = source_subdev;
> >> +	src_pad = ret;
> >> +
> >> +	ret = media_create_pad_link(&source_subdev->entity, src_pad,
> >> +				    &priv->sd.entity, 0,
> > 
> > 				    &priv->sd.entity, UB913_PAD_SINK,
> 
> Yep.
> 
> >> +				    MEDIA_LNK_FL_ENABLED |
> >> +				    MEDIA_LNK_FL_IMMUTABLE);
> >> +	if (ret) {
> >> +		dev_err(dev, "Unable to link %s:%u -> %s:0\n",
> >> +			source_subdev->name, src_pad, priv->sd.name);
> >> +		return ret;
> >> +	}
> >> +
> >> +	dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad);
> >> +
> >> +	dev_dbg(dev, "All subdevs bound\n");
> > 
> > I'd drop this message.
> > 
> >> +
> >> +	return 0;
> >> +}
> >> +
> >> +static void ub913_notify_unbind(struct v4l2_async_notifier *notifier,
> >> +				struct v4l2_subdev *source_subdev,
> >> +				struct v4l2_async_subdev *asd)
> >> +{
> >> +	struct ub913_data *priv = sd_to_ub913(notifier->sd);
> >> +	struct device *dev = &priv->client->dev;
> >> +
> >> +	dev_dbg(dev, "Unbind %s\n", source_subdev->name);
> >> +}
> > 
> > This is a no-op so you can drop it.
> 
> This has been useful for development, but, yes, perhaps it's time to 
> drop it.
> 
> >> +
> >> +static const struct v4l2_async_notifier_operations ub913_notify_ops = {
> >> +	.bound = ub913_notify_bound,
> >> +	.unbind = ub913_notify_unbind,
> >> +};

[snip]

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-26 16:56         ` Laurent Pinchart
@ 2022-12-26 19:25           ` Tomi Valkeinen
  2023-01-04 13:55             ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2022-12-26 19:25 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 26/12/2022 18:56, Laurent Pinchart wrote:
> Hi Tomi,
> 
> On Wed, Dec 14, 2022 at 08:36:47AM +0200, Tomi Valkeinen wrote:
>> On 14/12/2022 08:29, Tomi Valkeinen wrote:
>>
>>>> wondering if the struct device of the DS90UB913 could be passed instead
>>>> of the port, to avoid passing the port throught
>>>> ds90ub9xx_platform_data.
>>>
>>> Interesting thought. That would limit the number of remote i2c busses to
>>> one, though. Not a problem for FPD-Link, but I wonder if that's assuming
>>> too much for the future users. Then again, this is an in-kernel API so
>>> we could extend it later if needed. So I'll try this out and see if I
>>> hit any issues.
>>
>> Right, so the issue with this one would be that it would prevent a
>> single device uses. E.g. a single chip which acts as an ATR (similar to
>> i2c-mux chips), i.e. it contains both the main and the remote i2c busses.
> 
> I don't think I understand this, sorry.

What you are suggesting above means that we'd have a separate device for 
each port of the ATR. Which is fine in our current case, as the i2c 
master busses are behind separate remote devices.

But if you consider a case similar to i2c-mux, where we have a single 
chip with the slave bus and, say, 4 master busses. We would probably 
have only a single device for that.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach
  2022-12-26 16:54         ` Laurent Pinchart
@ 2022-12-27 20:07           ` Andy Shevchenko
  0 siblings, 0 replies; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-27 20:07 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Luca Ceresoli, Tomi Valkeinen, linux-media, devicetree,
	linux-kernel, linux-i2c, Rob Herring, Krzysztof Kozlowski,
	Wolfram Sang, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut, Luca Ceresoli

On Mon, Dec 26, 2022 at 06:54:37PM +0200, Laurent Pinchart wrote:
> On Mon, Dec 19, 2022 at 11:48:51AM +0200, Andy Shevchenko wrote:
> > On Mon, Dec 19, 2022 at 09:51:43AM +0100, Luca Ceresoli wrote:
> > > On Sun, 11 Dec 2022 18:55:39 +0200 Laurent Pinchart wrote:
> > > > On Thu, Dec 08, 2022 at 12:39:59PM +0200, Tomi Valkeinen wrote:

...

> > > > This may be a stupid question, but couldn't you instead use the
> > > > BUS_NOTIFY_ADD_DEVICE and BUS_NOTIFY_DEL_DEVICE bus notifiers ?
> > > 
> > > I'm not sure they would be the correct tool for this task. Bus
> > > notifiers inform about new events on the 'struct bus_type, i.e. any
> > > event on the global i2c bus type. In the i2c world this means being
> > > notified about new _adapters_, which is exactly what
> > > drivers/i2c/i2c-dev.c does.
> > > 
> > > Here, however, we need to be informed about new _clients_ being added
> > > under a specific adapter.
> > 
> > This is for example exactly what ACPI integration in I2C framework does. But...
> > 
> > > I'm not sure whether the bus notifiers can
> > > inform about new clients in addition of new adapters, but they at least
> > > seem unable to provide per-adapter notification.
> > 
> > ...personally I don't like notifiers, they looks like overkill for this task.
> 
> But isn't this patch essentially implementing a custom notification
> system ? If we need notifiers, why would it be better to add an ad-hoc
> API for I2C instead of using bus notifiers ?

Notifiers (as implemented in the Linux kernel) have some drawbacks IIUC.
For example, it's not easy to pass the data over it. Another example is
the context (you wouldn't know when the notifier be called and if it can
hold some locks or not).

That said, each case should be discussed individually despite the generic
approach being present (i.o.w. do not consider notifiers as a silver
bullet in _any_ notification scheme).

> > > Does that seem correct?

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-26 17:01       ` Laurent Pinchart
@ 2022-12-27 20:09         ` Andy Shevchenko
  2023-01-04 13:29           ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Andy Shevchenko @ 2022-12-27 20:09 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Tomi Valkeinen, linux-media, devicetree, linux-kernel, linux-i2c,
	Rob Herring, Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Michael Tretter,
	Shawn Tu, Hans Verkuil, Mike Pagano, Krzysztof Hałasa,
	Marek Vasut

On Mon, Dec 26, 2022 at 07:01:11PM +0200, Laurent Pinchart wrote:
> On Wed, Dec 14, 2022 at 08:29:48AM +0200, Tomi Valkeinen wrote:
> > On 11/12/2022 20:33, Laurent Pinchart wrote:
> > > On Thu, Dec 08, 2022 at 12:40:05PM +0200, Tomi Valkeinen wrote:

...

> > >> +	dev_dbg(dev, "Bind %s\n", source_subdev->name);
> > > 
> > > I'd drop this message.

+1 here.

> > Why is that? Do we get this easily from the v4l2 core? These debug 
> > prints in the bind/unbind process have been valuable for me.
> 
> Because debug messages are not meant to be a tracing infrastructure, and
> because, if we want to keep this message, it would be best handled in
> the v4l2-async core instead of being duplicated across drivers. Same for
> the messages at the end of the function.

I don't think v4l2 needs debug prints. If we consider the above case, the
ftrace already provides that. If we consider something specific to v4l2 to
trace only critical parts, then trace events should be implemented.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2022-12-26 16:46         ` Laurent Pinchart
@ 2023-01-04  8:12           ` Tomi Valkeinen
  2023-01-04 12:52             ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2023-01-04  8:12 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 26/12/2022 18:46, Laurent Pinchart wrote:
> Hi Tomi,
> 
> On Tue, Dec 13, 2022 at 03:36:49PM +0200, Tomi Valkeinen wrote:
>> On 11/12/2022 19:21, Laurent Pinchart wrote:
>>> On Sun, Dec 11, 2022 at 07:13:10PM +0200, Laurent Pinchart wrote:
>>>> On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
>>>>> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
>>>>>
>>>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>>> ---
>>>>>    .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
>>>>>    1 file changed, 121 insertions(+)
>>>>>    create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>>>>
>>>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>>>> new file mode 100644
>>>>> index 000000000000..3a5b34c6bb64
>>>>> --- /dev/null
>>>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>>>>> @@ -0,0 +1,121 @@
>>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>>> +%YAML 1.2
>>>>> +---
>>>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
>>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>>> +
>>>>> +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
>>>>
>>>> I think TI consistently writes it "FPD-Link III". If you rename it,
>>>> please do so through the whole series.
>>>>
>>>>> +
>>>>> +maintainers:
>>>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>>> +
>>>>> +description:
>>>>> +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
>>>>> +
>>>>> +properties:
>>>>> +  compatible:
>>>>> +    enum:
>>>>> +      - ti,ds90ub913a-q1
>>>>
>>>> Is the -q1 suffix needed, are there other variants ?
>>>>
>>>>> +
>>>>> +  '#gpio-cells':
>>>>> +    const: 2
>>>>> +
>>>>> +  gpio-controller: true
>>>>> +
>>>>> +  clocks:
>>>>> +    maxItems: 1
>>>>> +    description:
>>>>> +      Reference clock connected to the CLKIN pin.
>>>>> +
>>>>> +  clock-names:
>>>>> +    items:
>>>>> +      - const: clkin
>>>>> +
>>>>> +  '#clock-cells':
>>>>> +    const: 0
>>>>> +
>>>>> +  ports:
>>>>> +    $ref: /schemas/graph.yaml#/properties/ports
>>>>> +
>>>>> +    properties:
>>>>> +      port@0:
>>>>> +        $ref: /schemas/graph.yaml#/$defs/port-base
>>>>> +        unevaluatedProperties: false
>>>>> +        description: CSI-2 input port
>>>
>>> This should be "Parallel input port".
>>
>> Oops...
>>
>>>>> +
>>>>> +        properties:
>>>>> +          endpoint:
>>>>> +            $ref: /schemas/media/video-interfaces.yaml#
>>>>> +            unevaluatedProperties: false
>>>
>>> Should at least the bus-width property be mandatory, as the device
>>> supports both 10- and 12-bit inputs ?
>>
>> Hmm... It supports 10-bit, 12-bit HF and 12-bit LF modes. If we need to
>> configure the mode based on DT, we need one more property for the HF/LF.
>> Then again, the HF/LF is separate from the input port, it's more about
>> internal operation and the link to the deserializer.
>>
>> However, this (the mode) should always be set in the HW via the MODE
>> pins. And the driver can read the HW's MODE from the registers. Only in
>> some very odd circumstances should the mode be configured by hand (and
>> then carefully, as the link to the deserializer will drop).
> 
> Both the DS90UB913A and DS90UB913Q datasheets state that the MODE pin on
> the serializer only selects between PCLK and external oscillator modes.
> 
> The DS90UB913A datasheet seems to hint in documentation of the mode
> select register (0x05) that the mode is selected on the deserializer and
> transmitted to the serializer through the back-channel, as the
> MODE_OVERRIDE bit is documented as "Allows overriding mode select bits
> coming from back-channel" and the MODE_UP_TO_DATE bit as "Status of mode
> select from Deserializer is up-to- date". Bits 2 and 3 are however named
> "Pin_MODE_12-bit High Frequency" and "Pin_MODE_10-bit mode", which hint
> that their value could come from a mode pin, but I see no trace of that
> anywhere.
> 
> The DS90UB913Q datasheet is similar, with a notable difference in that
> it documents bits 1 and 0 as reserved, where the DS90UB913A datasheet
> documents them as mode override selection. In the same document, the
> DS90UB914Q MODE pin is documented as selecting the 10-bit, 12-bit LF or
> 12-bit HF operation mode. The datasheet also states that "The
> deserializer automatically configures the serializer to correct mode
> through the back-channel".
> 
> Th DS90UB953 datasheet also hints in the documentation of the
> BC_MODE_SELECT register (0x04) that the mode is configured automatically
> for backward-compatible DVP mode. For CSI-2 mode, I assume the mode is
> strapped from the MODE pin and not configured through the back-channel.
> 
> The DS90UB960 datasheet documents how to configure the mode on the
> deserializer side, but doesn't state whether the serializer is then
> automatically configured through the back-channel (in RAW/DVP mode). I
> assume it is, do you have any information about this ?

I have to admit I had missed the mode management of the RAW mode while 
going through all this. I had mostly looked at the UB953's CSI mode.

I don't have more information, but your analysis looks correct to me. So 
the whole mode thing is an interesting mix of serializer & deserializer 
HW straps, deserializer sending the (RAW) mode to the serializer, and 
then the override registers on the serializer side.

As to the original question, should we have mandatory bus-width for 
ub913... I don't think it would be useful, even after the updated 
understanding about modes. Do you agree?

>> So the bus-width is not something that the driver would normally use. If
>> we would need to define the bus-width and HF/LF in the DT for some
>> reason in the future, I think an "old" DT without those specified should
>> continue working fine, as the mode can be read from a register.
>>
>> That said, to complicate matters, the deserializer needs to know the
>> serializer's mode before it can communicate with it (and thus, before we
>> can read the mode). This is set with the deserializer's "ti,rx-mode"
>> property, where you find RAW10, RAW12LF and RAW12HF modes (and for
>> ub953, CSI-2 sync and non-sync modes).
>>
>> So if we would define the bus-width and HF/LF in ub913's properties, the
>> deserializer could go peeking the mode from there. But is that a good
>> idea... I'm not so sure.
> 
> Peeking into another device's DT node isn't great. It looks like the
> best option for the DS90UB913 is to specify the mode on the
> deserializer's side (either through the MODE strap or with a software
> override through DT). In case the serializer mode would need to be
> manually overridden in the future, we could add an optional DT property.

I don't like the mode strap on UB960 side, as it's for all ports. It 
works in certain cases, of course, but if we anyway need the mode in DT 
to allow port-specific configuration, I think it's just easier to always 
require the DT mode and always use that, overriding the strap mode.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953
  2022-12-09 21:27   ` Rob Herring
@ 2023-01-04  8:26     ` Tomi Valkeinen
  0 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2023-01-04  8:26 UTC (permalink / raw)
  To: Rob Herring
  Cc: linux-media, devicetree, linux-kernel, linux-i2c,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Laurent Pinchart, Michael Tretter, Shawn Tu, Hans Verkuil,
	Mike Pagano, Krzysztof Hałasa, Marek Vasut

Hi Rob,

On 09/12/2022 23:27, Rob Herring wrote:
> On Thu, Dec 08, 2022 at 12:40:02PM +0200, Tomi Valkeinen wrote:
>> Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.
> 
> Seems like this and DS90UB913 binding could be combined. I couldn't spot
> a difference.

They are indeed quite similar, but there are a few diffs, especially 
after fixing Laurent's review comments.

E.g, as the UB913 is a parallel video serializer and the UB953 is a 
CSI-2 serializer, the input port on UB913 has 'pclk-sample' property but 
UB953 has 'data-lanes' property. The descriptions differ also a bit for 
the above mentioned difference.

The above points would still allow combining the bindings, though. But I 
feel the UB913 is somewhat a different class of serializer device 
compared to UB953 (and UB971 which the UB953's binding also supports), 
so my gut feeling says it's better to keep them separate. But I don't 
have much experience on maintaining such bindings, and, afaik, we could 
always split the bindings later if needed.

So... Do you have a preference on either way? Or maybe we can come back 
to this after I send the next version with the updates.

> In the subjects, drop 'binding for'. The prefix says this is a binding.
> Maybe add 'Serializer'.

Ok.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2022-12-26 16:52       ` Laurent Pinchart
@ 2023-01-04  8:59         ` Tomi Valkeinen
  2023-01-04 12:57           ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2023-01-04  8:59 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 26/12/2022 18:52, Laurent Pinchart wrote:
> Hi Tomi,
> 
> On Tue, Dec 13, 2022 at 04:25:46PM +0200, Tomi Valkeinen wrote:
>> On 11/12/2022 19:58, Laurent Pinchart wrote:
>>> On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
>>>> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>>>>
>>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>> ---
>>>>    .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
>>>>    1 file changed, 358 insertions(+)
>>>>    create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>>>
>>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>>> new file mode 100644
>>>> index 000000000000..d8b5e219d420
>>>> --- /dev/null
>>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>>> @@ -0,0 +1,358 @@
>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>> +%YAML 1.2
>>>> +---
>>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>> +
>>>> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
>>>> +
>>>> +maintainers:
>>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>> +
>>>> +description:
>>>> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
>>>> +  forwarding.
>>>> +
>>>> +properties:
>>>> +  compatible:
>>>> +    enum:
>>>> +      - ti,ds90ub960-q1
>>>> +      - ti,ds90ub9702-q1
>>>> +
>>>> +  reg:
>>>> +    maxItems: 1
>>>> +    description:
>>>> +      i2c addresses for the deserializer and the serializers
>>>
>>> s/i2c/I2C/
>>>
>>> Same below.
>>>
>>> A bit more details would be nice, for instance the order in which
>>> addresses should be specified should be documented. The example below
>>> has one address only, so it's quite unclear. Or is this a left-over,
>>> from before the i2c-alias-pool ?
>>
>> That's a left over, but not related to i2c-alias-pool but the i2c-alias
>> for the serializers. It already says above 'maxItems: 1', so now it only
>> contains the deserializer address. I'll drop the desc.
> 
> Looks good to me.
> 
>>>> +
>>>> +  clocks:
>>>> +    maxItems: 1
>>>> +    description:
>>>> +      Reference clock connected to the REFCLK pin.
>>>> +
>>>> +  clock-names:
>>>> +    items:
>>>> +      - const: refclk
>>>> +
>>>> +  powerdown-gpios:
>>>> +    maxItems: 1
>>>> +    description:
>>>> +      Specifier for the GPIO connected to the PDB pin.
>>>> +
>>>> +  i2c-alias-pool:
>>>> +    $ref: /schemas/types.yaml#/definitions/uint16-array
>>>> +    description:
>>>> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
>>>> +      used to access the remote peripherals. The addresses must be available,
>>>> +      not used by any other peripheral. Each remote peripheral is assigned an
>>>> +      alias from the pool, and transactions to that address will be forwarded
>>>> +      to the remote peripheral, with the address translated to the remote
>>>> +      peripheral's real address.
>>>
>>> As this property is optional, should you describe what happens when it's
>>> not specified ?
>>>
>>> I would also indicate that the pool doesn't cover the serializers, only
>>> the devices behind them.
>>
>> Yep, I'll clarify these.
>>
>>>> +
>>>> +  links:
>>>> +    type: object
>>>> +    additionalProperties: false
>>>> +
>>>> +    properties:
>>>> +      '#address-cells':
>>>> +        const: 1
>>>> +
>>>> +      '#size-cells':
>>>> +        const: 0
>>>> +
>>>> +      ti,manual-strobe:
>>>> +        type: boolean
>>>> +        description:
>>>> +          Enable manual strobe position and EQ level
>>>> +
>>>> +    patternProperties:
>>>> +      '^link@[0-9a-f]+$':
>>>
>>> There can be up to 4 links only, right ? I would then use
>>>
>>>         '^link@[0-3]$':
>>
>> Yes, I'll change that.
>>
>>>> +        type: object
>>>> +        additionalProperties: false
>>>> +        properties:
>>>> +          reg:
>>>> +            description: The link number
>>>> +            maxItems: 1
>>>> +
>>>> +          i2c-alias:
>>>> +            description:
>>>> +              The i2c address used for the serializer. Transactions to this
>>>> +              address on the i2c bus where the deserializer resides are
>>>> +              forwarded to the serializer.
>>>> +
>>>> +          ti,rx-mode:
>>>> +            $ref: /schemas/types.yaml#/definitions/uint32
>>>> +            enum:
>>>> +              - 0 # RAW10
>>>> +              - 1 # RAW12 HF
>>>> +              - 2 # RAW12 LF
>>>> +              - 3 # CSI2 SYNC
>>>> +              - 4 # CSI2 NON-SYNC
>>>> +            description: FPD-Link Input Mode
>>>
>>> Are there use cases for controlling this dynamically (in particular the
>>> sync/non-sync modes) ? Is there anything that could be queried at
>>> runtime from the serializers instead of being specified in DT ?
>>
>> We need a link to the serializer before we can query anything from the
>> serializer.
> 
> I meant querying it from the serializer driver, not the serializer
> hardware. This being said, it would likely be difficult to do so, as the
> serializer driver would need to probe first. I think I'm thus fine
> selecting the mode in DT on the deserializer side.
> 
>> To have a link, we need the mode... So, as I mentioned in
>> the other reply, we could define these in some way in the serializer's
>> properties instead of here, but I'm not sure if that's a good change.
>>
>> The driver can change the mode at runtime (say, from sync to non-sync
>> mode, if the HW supports that). But I think this property should reflect
>> the HW strapped configuration of the serializer.
> 
> That would possibly work for the DS90UB953, but the DS90UB913 has no
> strapped mode selected at boot time but is instead configured
> automatically through the back-channel (see my last reply to patch 3/8).

Indeed.

> When connecting a DS90UB913 to a DS90UB914 deserializer, we can probably
> start without mode selection in software, as the MODE pin is meant to
> bootstrap that to a correct value which is then automatically
> transmitted to the serializer (hardware designs where the mode would
> need to be overridden should be rate). However, when connecting multiple

I don't know if that's true. I guess it depends on how you see the deser 
and the camera module. Are they part of the same HW design or not? In my 
setups they are quite separate, and I connect different kinds of camera 
modules to my deserializers. But I can see that if you create a, say, 
car, you'd have both sides known at design time and would never change.

> DS90UB913 to a DS90UB960, I can imagine connecting different types of
> cameras on the four input ports, so the need to specify the mode
> per-port in DT would be more common.

Right, and even with UB914, you might well design the deserializer side 
with, say, RAW10 sensors, but later in the cycle you'd need to change to 
a RAW12 sensor. Depending on the deser mode strap would require you to 
do a HW change on the deser side too.

As I said in the other mail, I don't like the deser's strap, and I think 
we should just basically ignore it as we can provide the necessary data 
in the DT.

> For these reasons, I don't think the ti,rx-mode property can be defined
> as reflecting the hardware MODE strap with the DS90UB913. I also think
> it would be quite confusing to define it as the desired runtime
> configuration for the DS90UB913 and as the hardware MODE strap for the
> DS90UB953. Could it be (explicitly) defined as the desired runtime
> configuration in all cases ?

That sounds bad in a DT context =). You're right that the rx-mode can't 
be defined as reflecting the serializer mode strap, but I think we can 
define it as reflecting the default operation mode of the serializer 
hardware (or maybe rather the camera module).

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913
  2023-01-04  8:12           ` Tomi Valkeinen
@ 2023-01-04 12:52             ` Laurent Pinchart
  0 siblings, 0 replies; 57+ messages in thread
From: Laurent Pinchart @ 2023-01-04 12:52 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Wed, Jan 04, 2023 at 10:12:56AM +0200, Tomi Valkeinen wrote:
> On 26/12/2022 18:46, Laurent Pinchart wrote:
> > On Tue, Dec 13, 2022 at 03:36:49PM +0200, Tomi Valkeinen wrote:
> >> On 11/12/2022 19:21, Laurent Pinchart wrote:
> >>> On Sun, Dec 11, 2022 at 07:13:10PM +0200, Laurent Pinchart wrote:
> >>>> On Thu, Dec 08, 2022 at 12:40:01PM +0200, Tomi Valkeinen wrote:
> >>>>> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
> >>>>>
> >>>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>>>> ---
> >>>>>    .../bindings/media/i2c/ti,ds90ub913.yaml      | 121 ++++++++++++++++++
> >>>>>    1 file changed, 121 insertions(+)
> >>>>>    create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> >>>>>
> >>>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> >>>>> new file mode 100644
> >>>>> index 000000000000..3a5b34c6bb64
> >>>>> --- /dev/null
> >>>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
> >>>>> @@ -0,0 +1,121 @@
> >>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >>>>> +%YAML 1.2
> >>>>> +---
> >>>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml#
> >>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >>>>> +
> >>>>> +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer
> >>>>
> >>>> I think TI consistently writes it "FPD-Link III". If you rename it,
> >>>> please do so through the whole series.
> >>>>
> >>>>> +
> >>>>> +maintainers:
> >>>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>>>> +
> >>>>> +description:
> >>>>> +  The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video.
> >>>>> +
> >>>>> +properties:
> >>>>> +  compatible:
> >>>>> +    enum:
> >>>>> +      - ti,ds90ub913a-q1
> >>>>
> >>>> Is the -q1 suffix needed, are there other variants ?
> >>>>
> >>>>> +
> >>>>> +  '#gpio-cells':
> >>>>> +    const: 2
> >>>>> +
> >>>>> +  gpio-controller: true
> >>>>> +
> >>>>> +  clocks:
> >>>>> +    maxItems: 1
> >>>>> +    description:
> >>>>> +      Reference clock connected to the CLKIN pin.
> >>>>> +
> >>>>> +  clock-names:
> >>>>> +    items:
> >>>>> +      - const: clkin
> >>>>> +
> >>>>> +  '#clock-cells':
> >>>>> +    const: 0
> >>>>> +
> >>>>> +  ports:
> >>>>> +    $ref: /schemas/graph.yaml#/properties/ports
> >>>>> +
> >>>>> +    properties:
> >>>>> +      port@0:
> >>>>> +        $ref: /schemas/graph.yaml#/$defs/port-base
> >>>>> +        unevaluatedProperties: false
> >>>>> +        description: CSI-2 input port
> >>>
> >>> This should be "Parallel input port".
> >>
> >> Oops...
> >>
> >>>>> +
> >>>>> +        properties:
> >>>>> +          endpoint:
> >>>>> +            $ref: /schemas/media/video-interfaces.yaml#
> >>>>> +            unevaluatedProperties: false
> >>>
> >>> Should at least the bus-width property be mandatory, as the device
> >>> supports both 10- and 12-bit inputs ?
> >>
> >> Hmm... It supports 10-bit, 12-bit HF and 12-bit LF modes. If we need to
> >> configure the mode based on DT, we need one more property for the HF/LF.
> >> Then again, the HF/LF is separate from the input port, it's more about
> >> internal operation and the link to the deserializer.
> >>
> >> However, this (the mode) should always be set in the HW via the MODE
> >> pins. And the driver can read the HW's MODE from the registers. Only in
> >> some very odd circumstances should the mode be configured by hand (and
> >> then carefully, as the link to the deserializer will drop).
> > 
> > Both the DS90UB913A and DS90UB913Q datasheets state that the MODE pin on
> > the serializer only selects between PCLK and external oscillator modes.
> > 
> > The DS90UB913A datasheet seems to hint in documentation of the mode
> > select register (0x05) that the mode is selected on the deserializer and
> > transmitted to the serializer through the back-channel, as the
> > MODE_OVERRIDE bit is documented as "Allows overriding mode select bits
> > coming from back-channel" and the MODE_UP_TO_DATE bit as "Status of mode
> > select from Deserializer is up-to- date". Bits 2 and 3 are however named
> > "Pin_MODE_12-bit High Frequency" and "Pin_MODE_10-bit mode", which hint
> > that their value could come from a mode pin, but I see no trace of that
> > anywhere.
> > 
> > The DS90UB913Q datasheet is similar, with a notable difference in that
> > it documents bits 1 and 0 as reserved, where the DS90UB913A datasheet
> > documents them as mode override selection. In the same document, the
> > DS90UB914Q MODE pin is documented as selecting the 10-bit, 12-bit LF or
> > 12-bit HF operation mode. The datasheet also states that "The
> > deserializer automatically configures the serializer to correct mode
> > through the back-channel".
> > 
> > Th DS90UB953 datasheet also hints in the documentation of the
> > BC_MODE_SELECT register (0x04) that the mode is configured automatically
> > for backward-compatible DVP mode. For CSI-2 mode, I assume the mode is
> > strapped from the MODE pin and not configured through the back-channel.
> > 
> > The DS90UB960 datasheet documents how to configure the mode on the
> > deserializer side, but doesn't state whether the serializer is then
> > automatically configured through the back-channel (in RAW/DVP mode). I
> > assume it is, do you have any information about this ?
> 
> I have to admit I had missed the mode management of the RAW mode while 
> going through all this. I had mostly looked at the UB953's CSI mode.
> 
> I don't have more information, but your analysis looks correct to me. So 
> the whole mode thing is an interesting mix of serializer & deserializer 
> HW straps, deserializer sending the (RAW) mode to the serializer, and 
> then the override registers on the serializer side.
> 
> As to the original question, should we have mandatory bus-width for 
> ub913... I don't think it would be useful, even after the updated 
> understanding about modes. Do you agree?

Yes, as far as I understand, the mode is configured from the
deserializer side for parallel serializers. While it can be overridden
on the serializer side through I2C, this would be an exception rather
than a rule, so I don't think there's a need to make bus-width mandatory
in DT.

> >> So the bus-width is not something that the driver would normally use. If
> >> we would need to define the bus-width and HF/LF in the DT for some
> >> reason in the future, I think an "old" DT without those specified should
> >> continue working fine, as the mode can be read from a register.
> >>
> >> That said, to complicate matters, the deserializer needs to know the
> >> serializer's mode before it can communicate with it (and thus, before we
> >> can read the mode). This is set with the deserializer's "ti,rx-mode"
> >> property, where you find RAW10, RAW12LF and RAW12HF modes (and for
> >> ub953, CSI-2 sync and non-sync modes).
> >>
> >> So if we would define the bus-width and HF/LF in ub913's properties, the
> >> deserializer could go peeking the mode from there. But is that a good
> >> idea... I'm not so sure.
> > 
> > Peeking into another device's DT node isn't great. It looks like the
> > best option for the DS90UB913 is to specify the mode on the
> > deserializer's side (either through the MODE strap or with a software
> > override through DT). In case the serializer mode would need to be
> > manually overridden in the future, we could add an optional DT property.
> 
> I don't like the mode strap on UB960 side, as it's for all ports. It 
> works in certain cases, of course, but if we anyway need the mode in DT 
> to allow port-specific configuration, I think it's just easier to always 
> require the DT mode and always use that, overriding the strap mode.

I wonder if there could be systems where the strap could be configurable
through a DIP switch or a jumper, depending on what cameras are
connected. It could then be convenient to not have to modify the device
tree. And just as I write this, I realize we have to specify the cameras
in DT anyway, so I suppose we could as well specify the mode too.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2023-01-04  8:59         ` Tomi Valkeinen
@ 2023-01-04 12:57           ` Laurent Pinchart
  2023-01-04 14:05             ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2023-01-04 12:57 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Wed, Jan 04, 2023 at 10:59:00AM +0200, Tomi Valkeinen wrote:
> On 26/12/2022 18:52, Laurent Pinchart wrote:
> > On Tue, Dec 13, 2022 at 04:25:46PM +0200, Tomi Valkeinen wrote:
> >> On 11/12/2022 19:58, Laurent Pinchart wrote:
> >>> On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
> >>>> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
> >>>>
> >>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>>> ---
> >>>>    .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
> >>>>    1 file changed, 358 insertions(+)
> >>>>    create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>>>
> >>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>>> new file mode 100644
> >>>> index 000000000000..d8b5e219d420
> >>>> --- /dev/null
> >>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>>> @@ -0,0 +1,358 @@
> >>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >>>> +%YAML 1.2
> >>>> +---
> >>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
> >>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >>>> +
> >>>> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
> >>>> +
> >>>> +maintainers:
> >>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>>> +
> >>>> +description:
> >>>> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
> >>>> +  forwarding.
> >>>> +
> >>>> +properties:
> >>>> +  compatible:
> >>>> +    enum:
> >>>> +      - ti,ds90ub960-q1
> >>>> +      - ti,ds90ub9702-q1
> >>>> +
> >>>> +  reg:
> >>>> +    maxItems: 1
> >>>> +    description:
> >>>> +      i2c addresses for the deserializer and the serializers
> >>>
> >>> s/i2c/I2C/
> >>>
> >>> Same below.
> >>>
> >>> A bit more details would be nice, for instance the order in which
> >>> addresses should be specified should be documented. The example below
> >>> has one address only, so it's quite unclear. Or is this a left-over,
> >>> from before the i2c-alias-pool ?
> >>
> >> That's a left over, but not related to i2c-alias-pool but the i2c-alias
> >> for the serializers. It already says above 'maxItems: 1', so now it only
> >> contains the deserializer address. I'll drop the desc.
> > 
> > Looks good to me.
> > 
> >>>> +
> >>>> +  clocks:
> >>>> +    maxItems: 1
> >>>> +    description:
> >>>> +      Reference clock connected to the REFCLK pin.
> >>>> +
> >>>> +  clock-names:
> >>>> +    items:
> >>>> +      - const: refclk
> >>>> +
> >>>> +  powerdown-gpios:
> >>>> +    maxItems: 1
> >>>> +    description:
> >>>> +      Specifier for the GPIO connected to the PDB pin.
> >>>> +
> >>>> +  i2c-alias-pool:
> >>>> +    $ref: /schemas/types.yaml#/definitions/uint16-array
> >>>> +    description:
> >>>> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
> >>>> +      used to access the remote peripherals. The addresses must be available,
> >>>> +      not used by any other peripheral. Each remote peripheral is assigned an
> >>>> +      alias from the pool, and transactions to that address will be forwarded
> >>>> +      to the remote peripheral, with the address translated to the remote
> >>>> +      peripheral's real address.
> >>>
> >>> As this property is optional, should you describe what happens when it's
> >>> not specified ?
> >>>
> >>> I would also indicate that the pool doesn't cover the serializers, only
> >>> the devices behind them.
> >>
> >> Yep, I'll clarify these.
> >>
> >>>> +
> >>>> +  links:
> >>>> +    type: object
> >>>> +    additionalProperties: false
> >>>> +
> >>>> +    properties:
> >>>> +      '#address-cells':
> >>>> +        const: 1
> >>>> +
> >>>> +      '#size-cells':
> >>>> +        const: 0
> >>>> +
> >>>> +      ti,manual-strobe:
> >>>> +        type: boolean
> >>>> +        description:
> >>>> +          Enable manual strobe position and EQ level
> >>>> +
> >>>> +    patternProperties:
> >>>> +      '^link@[0-9a-f]+$':
> >>>
> >>> There can be up to 4 links only, right ? I would then use
> >>>
> >>>         '^link@[0-3]$':
> >>
> >> Yes, I'll change that.
> >>
> >>>> +        type: object
> >>>> +        additionalProperties: false
> >>>> +        properties:
> >>>> +          reg:
> >>>> +            description: The link number
> >>>> +            maxItems: 1
> >>>> +
> >>>> +          i2c-alias:
> >>>> +            description:
> >>>> +              The i2c address used for the serializer. Transactions to this
> >>>> +              address on the i2c bus where the deserializer resides are
> >>>> +              forwarded to the serializer.
> >>>> +
> >>>> +          ti,rx-mode:
> >>>> +            $ref: /schemas/types.yaml#/definitions/uint32
> >>>> +            enum:
> >>>> +              - 0 # RAW10
> >>>> +              - 1 # RAW12 HF
> >>>> +              - 2 # RAW12 LF
> >>>> +              - 3 # CSI2 SYNC
> >>>> +              - 4 # CSI2 NON-SYNC
> >>>> +            description: FPD-Link Input Mode
> >>>
> >>> Are there use cases for controlling this dynamically (in particular the
> >>> sync/non-sync modes) ? Is there anything that could be queried at
> >>> runtime from the serializers instead of being specified in DT ?
> >>
> >> We need a link to the serializer before we can query anything from the
> >> serializer.
> > 
> > I meant querying it from the serializer driver, not the serializer
> > hardware. This being said, it would likely be difficult to do so, as the
> > serializer driver would need to probe first. I think I'm thus fine
> > selecting the mode in DT on the deserializer side.
> > 
> >> To have a link, we need the mode... So, as I mentioned in
> >> the other reply, we could define these in some way in the serializer's
> >> properties instead of here, but I'm not sure if that's a good change.
> >>
> >> The driver can change the mode at runtime (say, from sync to non-sync
> >> mode, if the HW supports that). But I think this property should reflect
> >> the HW strapped configuration of the serializer.
> > 
> > That would possibly work for the DS90UB953, but the DS90UB913 has no
> > strapped mode selected at boot time but is instead configured
> > automatically through the back-channel (see my last reply to patch 3/8).
> 
> Indeed.
> 
> > When connecting a DS90UB913 to a DS90UB914 deserializer, we can probably
> > start without mode selection in software, as the MODE pin is meant to
> > bootstrap that to a correct value which is then automatically
> > transmitted to the serializer (hardware designs where the mode would
> > need to be overridden should be rate). However, when connecting multiple
> 
> I don't know if that's true. I guess it depends on how you see the deser 
> and the camera module. Are they part of the same HW design or not? In my 
> setups they are quite separate, and I connect different kinds of camera 
> modules to my deserializers. But I can see that if you create a, say, 
> car, you'd have both sides known at design time and would never change.
> 
> > DS90UB913 to a DS90UB960, I can imagine connecting different types of
> > cameras on the four input ports, so the need to specify the mode
> > per-port in DT would be more common.
> 
> Right, and even with UB914, you might well design the deserializer side 
> with, say, RAW10 sensors, but later in the cycle you'd need to change to 
> a RAW12 sensor. Depending on the deser mode strap would require you to 
> do a HW change on the deser side too.
> 
> As I said in the other mail, I don't like the deser's strap, and I think 
> we should just basically ignore it as we can provide the necessary data 
> in the DT.

What I meant is that, given that the UB914 is meant to be used with a
single camera, using a RAW mode, there's a much higher chance that
hardware strapping will work as intended there. We could thus start
without support for overrides in a UB914 driver (but as far as I
understand we're not planning to work on such a driver in the near
future, so it's hypothetical only), while in the UB960 driver we
probably need override support from the beginning.

> > For these reasons, I don't think the ti,rx-mode property can be defined
> > as reflecting the hardware MODE strap with the DS90UB913. I also think
> > it would be quite confusing to define it as the desired runtime
> > configuration for the DS90UB913 and as the hardware MODE strap for the
> > DS90UB953. Could it be (explicitly) defined as the desired runtime
> > configuration in all cases ?
> 
> That sounds bad in a DT context =). You're right that the rx-mode can't 
> be defined as reflecting the serializer mode strap, but I think we can 
> define it as reflecting the default operation mode of the serializer 
> hardware (or maybe rather the camera module).

What do you mean by "default operation mode" in this case ?

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-27 20:09         ` Andy Shevchenko
@ 2023-01-04 13:29           ` Laurent Pinchart
  0 siblings, 0 replies; 57+ messages in thread
From: Laurent Pinchart @ 2023-01-04 13:29 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Tomi Valkeinen, linux-media, devicetree, linux-kernel, linux-i2c,
	Rob Herring, Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Matti Vaittinen, Mauro Carvalho Chehab, Peter Rosin,
	Liam Girdwood, Mark Brown, Sakari Ailus, Michael Tretter,
	Shawn Tu, Hans Verkuil, Mike Pagano, Krzysztof Hałasa,
	Marek Vasut

On Tue, Dec 27, 2022 at 10:09:43PM +0200, Andy Shevchenko wrote:
> On Mon, Dec 26, 2022 at 07:01:11PM +0200, Laurent Pinchart wrote:
> > On Wed, Dec 14, 2022 at 08:29:48AM +0200, Tomi Valkeinen wrote:
> > > On 11/12/2022 20:33, Laurent Pinchart wrote:
> > > > On Thu, Dec 08, 2022 at 12:40:05PM +0200, Tomi Valkeinen wrote:
> 
> ...
> 
> > > >> +	dev_dbg(dev, "Bind %s\n", source_subdev->name);
> > > > 
> > > > I'd drop this message.
> 
> +1 here.
> 
> > > Why is that? Do we get this easily from the v4l2 core? These debug 
> > > prints in the bind/unbind process have been valuable for me.
> > 
> > Because debug messages are not meant to be a tracing infrastructure, and
> > because, if we want to keep this message, it would be best handled in
> > the v4l2-async core instead of being duplicated across drivers. Same for
> > the messages at the end of the function.
> 
> I don't think v4l2 needs debug prints. If we consider the above case, the
> ftrace already provides that. If we consider something specific to v4l2 to
> trace only critical parts, then trace events should be implemented.

V4L2 has no support for trace events, and I agree that they would be
useful. It doesn't mean that dev_dbg() is entirely useless though, and
sometimes the line between the two isn't very clear. In any case, for
this very specific debug message, I don't think it should be printed in
individual drivers, but it should be handled in the V4L2 core, either as
a debug message or a trace event.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2022-12-26 19:25           ` Tomi Valkeinen
@ 2023-01-04 13:55             ` Laurent Pinchart
  2023-01-04 14:13               ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2023-01-04 13:55 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Mon, Dec 26, 2022 at 09:25:34PM +0200, Tomi Valkeinen wrote:
> On 26/12/2022 18:56, Laurent Pinchart wrote:
> > On Wed, Dec 14, 2022 at 08:36:47AM +0200, Tomi Valkeinen wrote:
> >> On 14/12/2022 08:29, Tomi Valkeinen wrote:
> >>
> >>>> wondering if the struct device of the DS90UB913 could be passed instead
> >>>> of the port, to avoid passing the port throught
> >>>> ds90ub9xx_platform_data.
> >>>
> >>> Interesting thought. That would limit the number of remote i2c busses to
> >>> one, though. Not a problem for FPD-Link, but I wonder if that's assuming
> >>> too much for the future users. Then again, this is an in-kernel API so
> >>> we could extend it later if needed. So I'll try this out and see if I
> >>> hit any issues.
> >>
> >> Right, so the issue with this one would be that it would prevent a
> >> single device uses. E.g. a single chip which acts as an ATR (similar to
> >> i2c-mux chips), i.e. it contains both the main and the remote i2c busses.
> > 
> > I don't think I understand this, sorry.
> 
> What you are suggesting above means that we'd have a separate device for 
> each port of the ATR. Which is fine in our current case, as the i2c 
> master busses are behind separate remote devices.
> 
> But if you consider a case similar to i2c-mux, where we have a single 
> chip with the slave bus and, say, 4 master busses. We would probably 
> have only a single device for that.

Hmmm... Yes you're right, it won't work in that case. Maybe we could
have two functions, the existing i2c_atr_add_adapter(), and another one
that wraps it ? It would be nice if we could get rid of the platform
data for the UB913 and UB953 drivers.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2023-01-04 12:57           ` Laurent Pinchart
@ 2023-01-04 14:05             ` Tomi Valkeinen
  2023-01-05  6:54               ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2023-01-04 14:05 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 04/01/2023 14:57, Laurent Pinchart wrote:
> Hi Tomi,
> 
> On Wed, Jan 04, 2023 at 10:59:00AM +0200, Tomi Valkeinen wrote:
>> On 26/12/2022 18:52, Laurent Pinchart wrote:
>>> On Tue, Dec 13, 2022 at 04:25:46PM +0200, Tomi Valkeinen wrote:
>>>> On 11/12/2022 19:58, Laurent Pinchart wrote:
>>>>> On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
>>>>>> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>>>>>>
>>>>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>>>> ---
>>>>>>     .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
>>>>>>     1 file changed, 358 insertions(+)
>>>>>>     create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>>>>>
>>>>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>>>>> new file mode 100644
>>>>>> index 000000000000..d8b5e219d420
>>>>>> --- /dev/null
>>>>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>>>>>> @@ -0,0 +1,358 @@
>>>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
>>>>>> +%YAML 1.2
>>>>>> +---
>>>>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
>>>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
>>>>>> +
>>>>>> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
>>>>>> +
>>>>>> +maintainers:
>>>>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>>>>>> +
>>>>>> +description:
>>>>>> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
>>>>>> +  forwarding.
>>>>>> +
>>>>>> +properties:
>>>>>> +  compatible:
>>>>>> +    enum:
>>>>>> +      - ti,ds90ub960-q1
>>>>>> +      - ti,ds90ub9702-q1
>>>>>> +
>>>>>> +  reg:
>>>>>> +    maxItems: 1
>>>>>> +    description:
>>>>>> +      i2c addresses for the deserializer and the serializers
>>>>>
>>>>> s/i2c/I2C/
>>>>>
>>>>> Same below.
>>>>>
>>>>> A bit more details would be nice, for instance the order in which
>>>>> addresses should be specified should be documented. The example below
>>>>> has one address only, so it's quite unclear. Or is this a left-over,
>>>>> from before the i2c-alias-pool ?
>>>>
>>>> That's a left over, but not related to i2c-alias-pool but the i2c-alias
>>>> for the serializers. It already says above 'maxItems: 1', so now it only
>>>> contains the deserializer address. I'll drop the desc.
>>>
>>> Looks good to me.
>>>
>>>>>> +
>>>>>> +  clocks:
>>>>>> +    maxItems: 1
>>>>>> +    description:
>>>>>> +      Reference clock connected to the REFCLK pin.
>>>>>> +
>>>>>> +  clock-names:
>>>>>> +    items:
>>>>>> +      - const: refclk
>>>>>> +
>>>>>> +  powerdown-gpios:
>>>>>> +    maxItems: 1
>>>>>> +    description:
>>>>>> +      Specifier for the GPIO connected to the PDB pin.
>>>>>> +
>>>>>> +  i2c-alias-pool:
>>>>>> +    $ref: /schemas/types.yaml#/definitions/uint16-array
>>>>>> +    description:
>>>>>> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
>>>>>> +      used to access the remote peripherals. The addresses must be available,
>>>>>> +      not used by any other peripheral. Each remote peripheral is assigned an
>>>>>> +      alias from the pool, and transactions to that address will be forwarded
>>>>>> +      to the remote peripheral, with the address translated to the remote
>>>>>> +      peripheral's real address.
>>>>>
>>>>> As this property is optional, should you describe what happens when it's
>>>>> not specified ?
>>>>>
>>>>> I would also indicate that the pool doesn't cover the serializers, only
>>>>> the devices behind them.
>>>>
>>>> Yep, I'll clarify these.
>>>>
>>>>>> +
>>>>>> +  links:
>>>>>> +    type: object
>>>>>> +    additionalProperties: false
>>>>>> +
>>>>>> +    properties:
>>>>>> +      '#address-cells':
>>>>>> +        const: 1
>>>>>> +
>>>>>> +      '#size-cells':
>>>>>> +        const: 0
>>>>>> +
>>>>>> +      ti,manual-strobe:
>>>>>> +        type: boolean
>>>>>> +        description:
>>>>>> +          Enable manual strobe position and EQ level
>>>>>> +
>>>>>> +    patternProperties:
>>>>>> +      '^link@[0-9a-f]+$':
>>>>>
>>>>> There can be up to 4 links only, right ? I would then use
>>>>>
>>>>>          '^link@[0-3]$':
>>>>
>>>> Yes, I'll change that.
>>>>
>>>>>> +        type: object
>>>>>> +        additionalProperties: false
>>>>>> +        properties:
>>>>>> +          reg:
>>>>>> +            description: The link number
>>>>>> +            maxItems: 1
>>>>>> +
>>>>>> +          i2c-alias:
>>>>>> +            description:
>>>>>> +              The i2c address used for the serializer. Transactions to this
>>>>>> +              address on the i2c bus where the deserializer resides are
>>>>>> +              forwarded to the serializer.
>>>>>> +
>>>>>> +          ti,rx-mode:
>>>>>> +            $ref: /schemas/types.yaml#/definitions/uint32
>>>>>> +            enum:
>>>>>> +              - 0 # RAW10
>>>>>> +              - 1 # RAW12 HF
>>>>>> +              - 2 # RAW12 LF
>>>>>> +              - 3 # CSI2 SYNC
>>>>>> +              - 4 # CSI2 NON-SYNC
>>>>>> +            description: FPD-Link Input Mode
>>>>>
>>>>> Are there use cases for controlling this dynamically (in particular the
>>>>> sync/non-sync modes) ? Is there anything that could be queried at
>>>>> runtime from the serializers instead of being specified in DT ?
>>>>
>>>> We need a link to the serializer before we can query anything from the
>>>> serializer.
>>>
>>> I meant querying it from the serializer driver, not the serializer
>>> hardware. This being said, it would likely be difficult to do so, as the
>>> serializer driver would need to probe first. I think I'm thus fine
>>> selecting the mode in DT on the deserializer side.
>>>
>>>> To have a link, we need the mode... So, as I mentioned in
>>>> the other reply, we could define these in some way in the serializer's
>>>> properties instead of here, but I'm not sure if that's a good change.
>>>>
>>>> The driver can change the mode at runtime (say, from sync to non-sync
>>>> mode, if the HW supports that). But I think this property should reflect
>>>> the HW strapped configuration of the serializer.
>>>
>>> That would possibly work for the DS90UB953, but the DS90UB913 has no
>>> strapped mode selected at boot time but is instead configured
>>> automatically through the back-channel (see my last reply to patch 3/8).
>>
>> Indeed.
>>
>>> When connecting a DS90UB913 to a DS90UB914 deserializer, we can probably
>>> start without mode selection in software, as the MODE pin is meant to
>>> bootstrap that to a correct value which is then automatically
>>> transmitted to the serializer (hardware designs where the mode would
>>> need to be overridden should be rate). However, when connecting multiple
>>
>> I don't know if that's true. I guess it depends on how you see the deser
>> and the camera module. Are they part of the same HW design or not? In my
>> setups they are quite separate, and I connect different kinds of camera
>> modules to my deserializers. But I can see that if you create a, say,
>> car, you'd have both sides known at design time and would never change.
>>
>>> DS90UB913 to a DS90UB960, I can imagine connecting different types of
>>> cameras on the four input ports, so the need to specify the mode
>>> per-port in DT would be more common.
>>
>> Right, and even with UB914, you might well design the deserializer side
>> with, say, RAW10 sensors, but later in the cycle you'd need to change to
>> a RAW12 sensor. Depending on the deser mode strap would require you to
>> do a HW change on the deser side too.
>>
>> As I said in the other mail, I don't like the deser's strap, and I think
>> we should just basically ignore it as we can provide the necessary data
>> in the DT.
> 
> What I meant is that, given that the UB914 is meant to be used with a
> single camera, using a RAW mode, there's a much higher chance that
> hardware strapping will work as intended there. We could thus start
> without support for overrides in a UB914 driver (but as far as I
> understand we're not planning to work on such a driver in the near
> future, so it's hypothetical only), while in the UB960 driver we
> probably need override support from the beginning.

Yes, but depending on the UB914 strap mode would still be only a partial 
solution, and it would still need to also support overriding the strap 
mode (from DT). As you said in the other mail, we anyway have to define 
the serializer and the sensor in the DT, so even with UB914 we couldn't 
just switch the camera module and change the mode via a DIP switch.

So I still don't see why we would even bother supporting the deser strap 
mode, even on UB914.

>>> For these reasons, I don't think the ti,rx-mode property can be defined
>>> as reflecting the hardware MODE strap with the DS90UB913. I also think
>>> it would be quite confusing to define it as the desired runtime
>>> configuration for the DS90UB913 and as the hardware MODE strap for the
>>> DS90UB953. Could it be (explicitly) defined as the desired runtime
>>> configuration in all cases ?
>>
>> That sounds bad in a DT context =). You're right that the rx-mode can't
>> be defined as reflecting the serializer mode strap, but I think we can
>> define it as reflecting the default operation mode of the serializer
>> hardware (or maybe rather the camera module).
> 
> What do you mean by "default operation mode" in this case ?

How the camera module is HW strapped and supposed to be used, how it 
works (more or less) out of the box:

- The UB953 is HW strapped to sync or non-sync CSI mode, or RAW mode.
- Both UB913 and UB953 (in RAW mode) have a sensor connected either with 
10 or 12 lines.
- The sensor has a default (either HW or use-case default) pixel clock.

Based on those, I think we get all the possible modes. So, I think, 
every camera module will have a known "normal" / "default" mode, which 
is what the mode in the DT tells.

Well, for FPD4, there's also the CDR mode. UB971 can work in both FDP3 
and FPD4 modes. But there again it should be clear what's the "normal" 
operation mode, as it comes from a serializer HW strap.

We can do changes to those at runtime (not supported by the drivers), 
e.g. changing from sync to non-sync CSI mode, changing the RAW mode, and 
even changing from FDP4 to FDP3. But those, I believe, are rare cases, 
and the changes have to be done carefully, on both the deser and ser 
sides in certain order.

So, back to the original point, "desired runtime configuration" and 
"default operation mode" are maybe the same thing =).

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2023-01-04 13:55             ` Laurent Pinchart
@ 2023-01-04 14:13               ` Tomi Valkeinen
  2023-01-04 15:32                 ` Laurent Pinchart
  0 siblings, 1 reply; 57+ messages in thread
From: Tomi Valkeinen @ 2023-01-04 14:13 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 04/01/2023 15:55, Laurent Pinchart wrote:
> Hi Tomi,
> 
> On Mon, Dec 26, 2022 at 09:25:34PM +0200, Tomi Valkeinen wrote:
>> On 26/12/2022 18:56, Laurent Pinchart wrote:
>>> On Wed, Dec 14, 2022 at 08:36:47AM +0200, Tomi Valkeinen wrote:
>>>> On 14/12/2022 08:29, Tomi Valkeinen wrote:
>>>>
>>>>>> wondering if the struct device of the DS90UB913 could be passed instead
>>>>>> of the port, to avoid passing the port throught
>>>>>> ds90ub9xx_platform_data.
>>>>>
>>>>> Interesting thought. That would limit the number of remote i2c busses to
>>>>> one, though. Not a problem for FPD-Link, but I wonder if that's assuming
>>>>> too much for the future users. Then again, this is an in-kernel API so
>>>>> we could extend it later if needed. So I'll try this out and see if I
>>>>> hit any issues.
>>>>
>>>> Right, so the issue with this one would be that it would prevent a
>>>> single device uses. E.g. a single chip which acts as an ATR (similar to
>>>> i2c-mux chips), i.e. it contains both the main and the remote i2c busses.
>>>
>>> I don't think I understand this, sorry.
>>
>> What you are suggesting above means that we'd have a separate device for
>> each port of the ATR. Which is fine in our current case, as the i2c
>> master busses are behind separate remote devices.
>>
>> But if you consider a case similar to i2c-mux, where we have a single
>> chip with the slave bus and, say, 4 master busses. We would probably
>> have only a single device for that.
> 
> Hmmm... Yes you're right, it won't work in that case. Maybe we could
> have two functions, the existing i2c_atr_add_adapter(), and another one
> that wraps it ? It would be nice if we could get rid of the platform
> data for the UB913 and UB953 drivers.

I wouldn't mind that at all, but we already have the bc_rate there. And 
I have a feeling that we might need more if we implement more features.

And we also have the atr pointer there. Or do you think that could be 
dropped also? In your mail above you only mention the port, but maybe 
the deser could register the serializer device and port to the ATR, and 
then the ser could just use its device pointer instead of atr & port.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2023-01-04 14:13               ` Tomi Valkeinen
@ 2023-01-04 15:32                 ` Laurent Pinchart
  2023-01-04 15:43                   ` Tomi Valkeinen
  0 siblings, 1 reply; 57+ messages in thread
From: Laurent Pinchart @ 2023-01-04 15:32 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On Wed, Jan 04, 2023 at 04:13:17PM +0200, Tomi Valkeinen wrote:
> On 04/01/2023 15:55, Laurent Pinchart wrote:
> > Hi Tomi,
> > 
> > On Mon, Dec 26, 2022 at 09:25:34PM +0200, Tomi Valkeinen wrote:
> >> On 26/12/2022 18:56, Laurent Pinchart wrote:
> >>> On Wed, Dec 14, 2022 at 08:36:47AM +0200, Tomi Valkeinen wrote:
> >>>> On 14/12/2022 08:29, Tomi Valkeinen wrote:
> >>>>
> >>>>>> wondering if the struct device of the DS90UB913 could be passed instead
> >>>>>> of the port, to avoid passing the port throught
> >>>>>> ds90ub9xx_platform_data.
> >>>>>
> >>>>> Interesting thought. That would limit the number of remote i2c busses to
> >>>>> one, though. Not a problem for FPD-Link, but I wonder if that's assuming
> >>>>> too much for the future users. Then again, this is an in-kernel API so
> >>>>> we could extend it later if needed. So I'll try this out and see if I
> >>>>> hit any issues.
> >>>>
> >>>> Right, so the issue with this one would be that it would prevent a
> >>>> single device uses. E.g. a single chip which acts as an ATR (similar to
> >>>> i2c-mux chips), i.e. it contains both the main and the remote i2c busses.
> >>>
> >>> I don't think I understand this, sorry.
> >>
> >> What you are suggesting above means that we'd have a separate device for
> >> each port of the ATR. Which is fine in our current case, as the i2c
> >> master busses are behind separate remote devices.
> >>
> >> But if you consider a case similar to i2c-mux, where we have a single
> >> chip with the slave bus and, say, 4 master busses. We would probably
> >> have only a single device for that.
> > 
> > Hmmm... Yes you're right, it won't work in that case. Maybe we could
> > have two functions, the existing i2c_atr_add_adapter(), and another one
> > that wraps it ? It would be nice if we could get rid of the platform
> > data for the UB913 and UB953 drivers.
> 
> I wouldn't mind that at all, but we already have the bc_rate there. And 
> I have a feeling that we might need more if we implement more features.

Indeed. I feel that platform data is a bit of a hack here, but maybe
it's not that bad.

> And we also have the atr pointer there. Or do you think that could be 
> dropped also? In your mail above you only mention the port, but maybe 
> the deser could register the serializer device and port to the ATR, and 
> then the ser could just use its device pointer instead of atr & port.

I was wondering if we could drop the atr pointer too, yes. I'm not sure
how, and there's no urgency to fix this. My main concern is that new
drivers should ideally not be forced to use platform data just for ATR
support, if they don't use it already for something else.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 7/8] media: i2c: add DS90UB913 driver
  2023-01-04 15:32                 ` Laurent Pinchart
@ 2023-01-04 15:43                   ` Tomi Valkeinen
  0 siblings, 0 replies; 57+ messages in thread
From: Tomi Valkeinen @ 2023-01-04 15:43 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

On 04/01/2023 17:32, Laurent Pinchart wrote:
> On Wed, Jan 04, 2023 at 04:13:17PM +0200, Tomi Valkeinen wrote:
>> On 04/01/2023 15:55, Laurent Pinchart wrote:
>>> Hi Tomi,
>>>
>>> On Mon, Dec 26, 2022 at 09:25:34PM +0200, Tomi Valkeinen wrote:
>>>> On 26/12/2022 18:56, Laurent Pinchart wrote:
>>>>> On Wed, Dec 14, 2022 at 08:36:47AM +0200, Tomi Valkeinen wrote:
>>>>>> On 14/12/2022 08:29, Tomi Valkeinen wrote:
>>>>>>
>>>>>>>> wondering if the struct device of the DS90UB913 could be passed instead
>>>>>>>> of the port, to avoid passing the port throught
>>>>>>>> ds90ub9xx_platform_data.
>>>>>>>
>>>>>>> Interesting thought. That would limit the number of remote i2c busses to
>>>>>>> one, though. Not a problem for FPD-Link, but I wonder if that's assuming
>>>>>>> too much for the future users. Then again, this is an in-kernel API so
>>>>>>> we could extend it later if needed. So I'll try this out and see if I
>>>>>>> hit any issues.
>>>>>>
>>>>>> Right, so the issue with this one would be that it would prevent a
>>>>>> single device uses. E.g. a single chip which acts as an ATR (similar to
>>>>>> i2c-mux chips), i.e. it contains both the main and the remote i2c busses.
>>>>>
>>>>> I don't think I understand this, sorry.
>>>>
>>>> What you are suggesting above means that we'd have a separate device for
>>>> each port of the ATR. Which is fine in our current case, as the i2c
>>>> master busses are behind separate remote devices.
>>>>
>>>> But if you consider a case similar to i2c-mux, where we have a single
>>>> chip with the slave bus and, say, 4 master busses. We would probably
>>>> have only a single device for that.
>>>
>>> Hmmm... Yes you're right, it won't work in that case. Maybe we could
>>> have two functions, the existing i2c_atr_add_adapter(), and another one
>>> that wraps it ? It would be nice if we could get rid of the platform
>>> data for the UB913 and UB953 drivers.
>>
>> I wouldn't mind that at all, but we already have the bc_rate there. And
>> I have a feeling that we might need more if we implement more features.
> 
> Indeed. I feel that platform data is a bit of a hack here, but maybe
> it's not that bad.
> 
>> And we also have the atr pointer there. Or do you think that could be
>> dropped also? In your mail above you only mention the port, but maybe
>> the deser could register the serializer device and port to the ATR, and
>> then the ser could just use its device pointer instead of atr & port.
> 
> I was wondering if we could drop the atr pointer too, yes. I'm not sure
> how, and there's no urgency to fix this. My main concern is that new
> drivers should ideally not be forced to use platform data just for ATR
> support, if they don't use it already for something else.

Good point. However, we don't know who will use ATR or how ATR is going 
to be used. Using it the same way i2c-mux is used, there's no problem 
and platform data is not needed. Using it in this split manner we do 
with FPDLink does bring up the problem. And using i2c-mux in the split 
manner would also bring up the same problem. So maybe there's some neat 
solution out there that would solve the issue for both i2c-atr and 
i2c-mux, but it escapes me at the moment.

  Tomi


^ permalink raw reply	[flat|nested] 57+ messages in thread

* Re: [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960
  2023-01-04 14:05             ` Tomi Valkeinen
@ 2023-01-05  6:54               ` Laurent Pinchart
  0 siblings, 0 replies; 57+ messages in thread
From: Laurent Pinchart @ 2023-01-05  6:54 UTC (permalink / raw)
  To: Tomi Valkeinen
  Cc: linux-media, devicetree, linux-kernel, linux-i2c, Rob Herring,
	Krzysztof Kozlowski, Wolfram Sang, Luca Ceresoli,
	Andy Shevchenko, Matti Vaittinen, Mauro Carvalho Chehab,
	Peter Rosin, Liam Girdwood, Mark Brown, Sakari Ailus,
	Michael Tretter, Shawn Tu, Hans Verkuil, Mike Pagano,
	Krzysztof Hałasa, Marek Vasut

Hi Tomi,

On Wed, Jan 04, 2023 at 04:05:08PM +0200, Tomi Valkeinen wrote:
> On 04/01/2023 14:57, Laurent Pinchart wrote:
> > On Wed, Jan 04, 2023 at 10:59:00AM +0200, Tomi Valkeinen wrote:
> >> On 26/12/2022 18:52, Laurent Pinchart wrote:
> >>> On Tue, Dec 13, 2022 at 04:25:46PM +0200, Tomi Valkeinen wrote:
> >>>> On 11/12/2022 19:58, Laurent Pinchart wrote:
> >>>>> On Thu, Dec 08, 2022 at 12:40:03PM +0200, Tomi Valkeinen wrote:
> >>>>>> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
> >>>>>>
> >>>>>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>>>>> ---
> >>>>>>     .../bindings/media/i2c/ti,ds90ub960.yaml      | 358 ++++++++++++++++++
> >>>>>>     1 file changed, 358 insertions(+)
> >>>>>>     create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>>>>>
> >>>>>> diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..d8b5e219d420
> >>>>>> --- /dev/null
> >>>>>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >>>>>> @@ -0,0 +1,358 @@
> >>>>>> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> >>>>>> +%YAML 1.2
> >>>>>> +---
> >>>>>> +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml#
> >>>>>> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> >>>>>> +
> >>>>>> +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs
> >>>>>> +
> >>>>>> +maintainers:
> >>>>>> +  - Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >>>>>> +
> >>>>>> +description:
> >>>>>> +  The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and GPIO
> >>>>>> +  forwarding.
> >>>>>> +
> >>>>>> +properties:
> >>>>>> +  compatible:
> >>>>>> +    enum:
> >>>>>> +      - ti,ds90ub960-q1
> >>>>>> +      - ti,ds90ub9702-q1
> >>>>>> +
> >>>>>> +  reg:
> >>>>>> +    maxItems: 1
> >>>>>> +    description:
> >>>>>> +      i2c addresses for the deserializer and the serializers
> >>>>>
> >>>>> s/i2c/I2C/
> >>>>>
> >>>>> Same below.
> >>>>>
> >>>>> A bit more details would be nice, for instance the order in which
> >>>>> addresses should be specified should be documented. The example below
> >>>>> has one address only, so it's quite unclear. Or is this a left-over,
> >>>>> from before the i2c-alias-pool ?
> >>>>
> >>>> That's a left over, but not related to i2c-alias-pool but the i2c-alias
> >>>> for the serializers. It already says above 'maxItems: 1', so now it only
> >>>> contains the deserializer address. I'll drop the desc.
> >>>
> >>> Looks good to me.
> >>>
> >>>>>> +
> >>>>>> +  clocks:
> >>>>>> +    maxItems: 1
> >>>>>> +    description:
> >>>>>> +      Reference clock connected to the REFCLK pin.
> >>>>>> +
> >>>>>> +  clock-names:
> >>>>>> +    items:
> >>>>>> +      - const: refclk
> >>>>>> +
> >>>>>> +  powerdown-gpios:
> >>>>>> +    maxItems: 1
> >>>>>> +    description:
> >>>>>> +      Specifier for the GPIO connected to the PDB pin.
> >>>>>> +
> >>>>>> +  i2c-alias-pool:
> >>>>>> +    $ref: /schemas/types.yaml#/definitions/uint16-array
> >>>>>> +    description:
> >>>>>> +      i2c alias pool is a pool of i2c addresses on the main i2c bus that can be
> >>>>>> +      used to access the remote peripherals. The addresses must be available,
> >>>>>> +      not used by any other peripheral. Each remote peripheral is assigned an
> >>>>>> +      alias from the pool, and transactions to that address will be forwarded
> >>>>>> +      to the remote peripheral, with the address translated to the remote
> >>>>>> +      peripheral's real address.
> >>>>>
> >>>>> As this property is optional, should you describe what happens when it's
> >>>>> not specified ?
> >>>>>
> >>>>> I would also indicate that the pool doesn't cover the serializers, only
> >>>>> the devices behind them.
> >>>>
> >>>> Yep, I'll clarify these.
> >>>>
> >>>>>> +
> >>>>>> +  links:
> >>>>>> +    type: object
> >>>>>> +    additionalProperties: false
> >>>>>> +
> >>>>>> +    properties:
> >>>>>> +      '#address-cells':
> >>>>>> +        const: 1
> >>>>>> +
> >>>>>> +      '#size-cells':
> >>>>>> +        const: 0
> >>>>>> +
> >>>>>> +      ti,manual-strobe:
> >>>>>> +        type: boolean
> >>>>>> +        description:
> >>>>>> +          Enable manual strobe position and EQ level
> >>>>>> +
> >>>>>> +    patternProperties:
> >>>>>> +      '^link@[0-9a-f]+$':
> >>>>>
> >>>>> There can be up to 4 links only, right ? I would then use
> >>>>>
> >>>>>          '^link@[0-3]$':
> >>>>
> >>>> Yes, I'll change that.
> >>>>
> >>>>>> +        type: object
> >>>>>> +        additionalProperties: false
> >>>>>> +        properties:
> >>>>>> +          reg:
> >>>>>> +            description: The link number
> >>>>>> +            maxItems: 1
> >>>>>> +
> >>>>>> +          i2c-alias:
> >>>>>> +            description:
> >>>>>> +              The i2c address used for the serializer. Transactions to this
> >>>>>> +              address on the i2c bus where the deserializer resides are
> >>>>>> +              forwarded to the serializer.
> >>>>>> +
> >>>>>> +          ti,rx-mode:
> >>>>>> +            $ref: /schemas/types.yaml#/definitions/uint32
> >>>>>> +            enum:
> >>>>>> +              - 0 # RAW10
> >>>>>> +              - 1 # RAW12 HF
> >>>>>> +              - 2 # RAW12 LF
> >>>>>> +              - 3 # CSI2 SYNC
> >>>>>> +              - 4 # CSI2 NON-SYNC
> >>>>>> +            description: FPD-Link Input Mode
> >>>>>
> >>>>> Are there use cases for controlling this dynamically (in particular the
> >>>>> sync/non-sync modes) ? Is there anything that could be queried at
> >>>>> runtime from the serializers instead of being specified in DT ?
> >>>>
> >>>> We need a link to the serializer before we can query anything from the
> >>>> serializer.
> >>>
> >>> I meant querying it from the serializer driver, not the serializer
> >>> hardware. This being said, it would likely be difficult to do so, as the
> >>> serializer driver would need to probe first. I think I'm thus fine
> >>> selecting the mode in DT on the deserializer side.
> >>>
> >>>> To have a link, we need the mode... So, as I mentioned in
> >>>> the other reply, we could define these in some way in the serializer's
> >>>> properties instead of here, but I'm not sure if that's a good change.
> >>>>
> >>>> The driver can change the mode at runtime (say, from sync to non-sync
> >>>> mode, if the HW supports that). But I think this property should reflect
> >>>> the HW strapped configuration of the serializer.
> >>>
> >>> That would possibly work for the DS90UB953, but the DS90UB913 has no
> >>> strapped mode selected at boot time but is instead configured
> >>> automatically through the back-channel (see my last reply to patch 3/8).
> >>
> >> Indeed.
> >>
> >>> When connecting a DS90UB913 to a DS90UB914 deserializer, we can probably
> >>> start without mode selection in software, as the MODE pin is meant to
> >>> bootstrap that to a correct value which is then automatically
> >>> transmitted to the serializer (hardware designs where the mode would
> >>> need to be overridden should be rate). However, when connecting multiple
> >>
> >> I don't know if that's true. I guess it depends on how you see the deser
> >> and the camera module. Are they part of the same HW design or not? In my
> >> setups they are quite separate, and I connect different kinds of camera
> >> modules to my deserializers. But I can see that if you create a, say,
> >> car, you'd have both sides known at design time and would never change.
> >>
> >>> DS90UB913 to a DS90UB960, I can imagine connecting different types of
> >>> cameras on the four input ports, so the need to specify the mode
> >>> per-port in DT would be more common.
> >>
> >> Right, and even with UB914, you might well design the deserializer side
> >> with, say, RAW10 sensors, but later in the cycle you'd need to change to
> >> a RAW12 sensor. Depending on the deser mode strap would require you to
> >> do a HW change on the deser side too.
> >>
> >> As I said in the other mail, I don't like the deser's strap, and I think
> >> we should just basically ignore it as we can provide the necessary data
> >> in the DT.
> > 
> > What I meant is that, given that the UB914 is meant to be used with a
> > single camera, using a RAW mode, there's a much higher chance that
> > hardware strapping will work as intended there. We could thus start
> > without support for overrides in a UB914 driver (but as far as I
> > understand we're not planning to work on such a driver in the near
> > future, so it's hypothetical only), while in the UB960 driver we
> > probably need override support from the beginning.
> 
> Yes, but depending on the UB914 strap mode would still be only a partial 
> solution, and it would still need to also support overriding the strap 
> mode (from DT). As you said in the other mail, we anyway have to define 
> the serializer and the sensor in the DT, so even with UB914 we couldn't 
> just switch the camera module and change the mode via a DIP switch.
> 
> So I still don't see why we would even bother supporting the deser strap 
> mode, even on UB914.

What I meant is that I wouldn't nack an initial UB914 driver submission
just because it didn't support that feature, while for the UB960, I
think it should be there from the start. That's all theoretical anyway,
no UB914 driver is planned, and the feature should be so easy to add
that it would likely be there from the start.

> >>> For these reasons, I don't think the ti,rx-mode property can be defined
> >>> as reflecting the hardware MODE strap with the DS90UB913. I also think
> >>> it would be quite confusing to define it as the desired runtime
> >>> configuration for the DS90UB913 and as the hardware MODE strap for the
> >>> DS90UB953. Could it be (explicitly) defined as the desired runtime
> >>> configuration in all cases ?
> >>
> >> That sounds bad in a DT context =). You're right that the rx-mode can't
> >> be defined as reflecting the serializer mode strap, but I think we can
> >> define it as reflecting the default operation mode of the serializer
> >> hardware (or maybe rather the camera module).
> > 
> > What do you mean by "default operation mode" in this case ?
> 
> How the camera module is HW strapped and supposed to be used, how it 
> works (more or less) out of the box:
> 
> - The UB953 is HW strapped to sync or non-sync CSI mode, or RAW mode.
> - Both UB913 and UB953 (in RAW mode) have a sensor connected either with 
> 10 or 12 lines.
> - The sensor has a default (either HW or use-case default) pixel clock.
> 
> Based on those, I think we get all the possible modes. So, I think, 
> every camera module will have a known "normal" / "default" mode, which 
> is what the mode in the DT tells.
> 
> Well, for FPD4, there's also the CDR mode. UB971 can work in both FDP3 
> and FPD4 modes. But there again it should be clear what's the "normal" 
> operation mode, as it comes from a serializer HW strap.
> 
> We can do changes to those at runtime (not supported by the drivers), 
> e.g. changing from sync to non-sync CSI mode, changing the RAW mode, and 
> even changing from FDP4 to FDP3. But those, I believe, are rare cases, 
> and the changes have to be done carefully, on both the deser and ser 
> sides in certain order.
> 
> So, back to the original point, "desired runtime configuration" and 
> "default operation mode" are maybe the same thing =).

I'm fine with this, it "just" needs to be expressed in a clear way in
the bindings :-)

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 57+ messages in thread

end of thread, other threads:[~2023-01-05  6:55 UTC | newest]

Thread overview: 57+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-08 10:39 [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
2022-12-08 10:39 ` [PATCH v5 1/8] i2c: core: let adapters be notified of client attach/detach Tomi Valkeinen
2022-12-08 12:30   ` Andy Shevchenko
2022-12-08 16:10     ` Tomi Valkeinen
2022-12-11 16:55   ` Laurent Pinchart
2022-12-19  8:51     ` Luca Ceresoli
2022-12-19  9:48       ` Andy Shevchenko
2022-12-26 16:54         ` Laurent Pinchart
2022-12-27 20:07           ` Andy Shevchenko
2022-12-08 10:40 ` [PATCH v5 2/8] i2c: add I2C Address Translator (ATR) support Tomi Valkeinen
2022-12-08 12:53   ` Andy Shevchenko
2022-12-08 16:01     ` Tomi Valkeinen
2022-12-08 18:09       ` Andy Shevchenko
2022-12-08 10:40 ` [PATCH v5 3/8] dt-bindings: media: add bindings for TI DS90UB913 Tomi Valkeinen
2022-12-11 17:13   ` Laurent Pinchart
2022-12-11 17:21     ` Laurent Pinchart
2022-12-13 13:36       ` Tomi Valkeinen
2022-12-26 16:46         ` Laurent Pinchart
2023-01-04  8:12           ` Tomi Valkeinen
2023-01-04 12:52             ` Laurent Pinchart
2022-12-13 13:21     ` Tomi Valkeinen
2022-12-08 10:40 ` [PATCH v5 4/8] dt-bindings: media: add bindings for TI DS90UB953 Tomi Valkeinen
2022-12-09 21:27   ` Rob Herring
2023-01-04  8:26     ` Tomi Valkeinen
2022-12-11 17:34   ` Laurent Pinchart
2022-12-13 14:06     ` Tomi Valkeinen
2022-12-08 10:40 ` [PATCH v5 5/8] dt-bindings: media: add bindings for TI DS90UB960 Tomi Valkeinen
2022-12-09 21:30   ` Rob Herring
2022-12-11 17:58   ` Laurent Pinchart
2022-12-13 14:25     ` Tomi Valkeinen
2022-12-26 16:52       ` Laurent Pinchart
2023-01-04  8:59         ` Tomi Valkeinen
2023-01-04 12:57           ` Laurent Pinchart
2023-01-04 14:05             ` Tomi Valkeinen
2023-01-05  6:54               ` Laurent Pinchart
2022-12-08 10:40 ` [PATCH v5 6/8] media: i2c: add DS90UB960 driver Tomi Valkeinen
2022-12-08 10:40 ` [PATCH v5 7/8] media: i2c: add DS90UB913 driver Tomi Valkeinen
2022-12-11 18:33   ` Laurent Pinchart
2022-12-14  6:29     ` Tomi Valkeinen
2022-12-14  6:36       ` Tomi Valkeinen
2022-12-26 16:56         ` Laurent Pinchart
2022-12-26 19:25           ` Tomi Valkeinen
2023-01-04 13:55             ` Laurent Pinchart
2023-01-04 14:13               ` Tomi Valkeinen
2023-01-04 15:32                 ` Laurent Pinchart
2023-01-04 15:43                   ` Tomi Valkeinen
2022-12-26 17:01       ` Laurent Pinchart
2022-12-27 20:09         ` Andy Shevchenko
2023-01-04 13:29           ` Laurent Pinchart
2022-12-14  6:48     ` Tomi Valkeinen
2022-12-08 10:40 ` [PATCH v5 8/8] media: i2c: add DS90UB953 driver Tomi Valkeinen
2022-12-08 10:42 ` [PATCH v5 0/8] i2c-atr and FPDLink Tomi Valkeinen
2022-12-08 12:26   ` Andy Shevchenko
2022-12-08 14:40     ` Tomi Valkeinen
2022-12-08 15:57       ` Andy Shevchenko
2022-12-08 15:58         ` Andy Shevchenko
2022-12-08 16:05           ` Tomi Valkeinen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).