2022-11-01 13:23:24

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 0/8] i2c-atr and FPDLink

Hi,

Intro
-----

This is, kind of, v4 of Luca's i2c-atr and FPDLink series, v3 of which
you can find from:

https://lore.kernel.org/all/[email protected]/

I say "kind of", as the FPDLink drivers have diverged from Luca's
version quite a bit and the drivers support different HW versions. A
Big thanks for Luca for working on the drivers!

I'd really like to send and review i2c-atr and FPDLink drivers
separately, but as the concepts are new and those drivers are linked
together, in the end I decided to keep them in one series. Even so, I
think these patches divide quite clearly into to areas:

1) i2c-atr and the related code in the FPDLink drivers
2) Everything else about FPDLink

The FPDLink drivers support multiple streams and for that reason the
series is based on V4L2 streams series v15:

https://lore.kernel.org/all/[email protected]/

HW overview
-----------

TI's DS90UB9xx IC (UB9xx for short) family is a set of deserializer and
serializer ICs for video, both display and capture. These ICs support
FPDLink 3 and some also support FPDLink 4. From the user's point of view
there's really not much difference between FPDLink 3 and 4.

An example HW setup with two cameras could be like this:

+-- [Ser 1] <-- [Camera 1]
[CSI-2 RX] <-- [Deser] <--|
+-- [Ser 2] <-- [Camera 2]

The cameras send video streams over CSI-2 to the serializers. The
serializers encode the received data to FPDLink and send it to the
deserializer. The deserializer decodes the data and sends it forward
over CSI-2 bus.

The FPDLink bus is a bi-directional bus, with a fast forward-channel and
a slow back-channel. In addition to the video, the devices support
forwarding GPIOs (both ways) and forwarding i2c transactions with
address translation.

This series is only about the capture ICs. The HW versions supported
by these drivers are:

- UB960, FPDLink 3 deserializer
- UB9702, FPDLink 3/4 deserializer
- UB953, FPDLink 3 CSI-2 serializer
- UB913, FPDLink 3 parallel video serializer
- UB971, FPDLink 3/4 CSI-2 serializer

Note that this series does not support UB954 deserializer, which Luca
has. I don't have it and decided early to drop it for simpler
development. As UB954 is a subset of UB960, adding support for it should
be trivial.

FPDLink Deser and Ser Devices & Drivers
---------------------------------------

The serializer devices are, in a way, child devices of the deserializer.
It is the deserializer driver which creates and removes the serializer
devices, although the serializer device itself is not a child of the
deserializer, but rather the serializer is created as a device on the
i2c bus.

There are perhaps other methods to add the serializer devices. I think a
real bus is an overkill, as FPDLink is a point-to-point link. But if
there's no FPDLink bus, what are the options? Platform device? That's
not good either. So, for the time being, the serializers are i2c clients
on the main i2c bus, even if they're really behind the FPDLink.

Also, the deserializer driver needs to share some information with the
serializers, and it is done with platform data set to the serializer
device. If we had a bus, the bus could probably be used to convey this
information.

I2C & I2C ATR
-------------

We have three different i2c client types:

1) The deserializer is a normal i2c slave, and there's nothing special
about accessing it. The deserializer is connected to the "main" i2c bus.

2) The serializers are also accessible with i2c via FPDLink: when a
transaction to a specific (programmable) address happens on the main
i2c-bus the deserializer takes the transaction and forwards it via
FPDLink to the serializer. This is implemented in the deserializer
driver by just creating the serializer i2c device on the main i2c-bus.
This is not 100% correct, as the serializers are not directly connected
to an i2c bus as i2c slaves.

3) The serializer is an i2c master, and the camera and possibly other
i2c peripherals can be connected to that "remote" i2c bus. The
deserializer hardware has an i2c-alias table which describes which
i2c-alias address the deserializer will react to, what is the real
i2c-address and to which serializer the transactions will be forwarded
to. This is called address translation (ATR).

I2C ATR Thoughts
----------------

I have addressed most of the comments Luca received for the i2c-atr
driver, but other than that the i2c-atr driver is mostly the same.
However, there's one big difference: the i2c bus can be given by the
drivers.

So, looking at the DT, in Luca's version the deserializer node looked
like this for serializers and i2c-atr:

deser {
remote-chips {
remote-chip@0 {
// The serializer
};
};

i2c-atr {
i2c@0 {
// This is the bus behind the serializer
};
};
};

I have:

deser {
links {
link@0 {
serializer {
// The serializer
i2c {
// This is the bus
};
}
};
};
};

I think this reflects the hardware much better. But this means that the
i2c-atr "device", which in Luca's version is only the deserializer, is
in my version split between the deser and serializer. However, the only
thing I changed in the i2c-atr driver is that the i2c_atr_add_adapter()
function takes an fwnode handle which tells where the i2c bus is found
(but if it's NULL, it looks for i2c-atr just like in Luca's version).

Perhaps the biggest difference is that in Luca's version the i2c-atr was
private to the deserializer, and the deser driver called
i2c_atr_add_adapter(). In my version the deser shares the i2c-atr with
the serializers, and the serializers call i2c_atr_add_adapter(). I think
this is much better, as the i2c-bus is behind the serializer, and the
serializer's registers affect the bus, and thus the bus should really be
create/destroyed by the serializer driver as it controls the bus'
i2c-master hardware.

Now, i2c-mux and i2c-atr are quite similar as has been discussed in the
earlier reviews. And while the FPDLink ICs support ATR, we can easily
imagine a simpler deserializer which only supports mux-style forwarding.
For these reasons I believe we have two topics: 1) i2c-atr without
FPDLink, and 2) i2c-atr (and i2c-mux) with FPDlink.

1)
I am not aware of a stand-alone IC that performs address translation.
If there is, I think i2c-atr as it is in this series is a good solution
(but the bus fwnode feature mentioned above can be dropped).

2)
If I suggested adding an i2c-bus fwnode parameter to
i2c_mux_add_adapter(), and the i2c-bus might be under some other device,
I think the reception could be quite negative (and I would agree). For
this reason I'm not very happy with the i2c-atr and using it with
FPDLink.

In fact, I'm thinking that it might be better to just drop the i2c-atr
driver, and add the support directly to the FPDlink drivers. But that
could mean possibly duplicating the same code for other deser/ser
architectures, so I have kept the i2c-atr driver for now.

In any case, I think when figuring out 2), we can forget about ATR and
the added complexity the translation brings, and just think how i2c-mux
could be used in a deser-ser setup. And maybe the answer is "don't use
it, just write the support directly to the deser-ser drivers".

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 DS90UB960
dt-bindings: media: add bindings for TI DS90UB913
dt-bindings: media: add bindings for TI DS90UB953
media: i2c: add DS90UB960 driver
media: i2c: add DS90UB913 driver
media: i2c: add DS90UB953 driver

.../bindings/media/i2c/ti,ds90ub913.yaml | 127 +
.../bindings/media/i2c/ti,ds90ub953.yaml | 120 +
.../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++
Documentation/i2c/index.rst | 1 +
Documentation/i2c/muxes/i2c-atr.rst | 78 +
MAINTAINERS | 7 +
drivers/i2c/Kconfig | 9 +
drivers/i2c/Makefile | 1 +
drivers/i2c/i2c-atr.c | 497 ++
drivers/i2c/i2c-core-base.c | 18 +-
drivers/media/i2c/Kconfig | 28 +
drivers/media/i2c/Makefile | 3 +
drivers/media/i2c/ds90ub913.c | 892 ++++
drivers/media/i2c/ds90ub953.c | 1607 +++++++
drivers/media/i2c/ds90ub960.c | 4198 +++++++++++++++++
include/linux/i2c-atr.h | 80 +
include/linux/i2c.h | 16 +
include/media/i2c/ds90ub9xx.h | 16 +
18 files changed, 8089 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



2022-11-01 13:23:38

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

From: Luca Ceresoli <[email protected]>

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 <[email protected]>
Signed-off-by: Tomi Valkeinen <[email protected]>
---
Documentation/i2c/index.rst | 1 +
Documentation/i2c/muxes/i2c-atr.rst | 78 +++++
MAINTAINERS | 7 +
drivers/i2c/Kconfig | 9 +
drivers/i2c/Makefile | 1 +
drivers/i2c/i2c-atr.c | 497 ++++++++++++++++++++++++++++
include/linux/i2c-atr.h | 80 +++++
7 files changed, 673 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..3d177d4ec1d2 100644
--- a/Documentation/i2c/index.rst
+++ b/Documentation/i2c/index.rst
@@ -18,6 +18,7 @@ Introduction
i2c-topology
muxes/i2c-mux-gpio
i2c-sysfs
+ muxes/i2c-atr

Writing device drivers
======================
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 <[email protected]>
+
+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..357de12cbca6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9568,6 +9568,13 @@ L: [email protected]
S: Maintained
F: drivers/i2c/i2c-core-acpi.c

+I2C ADDRESS TRANSLATOR (ATR)
+M: Luca Ceresoli <[email protected]>
+L: [email protected]
+S: Maintained
+F: drivers/i2c/i2c-atr.c
+F: include/linux/i2c-atr.h
+
I2C CONTROLLER DRIVER FOR NVIDIA GPU
M: Ajay Gupta <[email protected]>
L: [email protected]
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..5636bbd03b09
--- /dev/null
+++ b/drivers/i2c/i2c-atr.c
@@ -0,0 +1,497 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019,2022 Luca Ceresoli <[email protected]>
+ *
+ * Originally based on i2c-mux.c
+ */
+
+#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>
+
+/**
+ * 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)) {
+ void *new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
+ GFP_KERNEL);
+ if (new_buf == NULL)
+ 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 = 0;
+ int ret = 0;
+
+ 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;
+ 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_add_adapter;
+ }
+
+ 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);
+
+ kfree(symlink_name);
+
+ 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:
+ 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[20];
+
+ struct i2c_adapter *adap = atr->adapter[chan_id];
+ struct i2c_atr_chan *chan = adap->algo_data;
+ struct fwnode_handle *fwnode = adap->dev.fwnode;
+ struct device *dev = atr->dev;
+
+ if (atr->adapter[chan_id] == NULL) {
+ 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;
+ size_t atr_size;
+
+ if (!ops || !ops->attach_client || !ops->detach_client)
+ return ERR_PTR(-EINVAL);
+
+ atr_size = struct_size(atr, adapter, max_adapters);
+ if (atr_size == SIZE_MAX)
+ return ERR_PTR(-EOVERFLOW);
+
+ atr = devm_kzalloc(dev, atr_size, 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 <[email protected]>");
+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..19ac2f1db96b
--- /dev/null
+++ b/include/linux/i2c-atr.h
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019,2022 Luca Ceresoli <[email protected]>
+ *
+ * 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 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);
+};
+
+/**
+ * Helper to add I2C ATR features to a device driver.
+ */
+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;
+ struct mutex lock;
+ int max_adapters;
+
+ struct i2c_adapter *adapter[0];
+};
+
+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


2022-11-01 13:23:43

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 1/8] i2c: core: let adapters be notified of client attach/detach

From: Luca Ceresoli <[email protected]>

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 <[email protected]>
Signed-off-by: Tomi Valkeinen <[email protected]>
---
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


2022-11-01 13:25:17

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 6/8] media: i2c: add DS90UB960 driver

Add driver for TI DS90UB960 FPDLink-3 Deserializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
drivers/media/i2c/Kconfig | 16 +
drivers/media/i2c/Makefile | 2 +
drivers/media/i2c/ds90ub960.c | 4198 +++++++++++++++++++++++++++++++++
include/media/i2c/ds90ub9xx.h | 16 +
4 files changed, 4232 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..2a78889e3487 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1595,4 +1595,20 @@ 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_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..c653474b5efc
--- /dev/null
+++ b/drivers/media/i2c/ds90ub960.c
@@ -0,0 +1,4198 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DS90UB960-Q1 video deserializer
+ *
+ * Copyright (c) 2019 Luca Ceresoli <[email protected]>
+ * Copyright (c) 2022 Tomi Valkeinen <[email protected]>
+ */
+
+#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;
+ u32 active_tx_mask = 0;
+ 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;
+
+ 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++;
+
+ /* 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, "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, "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, "strobe-pos", &strobe_pos);
+ if (ret) {
+ if (ret != -EINVAL) {
+ dev_err(dev, "Failed to read '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, "'strobe-pos' ignored as 'manual-strobe' not set\n");
+ }
+
+ ret = of_property_read_u32(np, "eq-level", &eq_level);
+ if (ret) {
+ if (ret != -EINVAL) {
+ dev_err(dev, "Failed to read '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);
+ } 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, "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 <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
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


2022-11-01 13:25:59

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 7/8] media: i2c: add DS90UB913 driver

Add driver for TI DS90UB913 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
drivers/media/i2c/Kconfig | 6 +
drivers/media/i2c/Makefile | 2 +-
drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++
3 files changed, 899 insertions(+), 1 deletion(-)
create mode 100644 drivers/media/i2c/ds90ub913.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 2a78889e3487..17185fd107d6 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1609,6 +1609,12 @@ config VIDEO_DS90UB960
Device driver for the Texas Instruments DS90UB960
FPD-Link III Deserializer

+config VIDEO_DS90UB913
+ tristate "TI DS90UB913 Serializer"
+ 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 <[email protected]>
+ *
+ * Copyright (c) 2019 Luca Ceresoli <[email protected]>
+ * Copyright (c) 2022 Tomi Valkeinen <[email protected]>
+ */
+
+#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 <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
--
2.34.1


2022-11-01 13:27:09

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 8/8] media: i2c: add DS90UB953 driver

Add driver for TI DS90UB953 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
drivers/media/i2c/Kconfig | 6 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/ds90ub953.c | 1607 +++++++++++++++++++++++++++++++++
3 files changed, 1614 insertions(+)
create mode 100644 drivers/media/i2c/ds90ub953.c

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 17185fd107d6..1563fa0abf0e 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1615,6 +1615,12 @@ config VIDEO_DS90UB913
Device driver for the Texas Instruments DS90UB913
FPD-Link III Serializer.

+config VIDEO_DS90UB953
+ tristate "TI DS90UB953 Serializer"
+ 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 <[email protected]>
+ *
+ * Copyright (c) 2019 Luca Ceresoli <[email protected]>
+ * Copyright (c) 2022 Tomi Valkeinen <[email protected]>
+ */
+
+#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 <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
--
2.34.1


2022-11-01 13:30:21

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 5/8] dt-bindings: media: add bindings for TI DS90UB953

Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
.../bindings/media/i2c/ti,ds90ub953.yaml | 120 ++++++++++++++++++
1 file changed, 120 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..355f7d6a19fe
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
@@ -0,0 +1,120 @@
+# 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 <[email protected]>
+
+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
+ description: CSI-2 input port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: FPD-Link 3 output port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ 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@21 {
+ compatible = "sony,imx390";
+ reg = <0x21>;
+
+ clocks = <&clk_cam_27M>;
+ clock-names = "inck";
+
+ 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>;
+
+ port {
+ sensor_out: endpoint {
+ remote-endpoint = <&ub953_in>;
+ };
+ };
+ };
+ };
+ };
+...
--
2.34.1


2022-11-01 13:31:08

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 4/8] dt-bindings: media: add bindings for TI DS90UB913

Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
.../bindings/media/i2c/ti,ds90ub913.yaml | 127 ++++++++++++++++++
1 file changed, 127 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..1e0d66704968
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
@@ -0,0 +1,127 @@
+# 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 <[email protected]>
+
+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
+ description: CSI-2 input port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: FPD-Link 3 output port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ 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@37 {
+ compatible = "ovti,ov10635";
+ reg = <0x37>;
+
+ clocks = <&fixed_clock>;
+ clock-names = "xvclk";
+
+ reset-gpios = <&gpio4 17 GPIO_ACTIVE_HIGH>;
+ powerdown-gpios = <&gpio5 11 GPIO_ACTIVE_HIGH>;
+
+ port {
+ sensor_out: endpoint {
+ remote-endpoint = <&ub913_in>;
+ };
+ };
+ };
+ };
+ };
+...
--
2.34.1


2022-11-01 13:34:26

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
.../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++++++++++++++++++
1 file changed, 392 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..4456d9b3e2c7
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
@@ -0,0 +1,392 @@
+# 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 <[email protected]>
+
+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
+
+ reg-names:
+ items:
+ - const: main
+
+ 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 for remote devices.
+
+ links:
+ type: object
+ additionalProperties: false
+
+ properties:
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ 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.
+
+ 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
+
+ cdr-mode:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ enum:
+ - 0 # FPD3
+ - 1 # FPD4
+ description: FPD-Link CDR Mode
+
+ strobe-pos:
+ $ref: /schemas/types.yaml#/definitions/int32
+ minimum: -13
+ maximum: 13
+ description: Manual strobe position, from -13 to 13
+
+ eq-level:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ maximum: 14
+ description: Manual EQ level, from 0 to 14
+
+ serializer:
+ type: object
+ description: FPD-Link Serializer node
+
+ required:
+ - reg
+ - i2c-alias
+ - rx-mode
+ - serializer
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: FPD-Link input 0
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ port@1:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: FPD-Link input 1
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ port@2:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: FPD-Link input 2
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ port@3:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: FPD-Link input 3
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ port@4:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: CSI-2 Output 0
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ properties:
+ clock-lanes:
+ maxItems: 1
+
+ data-lanes:
+ minItems: 1
+ maxItems: 4
+
+ port@5:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ description: CSI-2 Output 1
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+
+ properties:
+ clock-lanes:
+ maxItems: 1
+
+ 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-names = "main";
+ 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>;
+
+ rx-mode = <0>;
+ };
+ };
+
+ /* Port 0, Camera 1 */
+ port@1 {
+ reg = <1>;
+
+ ub960_fpd3_2_in: endpoint {
+ remote-endpoint = <&ub913_2_out>;
+
+ rx-mode = <0>;
+ };
+ };
+
+ /* Port 4, CSI-2 TX */
+ 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>;
+ };
+ };
+ };
+
+ links {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ /* Link 0 has DS90UB953 serializer and IMX390 sensor */
+
+ link@0 {
+ reg = <0>;
+ i2c-alias = <68>;
+
+ 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 {
+ clock-lanes = <0>;
+ 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@21 {
+ compatible = "sony,imx390";
+ reg = <0x21>;
+
+ clocks = <&clk_cam_27M>;
+ clock-names = "inck";
+
+ 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>;
+
+ port {
+ sensor_1_out: endpoint {
+ remote-endpoint = <&ub953_1_in>;
+ };
+ };
+ };
+ };
+ };
+ }; /* End of link@0 */
+
+ /* Link 1 has DS90UB913 serializer and OV10635 sensor */
+
+ link@1 {
+ reg = <1>;
+ i2c-alias = <69>;
+
+ 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@30 {
+ compatible = "ovti,ov10635";
+ reg = <0x30>;
+
+ 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>;
+ };
+ };
+ };
+ };
+ };
+ }; /* End of link@1 */
+ };
+ };
+ };
+...
--
2.34.1


2022-11-01 14:39:59

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:
> From: Luca Ceresoli <[email protected]>
>
> 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.

...

> i2c-topology
> muxes/i2c-mux-gpio
> i2c-sysfs
> + muxes/i2c-atr

Doesn't make sense to group muxes/*, that they are following each other?

...

> +I2C ADDRESS TRANSLATOR (ATR)
> +M: Luca Ceresoli <[email protected]>

Hmm... Are you going to maintain this? Or Review? Why not?

> +L: [email protected]
> +S: Maintained
> +F: drivers/i2c/i2c-atr.c
> +F: include/linux/i2c-atr.h

...

> + void *new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
> + GFP_KERNEL);
> + if (new_buf == NULL)
> + return -ENOMEM;

Isn't it better to write this as

void *new_buf;

new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]), GFP_KERNEL);
if (!new_buf)
return -ENOMEM;

Remarks:
- note the style of the conditional
- why is it void?

Also, does it make sense to use krealloc_array() or is it complete replacement
of the data?

> + kfree(chan->orig_addrs);
> + chan->orig_addrs = new_buf;
> + chan->orig_addrs_size = num;

...

> +static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg msgs[],
> + int num)

[] in the function parameter is longer than * and actually doesn't make
difference in C. Ditto for the rest of similar cases.

...

> +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)

Can flags be fixed size (yes I understand that in our case short would probably
never be different to u16, but for the sake of clearness)?

...

> +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 = 0;

Can we split assignment from the definition and locate it closer to the first
use?

> + int ret = 0;

Useless assignment.

> +
> + c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
> + if (!c2a)
> + return -ENOMEM;
> +
> + ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
> + &alias_id);

On one line looks better.

> + 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;
> +}

...

> +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;
> + 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);
> + }

It seems you have OF independent code, but by some reason you included of.h
instead of property.h. Am I right?

> + 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_add_adapter;
> + }
> +
> + symlink_name = kasprintf(GFP_KERNEL, "channel-%u", chan_id);

No NULL check?

> + 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);

Why WARNs? sysfs has already some in their implementation.

> +
> + kfree(symlink_name);
> +
> + 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:
> + mutex_destroy(&chan->orig_addrs_lock);
> + kfree(chan);
> + return ret;
> +}

...

> + struct fwnode_handle *fwnode = adap->dev.fwnode;

Please don't dereference fwnode like this, we have dev_fwnode() for that.

...

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

!

> + dev_err(dev, "Adapter %d does not exist\n", chan_id);
> + return;
> + }

...

> + snprintf(symlink_name, sizeof(symlink_name),
> + "channel-%u", chan->chan_id);

Once line?

...

> + atr_size = struct_size(atr, adapter, max_adapters);

> + if (atr_size == SIZE_MAX)
> + return ERR_PTR(-EOVERFLOW);

Dunno if you really need this to be separated from devm_kzalloc(), either way
you will get an error, but in embedded case it will be -ENOMEM.

> + atr = devm_kzalloc(dev, atr_size, GFP_KERNEL);
> + if (!atr)
> + return ERR_PTR(-ENOMEM);

...

> +EXPORT_SYMBOL_GPL(i2c_atr_delete);

I would put these to their own namespace from day 1.


...

> +/**
> + * Helper to add I2C ATR features to a device driver.
> + */

??? Copy'n'paste typo?

> +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;
> + struct mutex lock;
> + int max_adapters;
> +
> + struct i2c_adapter *adapter[0];

No VLAs.

> +};

...

> +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
> + struct fwnode_handle *bus_np);

Missing

struct fwnode_handle;

at the top of the file?

--
With Best Regards,
Andy Shevchenko



2022-11-01 16:55:51

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v4 4/8] dt-bindings: media: add bindings for TI DS90UB913


On Tue, 01 Nov 2022 15:20:28 +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> .../bindings/media/i2c/ti,ds90ub913.yaml | 127 ++++++++++++++++++
> 1 file changed, 127 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
>

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.example.dtb:0:0: /example-0/serializer/i2c/sensor@37: failed to match any schema with compatible: ['ovti,ov10635']

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/patch/

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


2022-11-01 16:57:14

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960


On Tue, 01 Nov 2022 15:20:27 +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> .../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++++++++++++++++++
> 1 file changed, 392 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.example.dtb:0:0: /example-0/i2c/deser@3d/links/link@0/serializer: failed to match any schema with compatible: ['ti,ds90ub953-q1']
Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.example.dtb:0:0: /example-0/i2c/deser@3d/links/link@0/serializer/i2c/sensor@21: failed to match any schema with compatible: ['sony,imx390']
Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.example.dtb:0:0: /example-0/i2c/deser@3d/links/link@1/serializer: failed to match any schema with compatible: ['ti,ds90ub913a-q1']
Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.example.dtb:0:0: /example-0/i2c/deser@3d/links/link@1/serializer/i2c/sensor@30: failed to match any schema with compatible: ['ovti,ov10635']

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/patch/

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


2022-11-01 17:04:44

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v4 5/8] dt-bindings: media: add bindings for TI DS90UB953


On Tue, 01 Nov 2022 15:20:29 +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB953 FPDLink-3 Serializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> .../bindings/media/i2c/ti,ds90ub953.yaml | 120 ++++++++++++++++++
> 1 file changed, 120 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
>

My bot found errors running 'make DT_CHECKER_FLAGS=-m dt_binding_check'
on your patch (DT_CHECKER_FLAGS is new in v5.13):

yamllint warnings/errors:

dtschema/dtc warnings/errors:
Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.example.dtb:0:0: /example-0/serializer/i2c/sensor@21: failed to match any schema with compatible: ['sony,imx390']

doc reference errors (make refcheckdocs):

See https://patchwork.ozlabs.org/patch/

This check can fail if there are any dependencies. The base for a patch
series is generally the most recent rc1.

If you already ran 'make dt_binding_check' and didn't see the above
error(s), then make sure 'yamllint' is installed and dt-schema is up to
date:

pip3 install dtschema --upgrade

Please check and re-submit.


2022-11-02 08:28:37

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 6/8] media: i2c: add DS90UB960 driver

On 01/11/2022 15:20, Tomi Valkeinen wrote:
> Add driver for TI DS90UB960 FPDLink-3 Deserializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> drivers/media/i2c/Kconfig | 16 +
> drivers/media/i2c/Makefile | 2 +
> drivers/media/i2c/ds90ub960.c | 4198 +++++++++++++++++++++++++++++++++
> include/media/i2c/ds90ub9xx.h | 16 +
> 4 files changed, 4232 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..2a78889e3487 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1595,4 +1595,20 @@ 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_GPIO
> + select I2C_ATR
> + help
> + Device driver for the Texas Instruments DS90UB960
> + FPD-Link III Deserializer
> +
> +endmenu

Looks like I'm missing proper dependencies and selects from this and the
ub913 and ub953 drivers. I'll fix those.

Tomi


2022-11-02 17:34:02

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> .../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++++++++++++++++++
> 1 file changed, 392 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..4456d9b3e2c7
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> @@ -0,0 +1,392 @@
> +# 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 <[email protected]>
> +
> +description: |

Don't need '|'

> + 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
> +
> + reg-names:
> + items:
> + - const: main

'reg-names' is not all that useful with only 1 entry.

> +
> + 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:

Something common or could be? If not, then needs a vendor prefix.

> + $ref: /schemas/types.yaml#/definitions/uint16-array
> + description:
> + i2c alias pool for remote devices.

Needs a better description. What's an 'alias pool'?

0-0xffff are valid values?

> +
> + links:
> + type: object
> + additionalProperties: false
> +
> + properties:
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0
> +
> + 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:

Vendor prefix.

> + 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:

Vendor prefix. And so on...

> + $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
> +
> + cdr-mode:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + enum:
> + - 0 # FPD3
> + - 1 # FPD4
> + description: FPD-Link CDR Mode
> +
> + strobe-pos:
> + $ref: /schemas/types.yaml#/definitions/int32
> + minimum: -13
> + maximum: 13
> + description: Manual strobe position, from -13 to 13

No need to put constraints in free form text.

> +
> + eq-level:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + maximum: 14
> + description: Manual EQ level, from 0 to 14
> +
> + serializer:
> + type: object
> + description: FPD-Link Serializer node
> +
> + required:
> + - reg
> + - i2c-alias
> + - rx-mode
> + - serializer
> +
> + ports:
> + $ref: /schemas/graph.yaml#/properties/ports
> +
> + properties:
> + port@0:
> + $ref: /schemas/graph.yaml#/$defs/port-base

additionalProperties: false

> + description: FPD-Link input 0
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#

unevaluatedProperties: false

Same for the other port nodes

> +
> + port@1:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + description: FPD-Link input 1
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> +
> + port@2:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + description: FPD-Link input 2
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> +
> + port@3:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + description: FPD-Link input 3
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> +
> + port@4:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + description: CSI-2 Output 0
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> +
> + properties:
> + clock-lanes:
> + maxItems: 1
> +
> + data-lanes:
> + minItems: 1
> + maxItems: 4

Why the constraints on this endpoint? Are the other ones actually using
properties from video-interfaces.yaml? If not, then just reference
/properties/port and drop 'endpoint' instead.

> +
> + port@5:
> + $ref: /schemas/graph.yaml#/$defs/port-base
> + description: CSI-2 Output 1
> +
> + properties:
> + endpoint:
> + $ref: /schemas/media/video-interfaces.yaml#
> +
> + properties:
> + clock-lanes:
> + maxItems: 1
> +
> + 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-names = "main";
> + 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>;
> +
> + rx-mode = <0>;

Looks like this is not defined under 'endpoint'.

> + };
> + };
> +
> + /* Port 0, Camera 1 */
> + port@1 {
> + reg = <1>;
> +
> + ub960_fpd3_2_in: endpoint {
> + remote-endpoint = <&ub913_2_out>;
> +
> + rx-mode = <0>;
> + };
> + };
> +
> + /* Port 4, CSI-2 TX */
> + 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>;
> + };
> + };
> + };
> +
> + links {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + /* Link 0 has DS90UB953 serializer and IMX390 sensor */
> +
> + link@0 {
> + reg = <0>;
> + i2c-alias = <68>;
> +
> + 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 {
> + clock-lanes = <0>;
> + 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@21 {
> + compatible = "sony,imx390";
> + reg = <0x21>;
> +
> + clocks = <&clk_cam_27M>;
> + clock-names = "inck";
> +
> + 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>;
> +
> + port {
> + sensor_1_out: endpoint {
> + remote-endpoint = <&ub953_1_in>;
> + };
> + };
> + };
> + };
> + };
> + }; /* End of link@0 */
> +
> + /* Link 1 has DS90UB913 serializer and OV10635 sensor */
> +
> + link@1 {
> + reg = <1>;
> + i2c-alias = <69>;
> +
> + 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@30 {
> + compatible = "ovti,ov10635";
> + reg = <0x30>;
> +
> + 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>;
> + };
> + };
> + };
> + };
> + };
> + }; /* End of link@1 */
> + };
> + };
> + };
> +...
> --
> 2.34.1
>
>

2022-11-02 18:06:48

by Rob Herring (Arm)

[permalink] [raw]
Subject: Re: [PATCH v4 4/8] dt-bindings: media: add bindings for TI DS90UB913

On Tue, Nov 01, 2022 at 03:20:28PM +0200, Tomi Valkeinen wrote:
> Add DT bindings for TI DS90UB913 FPDLink-3 Serializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> .../bindings/media/i2c/ti,ds90ub913.yaml | 127 ++++++++++++++++++
> 1 file changed, 127 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml

Similar comments for this one.

Rob

2022-11-03 12:41:11

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

On 03/11/2022 14:13, Vaittinen, Matti wrote:
> On 11/3/22 13:50, Tomi Valkeinen wrote:
>> Hi Rob,
>>
>> On 02/11/2022 19:26, Rob Herring wrote:
>>> On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
>>>> +
>>>> +  i2c-alias-pool:
>>>
>>> Something common or could be? If not, then needs a vendor prefix.
>>
>> I'll have to think about this. It is related to the i2c-atr, so I think
>> it might be a common thing.
>
> I'd say this should be common. Where the i2c-atr properties should live
> is another question though. If the I2C-atr stays as a genericly usable
> component - then these bindings should be in a file that can be
> referenced by other I2C-atr users (like the UB960 here).

Yep. All the links, link, serializer and alias nodes/properties are new
things here, and I guess these could be used by other deser-ser systems.
That said, I don't have any experience with other systems.

> // snip
>
>>>> +
>>>> +          i2c-alias:
>>>
>>> Vendor prefix.
>>>
>>>> +            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:
>>>
>>> Vendor prefix. And so on... >
>> Yes, I totally missed these.
>
>
> I think the i2c-alias might need to be common as well?

Perhaps...

I was also thinking that the serializer addresses could be taken from
the i2c-alias-pool. But maybe that's not a good idea, as the
serializer-access and remote-peripheral-access are a bit different (e.g.
no ATR when accessing the serializer).

And I was thinking that, at least here, the alias addresses can be
"anything", so they could be reserved dynamically at runtime, without
any predefined aliases. But that might be a bit confusing to the user.

Tomi


2022-11-03 12:47:42

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

Hi Rob,

On 02/11/2022 19:26, Rob Herring wrote:
> On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
>> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>>
>> Signed-off-by: Tomi Valkeinen <[email protected]>
>> ---
>> .../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++++++++++++++++++
>> 1 file changed, 392 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..4456d9b3e2c7
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
>> @@ -0,0 +1,392 @@
>> +# 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 <[email protected]>
>> +
>> +description: |
>
> Don't need '|'

Hmm, ok... But why does that work? I can only find yaml examples for
multi-line with either | or >.

>> + 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
>> +
>> + reg-names:
>> + items:
>> + - const: main
>
> 'reg-names' is not all that useful with only 1 entry.

True.

>> +
>> + 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:
>
> Something common or could be? If not, then needs a vendor prefix.

I'll have to think about this. It is related to the i2c-atr, so I think
it might be a common thing.

>> + $ref: /schemas/types.yaml#/definitions/uint16-array
>> + description:
>> + i2c alias pool for remote devices.
>
> Needs a better description. What's an 'alias pool'?

Right.

"i2c alias pool is a pool of i2c addresses on the main i2c bus that can
be used to access the remote peripherals. 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."

> 0-0xffff are valid values?

They are i2c addresses, and linux i2c uses u16 for addresses. Then
again, the fpdlink devices only support 7-bit addresses, so maybe this
could be an uint8 array. I am not sure what's the best way to define this.

>> +
>> + links:
>> + type: object
>> + additionalProperties: false
>> +
>> + properties:
>> + '#address-cells':
>> + const: 1
>> +
>> + '#size-cells':
>> + const: 0
>> +
>> + 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:
>
> Vendor prefix.
>
>> + 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:
>
> Vendor prefix. And so on...

Yes, I totally missed these.

>> + $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
>> +
>> + cdr-mode:
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + enum:
>> + - 0 # FPD3
>> + - 1 # FPD4
>> + description: FPD-Link CDR Mode
>> +
>> + strobe-pos:
>> + $ref: /schemas/types.yaml#/definitions/int32
>> + minimum: -13
>> + maximum: 13
>> + description: Manual strobe position, from -13 to 13
>
> No need to put constraints in free form text.

Ok.

>> +
>> + eq-level:
>> + $ref: /schemas/types.yaml#/definitions/uint32
>> + maximum: 14
>> + description: Manual EQ level, from 0 to 14
>> +
>> + serializer:
>> + type: object
>> + description: FPD-Link Serializer node
>> +
>> + required:
>> + - reg
>> + - i2c-alias
>> + - rx-mode
>> + - serializer
>> +
>> + ports:
>> + $ref: /schemas/graph.yaml#/properties/ports
>> +
>> + properties:
>> + port@0:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>
> additionalProperties: false

This gives me check errors about the port's 'reg' property. Using
'unevaluatedProperties' works fine. Is 'unevaluatedProperties' correct,
or am I missing something here?

>> + description: FPD-Link input 0
>> +
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>
> unevaluatedProperties: false
>
> Same for the other port nodes

Yep.

>> +
>> + port@1:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>> + description: FPD-Link input 1
>> +
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>> +
>> + port@2:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>> + description: FPD-Link input 2
>> +
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>> +
>> + port@3:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>> + description: FPD-Link input 3
>> +
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>> +
>> + port@4:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>> + description: CSI-2 Output 0
>> +
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>> +
>> + properties:
>> + clock-lanes:
>> + maxItems: 1
>> +
>> + data-lanes:
>> + minItems: 1
>> + maxItems: 4
>
> Why the constraints on this endpoint? Are the other ones actually using
> properties from video-interfaces.yaml? If not, then just reference
> /properties/port and drop 'endpoint' instead.

The ports 0-3 do not use any properties from video-interfaces.yaml, so
I'll drop the endpoint.

Ports 4,5 are CSI-2 ports and need the clock-lanes and data-lanes to be
defined.

Is there something wrong with the constraints, or were you just
wondering about the difference between ports 0-3 and 4,5

>> +
>> + port@5:
>> + $ref: /schemas/graph.yaml#/$defs/port-base
>> + description: CSI-2 Output 1
>> +
>> + properties:
>> + endpoint:
>> + $ref: /schemas/media/video-interfaces.yaml#
>> +
>> + properties:
>> + clock-lanes:
>> + maxItems: 1
>> +
>> + 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-names = "main";
>> + 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>;
>> +
>> + rx-mode = <0>;
>
> Looks like this is not defined under 'endpoint'.

Indeed, and after adding the 'unevaluatedProperties' I do get a warning
here.

>> + };
>> + };
>> +
>> + /* Port 0, Camera 1 */
>> + port@1 {
>> + reg = <1>;
>> +
>> + ub960_fpd3_2_in: endpoint {
>> + remote-endpoint = <&ub913_2_out>;
>> +
>> + rx-mode = <0>;
>> + };
>> + };
>> +
>> + /* Port 4, CSI-2 TX */
>> + 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>;
>> + };
>> + };
>> + };
>> +
>> + links {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + /* Link 0 has DS90UB953 serializer and IMX390 sensor */
>> +
>> + link@0 {
>> + reg = <0>;
>> + i2c-alias = <68>;
>> +
>> + 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 {
>> + clock-lanes = <0>;
>> + 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@21 {
>> + compatible = "sony,imx390";

DT_CHECKER_FLAGS=-m gives a warning here, as sony,imx390 is not in
upstream. The sensor details are not really relevant here, but I used
the data for the setup I have.

Should I instead use some sensor here that is in upstream, which I think
should work with the fpdlink ICs?

Tomi


2022-11-03 13:44:04

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

On 11/3/22 13:50, Tomi Valkeinen wrote:
> Hi Rob,
>
> On 02/11/2022 19:26, Rob Herring wrote:
>> On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
>>> +
>>> +  i2c-alias-pool:
>>
>> Something common or could be? If not, then needs a vendor prefix.
>
> I'll have to think about this. It is related to the i2c-atr, so I think
> it might be a common thing.

I'd say this should be common. Where the i2c-atr properties should live
is another question though. If the I2C-atr stays as a genericly usable
component - then these bindings should be in a file that can be
referenced by other I2C-atr users (like the UB960 here).

// snip

>>> +
>>> +          i2c-alias:
>>
>> Vendor prefix.
>>
>>> +            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:
>>
>> Vendor prefix. And so on... >
> Yes, I totally missed these.


I think the i2c-alias might need to be common as well?

Best Regards
-- Matti Vaittinen

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~

2022-11-04 12:42:04

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

Hi Andy,

On 01/11/2022 16:30, Andy Shevchenko wrote:
> On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:
>> From: Luca Ceresoli <[email protected]>
>>
>> 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.
>
> ...
>
>> i2c-topology
>> muxes/i2c-mux-gpio
>> i2c-sysfs
>> + muxes/i2c-atr
>
> Doesn't make sense to group muxes/*, that they are following each other?

Ok.

> ...
>
>> +I2C ADDRESS TRANSLATOR (ATR)
>> +M: Luca Ceresoli <[email protected]>
>
> Hmm... Are you going to maintain this? Or Review? Why not?

We haven't discussed with Luca if he wants to maintain this (this is
mostly his code). But, indeed, I should add my name there.

>> +L: [email protected]
>> +S: Maintained
>> +F: drivers/i2c/i2c-atr.c
>> +F: include/linux/i2c-atr.h
>
> ...
>
>> + void *new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]),
>> + GFP_KERNEL);
>> + if (new_buf == NULL)
>> + return -ENOMEM;
>
> Isn't it better to write this as
>
> void *new_buf;
>
> new_buf = kmalloc_array(num, sizeof(chan->orig_addrs[0]), GFP_KERNEL);
> if (!new_buf)
> return -ENOMEM;

Ok.

> Remarks:
> - note the style of the conditional
> - why is it void?

No idea. I'll change it.

>
> Also, does it make sense to use krealloc_array() or is it complete replacement
> of the data?

The whole array will be rewritten, so we don't need to preserve the
current data.

The buffer allocated here (i.e. orig_addrs) is only used for the
duration of the i2c_atr_master_xfer(). So, we could allocate a new
buffer for every xfer call, but to avoid that, we retain the old buffer.
Any old data in the buffer can be discarded.

>> + kfree(chan->orig_addrs);
>> + chan->orig_addrs = new_buf;
>> + chan->orig_addrs_size = num;
>
> ...
>
>> +static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg msgs[],
>> + int num)
>
> [] in the function parameter is longer than * and actually doesn't make
> difference in C. Ditto for the rest of similar cases.

Ok. I missed a few, it seems.

> ...
>
>> +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)
>
> Can flags be fixed size (yes I understand that in our case short would probably
> never be different to u16, but for the sake of clearness)?

The parameters and their types come from the ops in struct i2c_algorithm.

> ...
>
>> +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 = 0;
>
> Can we split assignment from the definition and locate it closer to the first
> use?

Actually, I don't think we need to initialize it at all. If
attach_client() fails, we don't care about alias_id. If attach_client()
succeeds, it _must_ return alias_id.

>> + int ret = 0;
>
> Useless assignment.

Yep.

>> +
>> + c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
>> + if (!c2a)
>> + return -ENOMEM;
>> +
>> + ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
>> + &alias_id);
>
> On one line looks better.

I agree, but it doesn't fit into 80 characters. I personally think
that's a too narrow a limit, but some maintainers absolutely require max
80 chars, so I try to limit the lines to 80 unless it looks really ugly.

>> + 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;
>> +}
>
> ...
>
>> +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;
>> + 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);
>> + }
>
> It seems you have OF independent code, but by some reason you included of.h
> instead of property.h. Am I right?

Just an leftover from the conversion from of to fwnode.

>> + 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_add_adapter;
>> + }
>> +
>> + symlink_name = kasprintf(GFP_KERNEL, "channel-%u", chan_id);
>
> No NULL check?

Right, missed that.

>> + 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);
>
> Why WARNs? sysfs has already some in their implementation.

True, and I can drop these if required. But afaics, sysfs_create_link
only warns if there's a duplicate entry, not for other errors.

>> +
>> + kfree(symlink_name);
>> +
>> + 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:
>> + mutex_destroy(&chan->orig_addrs_lock);
>> + kfree(chan);
>> + return ret;
>> +}
>
> ...
>
>> + struct fwnode_handle *fwnode = adap->dev.fwnode;
>
> Please don't dereference fwnode like this, we have dev_fwnode() for that.

Ok.

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

Yep.

>> + dev_err(dev, "Adapter %d does not exist\n", chan_id);
>> + return;
>> + }
>
> ...
>
>> + snprintf(symlink_name, sizeof(symlink_name),
>> + "channel-%u", chan->chan_id);
>
> Once line?

80 char limit here too. But I see that this is (kind of) broken. In the
i2c_atr_add_adapter() I used dynamic alloc for the symlink_name, but
here we still have the fixed size buffer.

>
> ...
>
>> + atr_size = struct_size(atr, adapter, max_adapters);
>
>> + if (atr_size == SIZE_MAX)
>> + return ERR_PTR(-EOVERFLOW);
>
> Dunno if you really need this to be separated from devm_kzalloc(), either way
> you will get an error, but in embedded case it will be -ENOMEM.

Yep. Well... I kind of like it to be explicit. Calling alloc(SIZE_MAX)
doesn't feel nice.

>> + atr = devm_kzalloc(dev, atr_size, GFP_KERNEL);
>> + if (!atr)
>> + return ERR_PTR(-ENOMEM);
>
> ...
>
>> +EXPORT_SYMBOL_GPL(i2c_atr_delete);
>
> I would put these to their own namespace from day 1.

What would be the namespace? Isn't this something that should be
subsystem-wide decision? I have to admit I have never used symbol
namespaces, and don't know much about them.

>
> ...
>
>> +/**
>> + * Helper to add I2C ATR features to a device driver.
>> + */
>
> ??? Copy'n'paste typo?

No idea where that is from... I'll fix it.

>> +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;
>> + struct mutex lock;
>> + int max_adapters;
>> +
>> + struct i2c_adapter *adapter[0];
>
> No VLAs.

Ok.

I'm not arguing against any of the comments you've made, I think they
are all valid, but I want to point out that many of them are in a code
copied from i2c-mux.

Whether there's any value in keeping i2c-mux and i2c-atr similar in
design/style... Maybe not.

>> +};
>
> ...
>
>> +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
>> + struct fwnode_handle *bus_np);
>
> Missing
>
> struct fwnode_handle;
>
> at the top of the file?

Ok.

Tomi




2022-11-04 13:04:13

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

On Fri, Nov 04, 2022 at 01:59:06PM +0200, Tomi Valkeinen wrote:
> On 01/11/2022 16:30, Andy Shevchenko wrote:
> > On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:

...

> > > + ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
> > > + &alias_id);
> >
> > On one line looks better.
>
> I agree, but it doesn't fit into 80 characters. I personally think that's a
> too narrow a limit, but some maintainers absolutely require max 80 chars, so
> I try to limit the lines to 80 unless it looks really ugly.

OK.

...

> > > + 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);
> >
> > Why WARNs? sysfs has already some in their implementation.
>
> True, and I can drop these if required. But afaics, sysfs_create_link only
> warns if there's a duplicate entry, not for other errors.

The problem with WARN that it can be easily converted to real Oops. Do you
consider other errors are so fatal that machine would need a reboot?

...

> > > + atr_size = struct_size(atr, adapter, max_adapters);
> >
> > > + if (atr_size == SIZE_MAX)
> > > + return ERR_PTR(-EOVERFLOW);
> >
> > Dunno if you really need this to be separated from devm_kzalloc(), either way
> > you will get an error, but in embedded case it will be -ENOMEM.
>
> Yep. Well... I kind of like it to be explicit. Calling alloc(SIZE_MAX)
> doesn't feel nice.

Yeah, but that is exactly the point of returning SIZE_MAX by the helpers from
overflow.h. And many of them are called inside a few k*alloc*() APIs.

So, I don't think it's ugly or not nice from that perspective.

> > > + atr = devm_kzalloc(dev, atr_size, GFP_KERNEL);
> > > + if (!atr)
> > > + return ERR_PTR(-ENOMEM);

...

> > > +EXPORT_SYMBOL_GPL(i2c_atr_delete);
> >
> > I would put these to their own namespace from day 1.
>
> What would be the namespace? Isn't this something that should be
> subsystem-wide decision? I have to admit I have never used symbol
> namespaces, and don't know much about them.

Yes, subsystem is I2C, but you introducing a kinda subsubsystem. Wouldn't be
better to provide all symbols in the I2C_ATR namespace from now on?

It really helps not polluting global namespace and also helps to identify
users in the source tree.

...

> > > +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;
> > > + struct mutex lock;
> > > + int max_adapters;
> > > +
> > > + struct i2c_adapter *adapter[0];
> >
> > No VLAs.
>
> Ok.
>
> I'm not arguing against any of the comments you've made, I think they are
> all valid, but I want to point out that many of them are in a code copied
> from i2c-mux.
>
> Whether there's any value in keeping i2c-mux and i2c-atr similar in
> design/style... Maybe not.

You can address my comment by simply dropping 0 in the respective member.

> > > +};

--
With Best Regards,
Andy Shevchenko



2022-11-04 16:19:38

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

On 04/11/2022 14:38, Andy Shevchenko wrote:
> On Fri, Nov 04, 2022 at 01:59:06PM +0200, Tomi Valkeinen wrote:
>> On 01/11/2022 16:30, Andy Shevchenko wrote:
>>> On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:
>
> ...
>
>>>> + ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
>>>> + &alias_id);
>>>
>>> On one line looks better.
>>
>> I agree, but it doesn't fit into 80 characters. I personally think that's a
>> too narrow a limit, but some maintainers absolutely require max 80 chars, so
>> I try to limit the lines to 80 unless it looks really ugly.
>
> OK.
>
> ...
>
>>>> + 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);
>>>
>>> Why WARNs? sysfs has already some in their implementation.
>>
>> True, and I can drop these if required. But afaics, sysfs_create_link only
>> warns if there's a duplicate entry, not for other errors.
>
> The problem with WARN that it can be easily converted to real Oops. Do you
> consider other errors are so fatal that machine would need a reboot?

Yes, WARNs are bad, especially as the error here is not critical. I'll
change these to dev_warn(). (also, I didn't know WARN could be made to
oops).

> ...
>
>>>> + atr_size = struct_size(atr, adapter, max_adapters);
>>>
>>>> + if (atr_size == SIZE_MAX)
>>>> + return ERR_PTR(-EOVERFLOW);
>>>
>>> Dunno if you really need this to be separated from devm_kzalloc(), either way
>>> you will get an error, but in embedded case it will be -ENOMEM.
>>
>> Yep. Well... I kind of like it to be explicit. Calling alloc(SIZE_MAX)
>> doesn't feel nice.
>
> Yeah, but that is exactly the point of returning SIZE_MAX by the helpers from
> overflow.h. And many of them are called inside a few k*alloc*() APIs.
>
> So, I don't think it's ugly or not nice from that perspective.

Ok, sounds fine to me. I'll drop the check.

>>>> + atr = devm_kzalloc(dev, atr_size, GFP_KERNEL);
>>>> + if (!atr)
>>>> + return ERR_PTR(-ENOMEM);
>
> ...
>
>>>> +EXPORT_SYMBOL_GPL(i2c_atr_delete);
>>>
>>> I would put these to their own namespace from day 1.
>>
>> What would be the namespace? Isn't this something that should be
>> subsystem-wide decision? I have to admit I have never used symbol
>> namespaces, and don't know much about them.
>
> Yes, subsystem is I2C, but you introducing a kinda subsubsystem. Wouldn't be
> better to provide all symbols in the I2C_ATR namespace from now on?
>
> It really helps not polluting global namespace and also helps to identify
> users in the source tree.

Alright, I'll look into this.

> ...
>
>>>> +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;
>>>> + struct mutex lock;
>>>> + int max_adapters;
>>>> +
>>>> + struct i2c_adapter *adapter[0];
>>>
>>> No VLAs.
>>
>> Ok.
>>
>> I'm not arguing against any of the comments you've made, I think they are
>> all valid, but I want to point out that many of them are in a code copied
>> from i2c-mux.
>>
>> Whether there's any value in keeping i2c-mux and i2c-atr similar in
>> design/style... Maybe not.
>
> You can address my comment by simply dropping 0 in the respective member.

Oh, I thought you meant no "extensible" structs. I'll drop the 0.

Tomi


2022-11-04 17:56:31

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

On Fri, Nov 04, 2022 at 05:26:24PM +0200, Tomi Valkeinen wrote:
> On 04/11/2022 14:38, Andy Shevchenko wrote:
> > On Fri, Nov 04, 2022 at 01:59:06PM +0200, Tomi Valkeinen wrote:
> > > On 01/11/2022 16:30, Andy Shevchenko wrote:
> > > > On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:

...

> > > > > + 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);
> > > >
> > > > Why WARNs? sysfs has already some in their implementation.
> > >
> > > True, and I can drop these if required. But afaics, sysfs_create_link only
> > > warns if there's a duplicate entry, not for other errors.
> >
> > The problem with WARN that it can be easily converted to real Oops. Do you
> > consider other errors are so fatal that machine would need a reboot?
>
> Yes, WARNs are bad, especially as the error here is not critical. I'll
> change these to dev_warn(). (also, I didn't know WARN could be made to
> oops).

panic_on_warn

--
With Best Regards,
Andy Shevchenko



2022-11-07 12:06:41

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v4 0/8] i2c-atr and FPDLink

Hi Tomi, All,

On Tue, 1 Nov 2022 15:20:24 +0200
Tomi Valkeinen <[email protected]> wrote:

> Hi,
>
> Intro
> -----
>
> This is, kind of, v4 of Luca's i2c-atr and FPDLink series, v3 of which
> you can find from:
>
> https://lore.kernel.org/all/[email protected]/
>
> I say "kind of", as the FPDLink drivers have diverged from Luca's
> version quite a bit and the drivers support different HW versions. A
> Big thanks for Luca for working on the drivers!
>
> I'd really like to send and review i2c-atr and FPDLink drivers
> separately, but as the concepts are new and those drivers are linked
> together, in the end I decided to keep them in one series. Even so, I
> think these patches divide quite clearly into to areas:
>
> 1) i2c-atr and the related code in the FPDLink drivers
> 2) Everything else about FPDLink
>
> The FPDLink drivers support multiple streams and for that reason the
> series is based on V4L2 streams series v15:
>
> https://lore.kernel.org/all/[email protected]/
>
> HW overview
> -----------
>
> TI's DS90UB9xx IC (UB9xx for short) family is a set of deserializer and
> serializer ICs for video, both display and capture. These ICs support
> FPDLink 3 and some also support FPDLink 4. From the user's point of view
> there's really not much difference between FPDLink 3 and 4.
>
> An example HW setup with two cameras could be like this:
>
> +-- [Ser 1] <-- [Camera 1]
> [CSI-2 RX] <-- [Deser] <--|
> +-- [Ser 2] <-- [Camera 2]
>
> The cameras send video streams over CSI-2 to the serializers. The
> serializers encode the received data to FPDLink and send it to the
> deserializer. The deserializer decodes the data and sends it forward
> over CSI-2 bus.
>
> The FPDLink bus is a bi-directional bus, with a fast forward-channel and
> a slow back-channel. In addition to the video, the devices support
> forwarding GPIOs (both ways) and forwarding i2c transactions with
> address translation.
>
> This series is only about the capture ICs. The HW versions supported
> by these drivers are:
>
> - UB960, FPDLink 3 deserializer
> - UB9702, FPDLink 3/4 deserializer
> - UB953, FPDLink 3 CSI-2 serializer
> - UB913, FPDLink 3 parallel video serializer
> - UB971, FPDLink 3/4 CSI-2 serializer
>
> Note that this series does not support UB954 deserializer, which Luca
> has. I don't have it and decided early to drop it for simpler
> development. As UB954 is a subset of UB960, adding support for it should
> be trivial.
>
> FPDLink Deser and Ser Devices & Drivers
> ---------------------------------------
>
> The serializer devices are, in a way, child devices of the deserializer.
> It is the deserializer driver which creates and removes the serializer
> devices, although the serializer device itself is not a child of the
> deserializer, but rather the serializer is created as a device on the
> i2c bus.
>
> There are perhaps other methods to add the serializer devices. I think a
> real bus is an overkill, as FPDLink is a point-to-point link. But if
> there's no FPDLink bus, what are the options? Platform device? That's
> not good either. So, for the time being, the serializers are i2c clients
> on the main i2c bus, even if they're really behind the FPDLink.
>
> Also, the deserializer driver needs to share some information with the
> serializers, and it is done with platform data set to the serializer
> device. If we had a bus, the bus could probably be used to convey this
> information.
>
> I2C & I2C ATR
> -------------
>
> We have three different i2c client types:
>
> 1) The deserializer is a normal i2c slave, and there's nothing special
> about accessing it. The deserializer is connected to the "main" i2c bus.
>
> 2) The serializers are also accessible with i2c via FPDLink: when a
> transaction to a specific (programmable) address happens on the main
> i2c-bus the deserializer takes the transaction and forwards it via
> FPDLink to the serializer. This is implemented in the deserializer
> driver by just creating the serializer i2c device on the main i2c-bus.
> This is not 100% correct, as the serializers are not directly connected
> to an i2c bus as i2c slaves.
>
> 3) The serializer is an i2c master, and the camera and possibly other
> i2c peripherals can be connected to that "remote" i2c bus. The
> deserializer hardware has an i2c-alias table which describes which
> i2c-alias address the deserializer will react to, what is the real
> i2c-address and to which serializer the transactions will be forwarded
> to. This is called address translation (ATR).

An excellent description so far!

> I2C ATR Thoughts
> ----------------
>
> I have addressed most of the comments Luca received for the i2c-atr
> driver, but other than that the i2c-atr driver is mostly the same.
> However, there's one big difference: the i2c bus can be given by the
> drivers.
>
> So, looking at the DT, in Luca's version the deserializer node looked
> like this for serializers and i2c-atr:
>
> deser {
> remote-chips {
> remote-chip@0 {
> // The serializer
> };
> };
>
> i2c-atr {
> i2c@0 {
> // This is the bus behind the serializer
> };
> };
> };
>
> I have:
>
> deser {
> links {
> link@0 {
> serializer {
> // The serializer
> i2c {
> // This is the bus
> };
> }
> };
> };
> };
>
> I think this reflects the hardware much better. But this means that the
> i2c-atr "device", which in Luca's version is only the deserializer, is
> in my version split between the deser and serializer. However, the only
> thing I changed in the i2c-atr driver is that the i2c_atr_add_adapter()
> function takes an fwnode handle which tells where the i2c bus is found
> (but if it's NULL, it looks for i2c-atr just like in Luca's version).
>
> Perhaps the biggest difference is that in Luca's version the i2c-atr was
> private to the deserializer, and the deser driver called
> i2c_atr_add_adapter(). In my version the deser shares the i2c-atr with
> the serializers, and the serializers call i2c_atr_add_adapter(). I think
> this is much better, as the i2c-bus is behind the serializer, and the
> serializer's registers affect the bus, and thus the bus should really be
> create/destroyed by the serializer driver as it controls the bus'
> i2c-master hardware.
>
> Now, i2c-mux and i2c-atr are quite similar as has been discussed in the
> earlier reviews. And while the FPDLink ICs support ATR, we can easily
> imagine a simpler deserializer which only supports mux-style forwarding.
> For these reasons I believe we have two topics: 1) i2c-atr without
> FPDLink, and 2) i2c-atr (and i2c-mux) with FPDlink.
>
> 1)
> I am not aware of a stand-alone IC that performs address translation.
> If there is, I think i2c-atr as it is in this series is a good solution
> (but the bus fwnode feature mentioned above can be dropped).
>
> 2)
> If I suggested adding an i2c-bus fwnode parameter to
> i2c_mux_add_adapter(), and the i2c-bus might be under some other device,
> I think the reception could be quite negative (and I would agree). For
> this reason I'm not very happy with the i2c-atr and using it with
> FPDLink.
>
> In fact, I'm thinking that it might be better to just drop the i2c-atr
> driver, and add the support directly to the FPDlink drivers. But that
> could mean possibly duplicating the same code for other deser/ser
> architectures, so I have kept the i2c-atr driver for now.

Indeed I think the ROHM serdes chips do have an address translation
feature that works pretty much like the TI ones, and the ATR should be
cleanly reusable across the two brands. The ATR code might be
simplified to just provide helpers for common code maybe, but I'd
rather avoid code duplication.

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

2022-11-07 12:56:53

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [PATCH v4 0/8] i2c-atr and FPDLink

On 11/7/22 13:48, Luca Ceresoli wrote:
> Hi Tomi, All,
>
> On Tue, 1 Nov 2022 15:20:24 +0200
> Tomi Valkeinen <[email protected]> wrote:
>>
>> 2)
>> If I suggested adding an i2c-bus fwnode parameter to
>> i2c_mux_add_adapter(), and the i2c-bus might be under some other device,
>> I think the reception could be quite negative (and I would agree). For
>> this reason I'm not very happy with the i2c-atr and using it with
>> FPDLink.
>>
>> In fact, I'm thinking that it might be better to just drop the i2c-atr
>> driver, and add the support directly to the FPDlink drivers. But that
>> could mean possibly duplicating the same code for other deser/ser
>> architectures, so I have kept the i2c-atr driver for now.
>
> Indeed I think the ROHM serdes chips do have an address translation
> feature that works pretty much like the TI ones, and the ATR should be
> cleanly reusable across the two brands. The ATR code might be
> simplified to just provide helpers for common code maybe, but I'd
> rather avoid code duplication.

I tend to agree with Luca on keeping the I2C-atr a re-usable generic
code. The reason why I did not (yet) bring this (or other concerns) up
is that currently I don't have a permission to upstream my work for ROHM
SerDes Linux drivers :( I still do discuss the decision but to be
honest, I believe the chances to get my drivers upstream are getting
slim. Hence I did not see it reasonable to try pushing Tomi's / Luca's
work to any direction.

Yet, after that being said - these SerDes devices which can combine I2C
buses from multiple remote devices to one local I2C controller do bring
a real need for a solution like the address translation. Thus, I would
not be surprized if we saw new devices with this kind of feature.
Potentially also from other vendors. So, regardless what happens with
ROHM SERDESes - I (too) think we should keep the ATR as independently
reusable component.

Yours,
-- Matti Vaittinen

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~

2022-11-07 12:58:19

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

On Fri, 4 Nov 2022 13:59:06 +0200
Tomi Valkeinen <[email protected]> wrote:

> Hi Andy,
>
> On 01/11/2022 16:30, Andy Shevchenko wrote:
> > On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:
> >> From: Luca Ceresoli <[email protected]>
> >>
> >> 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.

First of all, thank you for bringing this work on!

> > ...
> >
> >> +I2C ADDRESS TRANSLATOR (ATR)
> >> +M: Luca Ceresoli <[email protected]>
> >
> > Hmm... Are you going to maintain this? Or Review? Why not?
>
> We haven't discussed with Luca if he wants to maintain this (this is
> mostly his code). But, indeed, I should add my name there.

I think at this point you are probably in a better position to be the
maintainer, but I'm OK with being listed here as reviewer (R:).

Ah, would you please use my bootlin dot com address here?

> I'm not arguing against any of the comments you've made, I think they
> are all valid, but I want to point out that many of them are in a code
> copied from i2c-mux.
>
> Whether there's any value in keeping i2c-mux and i2c-atr similar in
> design/style... Maybe not.

Tomi is right, when I wrote this initially I tried to keep it as
similar as possible to i2c-mux.c. No problem in deviating from that
wherever it makes sense.

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

2022-11-07 13:57:04

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 0/8] i2c-atr and FPDLink

On 07/11/2022 13:48, Luca Ceresoli wrote:

>> In fact, I'm thinking that it might be better to just drop the i2c-atr
>> driver, and add the support directly to the FPDlink drivers. But that
>> could mean possibly duplicating the same code for other deser/ser
>> architectures, so I have kept the i2c-atr driver for now.
>
> Indeed I think the ROHM serdes chips do have an address translation
> feature that works pretty much like the TI ones, and the ATR should be
> cleanly reusable across the two brands. The ATR code might be
> simplified to just provide helpers for common code maybe, but I'd
> rather avoid code duplication.

I agree. The reason I'm wondering about this is the fact that the i2c
slave side (deser) is in a different IC and driver than the i2c master
side and driver (ser). That makes it quite different from the i2c-mux
(at least how the i2c-mux exists now).

Tomi


2022-11-07 14:49:33

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [PATCH v4 0/8] i2c-atr and FPDLink

On 11/1/22 15:20, Tomi Valkeinen wrote:
> Hi,
>
> Intro
> -----
>
> This is, kind of, v4 of Luca's i2c-atr and FPDLink series, v3 of which
> you can find from:
>
> https://lore.kernel.org/all/[email protected]/
>

//snip

First of all, I like this series.

There is just one thing that slightly bothers me here. It is putting
everything the deserializer has inside one driver. I think I mentioned
this already earlier - but to me these devices seem like MFD ICs.

Hence, my attempt to support one of SerDes devices resembling the TI
SerDes devices had following split. MFD I2C drivers for SER and DES. Own
platform drivers for DES ATR, V4L2, pinctrl/GPIO. Also own platform
drivers for SER V4L2, pinctrl/GPIO.

Below you can see the relevant pieces of simplified (pseudo-)code
explaining the (optional?) design which attempts creating somewhat
smaller and re-usable individual drivers for DES/SER blocks. Please, do
not treat this as a requirement - please treat it as a food for thoughts ;)


DES MFD-core: as I2C - device. Registers regmap and IRQ-chip for
sub-devices. Creates the sub-devices for DES. Parses the required SerDes
topology from device-tree and makes the minimum required configuration
to get the actual link (FPDLink in TI's case) run in a level that I2C
connection is possible when ATR is configured. (Actually, I noticed this
could be put in ATR driver, but in my opinion it's better to parse the
overall DT in MFD and put it in the MFD driver-data so all sub-devices
can get the parsed bits of DT data from parent w/o duplicating the
parsing code).

static struct mfd_cell bu18rm84_mfd_cells[] = {
{ .name = "bu18rm84-i2c", },
{ .name = "bu18rm84-video", },
{ .name = "bu18rm84-pinctrl", },
};

...

static int link_init(struct device *dev, struct regmap *regmap,
struct bu18rm84_core_data *data,
struct bu18rm84_ser_conf *ser, int ser_id)
{
...
}

static int prepare_hw_for_subdevs(struct device *dev, struct regmap *regmap,
struct bu18rm84_core_data *data)
{
int ret, i;

ret = bu18rm84_common_init_pre(dev, regmap, data);
if (ret)
return ret;

for (i = 0; i < BU18RM84_MAX_NUM_SER_NODES; i++) {
if (data->ser[i].ser_ep) {
/*
* We initialize link between DES => SERsin MFD so
* the sub-devices have working link to SER devices.
*/
ret = link_init(dev, regmap, data, &data->ser[i], i);
if (ret)
return ret;
}
}

...

return ret;
}

static int connected_devices(struct device *dev, struct
bu18rm84_core_data *data)
{
int i;

for (i = 0; i < BU18RM84_MAX_NUM_SER_NODES; i++) {
/*
* TODO: See where/when/if we should drop the reference to
* the node
*/
data->ser[i].ser_ep = fwnode_graph_get_endpoint_by_id(data->fw, i,
0, 0);
if (data->ser[i].ser_ep) {
dev_dbg(dev, "Found node for SER %d\n", i);
data->num_ser++;
}
}


for (; i < BU18RM84_MAX_NUM_SER_NODES + BU18RM84_MAX_NUM_CSI_NODES; i++) {
int csi = i - BU18RM84_MAX_NUM_SER_NODES;

data->csi_ep[csi] = fwnode_graph_get_endpoint_by_id(data->fw, i,
0, 0);
if (data->csi_ep[csi])
data->num_csi++;
}
dev_dbg(dev, "found %d ser, %d csi\n", data->num_ser, data->num_csi);

return (!data->num_ser) ? -ENODEV : 0;
}

static int bu18rm84_i2c_probe(struct i2c_client *i2c)
{
data = devm_kzalloc(&i2c->dev, sizeof(*data), GFP_KERNEL);
regmap = devm_regmap_init_i2c(i2c, &bu18rm84_regmap);

ret = connected_devices(&i2c->dev, data);

/*
* We need to set-up the link between DES and SER(s) before
* the I2C to SER(s) is operational. Sub-devices like the SER GPIO, pinmux
* or media do need this I2C so we'd better to prepare things up-to-the
* point where I2C works prior registering the subdev cells.
*
* Unfortunately, this means the MFD needs to go and read & understand
* the DT for SER topology - and do some magic settings to get things
* up & running.
*/
ret = prepare_hw_for_subdevs(&i2c->dev, regmap, data);

ret = devm_regmap_add_irq_chip(&i2c->dev, regmap, i2c->irq,
IRQF_SHARED, 0, &bu18rm84_irq_chip,
&irq_data);

ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO,
bu18rm84_mfd_cells,
ARRAY_SIZE(bu18rm84_mfd_cells),
NULL, 0, regmap_irq_get_domain(irq_data));

}

===============

DES ATR platform driver: Kicked by DES MFD. Uses I2C-atr to configure
address translation. Creates SER devices and creates the I2C adapter for
the remote SER buses. This in-turn launches SER MFD driver + remote I2C
slave devices. And yes. I think the remote I2C slave devices must not be
created before SER side link configurations are done by SER MFD driver.
Thanks Tomi - I guess I've been lucky this far (or there is something I
didn't spot quite yet ;] )

static int bu18rm84_atr_attach_client(struct i2c_atr *atr, u32 chan_id,
const struct i2c_board_info *info,
const struct i2c_client *client,
u16 *alias_id)
{
/* Configure the ATR for slave to DES registers */
}

static void bu18rm84_atr_detach_client(struct i2c_atr *atr, u32 chan_id,
const struct i2c_client *client)
{
/* Clear the configuration for the ATR for slave at DES registers */
}

static const struct i2c_atr_ops bu18rm84_atr_ops = {
.attach_client = bu18rm84_atr_attach_client,
.detach_client = bu18rm84_atr_detach_client,
};

static int bu18rm84_populate_ser(struct bu18rm84_i2c_atr *data, u32 *addrs,
int num_addrs, struct fwnode_handle *des_node,
int ser_id)
{
/*
* Configure the I2C alias for SER devices in DES registers based on
* reg-names in the DT
*/
idx = fwnode_property_match_string(des_node, "reg-names",
ser_names[ser_id]);
...
}

static int setup_ser_i2c(struct bu18rm84_i2c_atr *data, int ser_id,
struct fwnode_handle *ser_ep)
{
int ret;
const char *name;
struct i2c_client **new = &data->ser_client[ser_id];
struct i2c_board_info ser_info = { 0 };

ser_info.fwnode = fwnode_get_named_child_node(ser_ep, "remote-chip");
name = fwnode_get_name(ser_info.fwnode);

ser_info.addr = data->ser_alias[ser_id];
*new = i2c_new_client_device(data->parent_i2c->adapter, &ser_info);

/* Add adapter to correctly enable i2c to devices behind SER */
/* We need to have ATR done before this */
ret = i2c_atr_add_adapter(data->atr, ser_id);
...

return ret;
}

static int bu18rm84_setup_ser_i2cs(const struct bu18rm84_core_data *core,
struct bu18rm84_i2c_atr *data)
{
for (i = 0; i < BU18RM84_MAX_NUM_SER_NODES; i++) {
if (core->ser[i].ser_ep) {
ret = setup_ser_i2c(data, i, core->ser[i].ser_ep);
...
}
}
}

static int bu18rm84_atr_probe(struct platform_device *pdev)
{
parent = dev->parent;
core = dev_get_drvdata(parent);

data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);

data->parent_i2c = to_i2c_client(parent);

data->regmap = dev_get_regmap(parent, NULL);

dev_set_drvdata(&pdev->dev, data);

/* Go read the I2C addresses from DT */
ret = bu18rm84_i2c_parse_dt(data, core);

/*
* Create device for SER. We won't add SER to ATR in order to avoid
* messing with the SER specific aliases in ATR.
*
* Also, add adapter.
*/
for (i = 0; i < BU18RM84_MAX_NUM_SER_NODES; i++) {
if (core->ser[i].ser_ep) {
ret = bu18rm84_populate_ser(data, &addrs[0], num_addrs,
core->fw, i);
}
}

data->atr = i2c_atr_new(data->parent_i2c->adapter, parent,
&bu18rm84_atr_ops, 4);

i2c_atr_set_clientdata(data->atr, data);

return bu18rm84_setup_ser_i2cs(core, data);

=============================

Ser MFD (i2c)driver. Kicked by DES ATR platform device when creating the
I2C devices with the SER MFD fwnode. Finalizes the SER <=> DES I2C
connection and kicks up the SER slave devices for DES pinctrl / V4L2 or
others.


static struct mfd_cell bu18tm41_mfd_cells[] = {
{ .name = "bu18tm41-video", }, /* Video forwarding */
{ .name = "bu18tm41-pinctrl", }, /* pinmux, pinconf and gpio */
};

static int bu18tm41_conf_ser(struct device *dev, struct regmap *regmap,
struct bu18tm41_core *core)
{
fw = dev_fwnode(dev);

ret = fwnode_property_read_u64(fw, "compulsory stuff to get link up",
&foo);

ret = bu18tm41_enable_link( ... );

...

return 0;
}

static int bu18tm41_i2c_probe(struct i2c_client *i2c)
{
regmap = devm_regmap_init_i2c(i2c, &bu18tm41_regmap);
core = devm_kzalloc(&i2c->dev, sizeof(*core), GFP_KERNEL);
dev_set_drvdata(&i2c->dev, core);

/*
* Do the minimum required link config on SER config to get I2C
* between DES <=> SER up
*
* Unfortunately, this means the MFD needs to do some magic settings
* to get things up & running.
*/
ret = bu18tm41_conf_ser(&i2c->dev, regmap, core);

ret = devm_mfd_add_devices(&i2c->dev, PLATFORM_DEVID_AUTO,
bu18tm41_mfd_cells,
ARRAY_SIZE(bu18tm41_mfd_cells),
NULL, 0, /*regmap_irq_get_domain(irq_data) */ NULL);

return ret;
}

===========================

Des media platform driver. V4L2/media stuff.

...

static int bu18rm84_media_probe(struct platform_device *pdev)
{

core = dev_get_drvdata(dev->parent);

data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);

data->dev = dev;
data->parent_i2c = to_i2c_client(dev->parent);
data->regmap = dev_get_regmap(dev->parent, NULL);

platform_set_drvdata(pdev, data);


ret = bu18rm84_parse_csi_dt(core, data);

ret = bu18rm84_config_csi2(data);

ret = bu18rm84_add_ser_data(core, data);

...

/* Create the V4L2 subdevices */
ret = bu18rm84_create_subdev(core, data);
if (ret)
goto err_subdev;

...
}

SER video, Des/Ser pinctrl/GPIO platform drivers:
...
(Like usual)


I think it was Tomi who asked me the benefit of using MFD. In some cases
the digital interface towards pinctrl/GPIO or other functional blocks in
SER/DES is re-used from other products - or the blocks are re-used on
other products. Separating them in own platform-drivers is a nice way to
re-use drivers and avoid code duplication.

Whether it is worth or correct can be evaluated by brighter minds :) As
I said, I am in a no way demanding a change if you see the current
proposal being better! I am just presenting an optional way to feed your
thoughts ;)

Ps. Sorry for _very_ stripped down code in this explanation. I am
currently not allowed to publish any details of the ICs these drivers
are written for.

Yours
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~

2022-11-07 16:04:20

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 0/8] i2c-atr and FPDLink

Hi Matti,

On 07/11/2022 16:37, Vaittinen, Matti wrote:

I only had time to have a brief look at your code, but I have a few
quick questions.

> I think it was Tomi who asked me the benefit of using MFD. In some cases
> the digital interface towards pinctrl/GPIO or other functional blocks in
> SER/DES is re-used from other products - or the blocks are re-used on
> other products. Separating them in own platform-drivers is a nice way to
> re-use drivers and avoid code duplication.

Is there anything that prevents us (or makes it difficult) from
refactoring a "monolithic" driver into an MFD later? If we see such IP
re-use, can we then move to an MFD?

I admit I have never written an MFD driver (but I have hacked with a few
years back). As I see it, the "subcomponents" in FPDLink ICs are more or
less tied to the FPDLink. It's not like they're independent. Compare to,
for example, a PMIC which provides regulators and GPIOs, and possibly
the only shared part between those two features are the pins.

So, I think I'm still wondering about the benefit...

In the current version I have the deser driver supporting UB960 and
UB9702. I guess I could split those into separate drivers, and using MFD
I could share lot of code between them. But I still can't see why that's
better than having both UB960 and UB9702 in the same driver (and, if the
amount of supported devices increases, perhaps dividing some parts to
separate files and using function points for a few things).

The benefit would be more obvious if there was some other type of IC
that uses the same IP subcomponents. Maybe the display side FPDLink
devices are such, but I have never done a deep dive in their
documentation. And, even then, I think I still have the question: can we
just move to an MFD later when the need comes?

Also, isn't the use or non-use of MFD strictly a driver private thing,
it should not affect any shared parts or shared designs? In other words,
if you have your ROHM hat on, why do you care how the UB9xx driver is
structured internally? ;)

Tomi


2022-11-07 18:23:53

by Matti Vaittinen

[permalink] [raw]
Subject: Re: [PATCH v4 0/8] i2c-atr and FPDLink

Evening Tomi,

On 11/7/22 17:37, Tomi Valkeinen wrote:
> Hi Matti,
>
> On 07/11/2022 16:37, Vaittinen, Matti wrote:
>
> I only had time to have a brief look at your code, but I have a few
> quick questions.
>
>> I think it was Tomi who asked me the benefit of using MFD. In some cases
>> the digital interface towards pinctrl/GPIO or other functional blocks in
>> SER/DES is re-used from other products - or the blocks are re-used on
>> other products. Separating them in own platform-drivers is a nice way to
>> re-use drivers and avoid code duplication.
>
> Is there anything that prevents us (or makes it difficult) from
> refactoring a "monolithic" driver into an MFD later? If we see such IP
> re-use, can we then move to an MFD?

I haven't done such conversion. I think the work for doing the
conversion at later phase is roughly same it would be now. However,
synchronizing such change with related subsystem trees might be some
extra work.

> I admit I have never written an MFD driver (but I have hacked with a few
> years back). As I see it, the "subcomponents" in FPDLink ICs are more or
> less tied to the FPDLink. It's not like they're independent. Compare to,
> for example, a PMIC which provides regulators and GPIOs, and possibly
> the only shared part between those two features are the pins.

I think that in SerDes driver case the benefit may come from re-use and
clarity. Smaller drivers tend to be easier to comprehend, although I
liked the way you had divided the drivers in sections.

> So, I think I'm still wondering about the benefit...
>
> In the current version I have the deser driver supporting UB960 and
> UB9702. I guess I could split those into separate drivers,

I wouldn't break the driver per IC. If the ICs are similar enough to be
nicely handled by same driver, then they probably should.

>
> The benefit would be more obvious if there was some other type of IC
> that uses the same IP subcomponents.

Yes. Same or similar subcomponents. This indeed is the benefit I see. I
don't know if TI could use same - say GPIO - control logic on another
type of device? Or, maybe separating the logic could guide one to use
some generic stuff like regmap_gpio driver? And finally, submitting
small platform drivers via respective subsystems can yield better review ;)

>
> Also, isn't the use or non-use of MFD strictly a driver private thing,

I am tempted to say "yes", but when giving it a thought - it's really
not fully that. Splitting a driver to subdrivers can allow re-use of
subcomponents by unrelated ICs. OTOH, always stuffing everything in a
driver "because it is driver internal decision" can lead to code
duplication and bloat.

> it should not affect any shared parts or shared designs? In other words,
> if you have your ROHM hat on, why do you care how the UB9xx driver is
> structured internally? ;)

As I wrote:
> > Please, do not treat this as a requirement - please treat it as a
food for thoughts ;)

Eg, I am not trying to tell you how to do the UB9xx drivers. I just
think that considering another design _might_ result more optimal design
- but I do leave the decision to you who know this area better than I do.

Yours
-- Matti

--
Matti Vaittinen
Linux kernel developer at ROHM Semiconductors
Oulu Finland

~~ When things go utterly wrong vim users can always type :help! ~~

2022-11-11 16:48:50

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

Hi Tomi,

On Thu, 3 Nov 2022 13:50:43 +0200
Tomi Valkeinen <[email protected]> wrote:

> Hi Rob,
>
> On 02/11/2022 19:26, Rob Herring wrote:
> > On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
> >> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
> >>
> >> Signed-off-by: Tomi Valkeinen <[email protected]>
> >> ---
> >> .../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++++++++++++++++++
> >> 1 file changed, 392 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..4456d9b3e2c7
> >> --- /dev/null
> >> +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
> >> @@ -0,0 +1,392 @@
> >> +# 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 <[email protected]>
> >> +
> >> +description: |
> >
> > Don't need '|'
>
> Hmm, ok... But why does that work? I can only find yaml examples for
> multi-line with either | or >.
>
> >> + 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
> >> +
> >> + reg-names:
> >> + items:
> >> + - const: main
> >
> > 'reg-names' is not all that useful with only 1 entry.
>
> True.
>
> >> +
> >> + 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:
> >
> > Something common or could be? If not, then needs a vendor prefix.
>
> I'll have to think about this. It is related to the i2c-atr, so I think
> it might be a common thing.
>
> >> + $ref: /schemas/types.yaml#/definitions/uint16-array
> >> + description:
> >> + i2c alias pool for remote devices.
> >
> > Needs a better description. What's an 'alias pool'?
>
> Right.
>
> "i2c alias pool is a pool of i2c addresses on the main i2c bus that can
> be used to access the remote peripherals. 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."

Good description, but I think re-adding this sentence from my v2 would
be useful:

list of I2C addresses that are known to be available on the"local"
(SoC-to-deser) I2C bus

> > 0-0xffff are valid values?
>
> They are i2c addresses, and linux i2c uses u16 for addresses. Then
> again, the fpdlink devices only support 7-bit addresses, so maybe this
> could be an uint8 array. I am not sure what's the best way to define this.

In DT the Linux implementation is irrelevant. Also if we want the ATR
code to be generic we must prepare DT bindings for future devices.
Thus, being these I2C addresses, they could be 7 or 10 bits, thus the
need for at least 16 bits.

All the above will become irrelevant in case the alias pool will
disappear, though.

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

2022-11-11 16:55:09

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

Hello Tomi, Matti, Wolfram,

On Thu, 3 Nov 2022 14:32:02 +0200
Tomi Valkeinen <[email protected]> wrote:

> On 03/11/2022 14:13, Vaittinen, Matti wrote:
> > On 11/3/22 13:50, Tomi Valkeinen wrote:
> >> Hi Rob,
> >>
> >> On 02/11/2022 19:26, Rob Herring wrote:
> >>> On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
> >>>> +
> >>>> +  i2c-alias-pool:
> >>>
> >>> Something common or could be? If not, then needs a vendor prefix.
> >>
> >> I'll have to think about this. It is related to the i2c-atr, so I think
> >> it might be a common thing.
> >
> > I'd say this should be common. Where the i2c-atr properties should live
> > is another question though. If the I2C-atr stays as a genericly usable
> > component - then these bindings should be in a file that can be
> > referenced by other I2C-atr users (like the UB960 here).
>
> Yep. All the links, link, serializer and alias nodes/properties are new
> things here, and I guess these could be used by other deser-ser systems.
> That said, I don't have any experience with other systems.

The i2c-alias-pool was discussed during the RFC,v2 review [1] and it
was agreed that it should be generic. The same principle should apply
to the other ATR properties.

That said, at some point it was also decided that the alias pool should
just be ditched in favor of an automatic selection of an unused address
by the i2c core [2] [3]. Maybe that idea has changed, definitely some
i2c core things needed to be omdified for it to happen, but overall I'm
still convinced automatic assignment without a pool was a good idea.

> >>>> +
> >>>> +          i2c-alias:
> >>>
> >>> Vendor prefix.
> >>>
> >>>> +            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:
> >>>
> >>> Vendor prefix. And so on... >
> >> Yes, I totally missed these.
> >
> >
> > I think the i2c-alias might need to be common as well?
>
> Perhaps...
>
> I was also thinking that the serializer addresses could be taken from
> the i2c-alias-pool. But maybe that's not a good idea, as the
> serializer-access and remote-peripheral-access are a bit different (e.g.
> no ATR when accessing the serializer).
>
> And I was thinking that, at least here, the alias addresses can be
> "anything", so they could be reserved dynamically at runtime, without
> any predefined aliases. But that might be a bit confusing to the user.

I think the serialized alias selection is in an intermediate situation
between the deser and the remove chips. For the deser there is a
physical address, nothing strange. For the remote chips, lots of things
could happen, including having several chips. The serializer is in
known quantity (one per connector) and the fact that it is "on the
remote bus" is also somewhat false as it does create the remote bus
itself. Thus for me it would make sense to keep the current structure
(which is quite simple) but mine in not a strong position.

[1]
https://lore.kernel.org/lkml/CAL_JsqKDkixeDJJVxbzWebD6nqMHyk6QqDGSKrQho0THjLdmKQ@mail.gmail.com/

[2] https://lore.kernel.org/lkml/20190903093455.GD1020@kunai/

[3] https://lucaceresoli.net/plumbers-i2c-bof/

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

2022-11-11 17:19:32

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

Hi Tomi,

On Tue, 1 Nov 2022 15:20:27 +0200
Tomi Valkeinen <[email protected]> wrote:

> Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>

...

> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> +
> + i2c {
> + clock-frequency = <400000>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + deser@3d {
> + compatible = "ti,ds90ub960-q1";
> +
> + reg-names = "main";
> + 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>;
> +
> + rx-mode = <0>;
> + };
> + };
> +
> + /* Port 0, Camera 1 */

I guess you mean "Port 1" here.

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

2022-12-08 08:02:10

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support

On 07/11/2022 13:40, Luca Ceresoli wrote:
> On Fri, 4 Nov 2022 13:59:06 +0200
> Tomi Valkeinen <[email protected]> wrote:
>
>> Hi Andy,
>>
>> On 01/11/2022 16:30, Andy Shevchenko wrote:
>>> On Tue, Nov 01, 2022 at 03:20:26PM +0200, Tomi Valkeinen wrote:
>>>> From: Luca Ceresoli <[email protected]>
>>>>
>>>> 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.
>
> First of all, thank you for bringing this work on!
>
>>> ...
>>>
>>>> +I2C ADDRESS TRANSLATOR (ATR)
>>>> +M: Luca Ceresoli <[email protected]>
>>>
>>> Hmm... Are you going to maintain this? Or Review? Why not?
>>
>> We haven't discussed with Luca if he wants to maintain this (this is
>> mostly his code). But, indeed, I should add my name there.
>
> I think at this point you are probably in a better position to be the
> maintainer, but I'm OK with being listed here as reviewer (R:).
>
> Ah, would you please use my bootlin dot com address here?

Ok, I've added your bootlin address as R:

Tomi

2022-12-08 10:05:28

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960

Hi Luca,

On 11/11/2022 18:26, Luca Ceresoli wrote:
> Hello Tomi, Matti, Wolfram,
>
> On Thu, 3 Nov 2022 14:32:02 +0200
> Tomi Valkeinen <[email protected]> wrote:
>
>> On 03/11/2022 14:13, Vaittinen, Matti wrote:
>>> On 11/3/22 13:50, Tomi Valkeinen wrote:
>>>> Hi Rob,
>>>>
>>>> On 02/11/2022 19:26, Rob Herring wrote:
>>>>> On Tue, Nov 01, 2022 at 03:20:27PM +0200, Tomi Valkeinen wrote:
>>>>>> +
>>>>>> +  i2c-alias-pool:
>>>>>
>>>>> Something common or could be? If not, then needs a vendor prefix.
>>>>
>>>> I'll have to think about this. It is related to the i2c-atr, so I think
>>>> it might be a common thing.
>>>
>>> I'd say this should be common. Where the i2c-atr properties should live
>>> is another question though. If the I2C-atr stays as a genericly usable
>>> component - then these bindings should be in a file that can be
>>> referenced by other I2C-atr users (like the UB960 here).
>>
>> Yep. All the links, link, serializer and alias nodes/properties are new
>> things here, and I guess these could be used by other deser-ser systems.
>> That said, I don't have any experience with other systems.
>
> The i2c-alias-pool was discussed during the RFC,v2 review [1] and it
> was agreed that it should be generic. The same principle should apply
> to the other ATR properties.
>
> That said, at some point it was also decided that the alias pool should
> just be ditched in favor of an automatic selection of an unused address
> by the i2c core [2] [3]. Maybe that idea has changed, definitely some
> i2c core things needed to be omdified for it to happen, but overall I'm
> still convinced automatic assignment without a pool was a good idea.

Yes, the serializer and the remote peripheral i2c aliases can be
dynamically reserved at runtime, so the i2c-alias-pool and the i2c-alias
are, in that sense, not needed.

I haven't looked at this in depth yet, but reading the references you
gave, it sounds like it's not quite clear what addresses are available
and what are not.

On the other hand, is dynamic i2c address reservation something that the
users expect to happen? All i2c devices I have used have always had a
fixed address in the DT, even if at times the devices may support
choosing between a few different addresses.

Keeping with that tradition, would it be best to just use fixed i2c
aliases, defined in the DT, for the serializers and the remote
peripherals? In the current series this is already the case for
serializers (with i2c-alias property), but we could do something similar
for the remote peripherals.

Tomi