2023-01-18 13:21:34

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 0/7] i2c-atr and FPDLink

Hi,

You can find the v6 from:

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

Main changes:

* i2c-atr: Use bus notifier. This allows us to drop the patch that adds
the attach_client/detach_client callbacks. On the downside, it removes
the option for error handling if the translation setup fails, and also
doesn't provide us the pointer to the i2c_board_info. I belive both
are acceptable downsides.

* Use fwnode in the fpdlink drivers instead of OF

* Addressed all the review comments (I hope)

* Lots of cosmetic or minor fixes which I came up while doing the fwnode
change

Diff to v6 below.

Tomi

Luca Ceresoli (1):
i2c: add I2C Address Translator (ATR) support

Tomi Valkeinen (6):
dt-bindings: media: add TI DS90UB913 FPD-Link III Serializer
dt-bindings: media: add TI DS90UB953 FPD-Link III Serializer
dt-bindings: media: add TI DS90UB960 FPD-Link III Deserializer
media: i2c: add DS90UB960 driver
media: i2c: add DS90UB913 driver
media: i2c: add DS90UB953 driver

.../bindings/media/i2c/ti,ds90ub913.yaml | 133 +
.../bindings/media/i2c/ti,ds90ub953.yaml | 134 +
.../bindings/media/i2c/ti,ds90ub960.yaml | 425 ++
Documentation/i2c/index.rst | 1 +
Documentation/i2c/muxes/i2c-atr.rst | 83 +
MAINTAINERS | 16 +
drivers/i2c/Kconfig | 9 +
drivers/i2c/Makefile | 1 +
drivers/i2c/i2c-atr.c | 547 +++
drivers/media/i2c/Kconfig | 47 +
drivers/media/i2c/Makefile | 3 +
drivers/media/i2c/ds90ub913.c | 848 ++++
drivers/media/i2c/ds90ub953.c | 1576 ++++++
drivers/media/i2c/ds90ub960.c | 4254 +++++++++++++++++
include/linux/i2c-atr.h | 117 +
include/media/i2c/ds90ub9xx.h | 16 +
16 files changed, 8210 insertions(+)
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

Interdiff against v6:
diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
index 664799ae55be..1ba22450cdba 100644
--- a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml
@@ -88,7 +88,7 @@ properties:
- 4 # CSI2 NON-SYNC
description:
FPD-Link Input Mode. This should reflect the hardware and the
- default mode of the connected camera module.
+ default mode of the connected device.

ti,cdr-mode:
$ref: /schemas/types.yaml#/definitions/uint32
@@ -97,7 +97,7 @@ properties:
- 1 # FPD-Link IV
description:
FPD-Link CDR Mode. This should reflect the hardware and the
- default mode of the connected camera module.
+ default mode of the connected device.

ti,strobe-pos:
$ref: /schemas/types.yaml#/definitions/int32
@@ -212,6 +212,14 @@ properties:
required:
- data-lanes

+ required:
+ - port@0
+ - port@1
+ - port@2
+ - port@3
+ - port@4
+ - port@5
+
required:
- compatible
- reg
@@ -265,6 +273,16 @@ examples:
};
};

+ /* Port 2, unused */
+ port@2 {
+ reg = <2>;
+ };
+
+ /* Port 3, unused */
+ port@3 {
+ reg = <3>;
+ };
+
/* Port 4, CSI-2 TX */
port@4 {
reg = <4>;
@@ -274,6 +292,11 @@ examples:
remote-endpoint = <&csi2_phy0>;
};
};
+
+ /* Port 5, unused */
+ port@5 {
+ reg = <5>;
+ };
};

links {
diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c
index 0668c98886ad..1d43cf3824eb 100644
--- a/drivers/i2c/i2c-atr.c
+++ b/drivers/i2c/i2c-atr.c
@@ -80,6 +80,8 @@ struct i2c_atr {
struct mutex lock;
int max_adapters;

+ struct notifier_block i2c_nb;
+
struct i2c_adapter *adapter[];
};

@@ -272,7 +274,6 @@ static const struct i2c_lock_operations i2c_atr_lock_ops = {
};

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;
@@ -285,8 +286,7 @@ static int i2c_atr_attach_client(struct i2c_adapter *adapter,
if (!c2a)
return -ENOMEM;

- ret = atr->ops->attach_client(atr, chan->chan_id, info, client,
- &alias_id);
+ ret = atr->ops->attach_client(atr, chan->chan_id, client, &alias_id);
if (ret)
goto err_free;

@@ -318,15 +318,53 @@ static void i2c_atr_detach_client(struct i2c_adapter *adapter,
}
}

-static const struct i2c_attach_operations i2c_atr_attach_ops = {
- .attach_client = i2c_atr_attach_client,
- .detach_client = i2c_atr_detach_client,
-};
+static int i2c_atr_bus_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *device)
+{
+ struct i2c_atr *atr = container_of(nb, struct i2c_atr, i2c_nb);
+ struct device *dev = device;
+ struct i2c_client *client;
+ u32 chan_id;
+ int ret;
+
+ client = i2c_verify_client(dev);
+ if (!client)
+ return NOTIFY_DONE;
+
+ /* Is the client in one of our adapters? */
+ for (chan_id = 0; chan_id < atr->max_adapters; ++chan_id) {
+ if (client->adapter == atr->adapter[chan_id])
+ break;
+ }
+
+ if (chan_id == atr->max_adapters)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ ret = i2c_atr_attach_client(client->adapter, client);
+ if (ret)
+ dev_err(atr->dev,
+ "Failed to attach remote client '%s': %d\n",
+ dev_name(dev), ret);
+ break;
+
+ case BUS_NOTIFY_DEL_DEVICE:
+ i2c_atr_detach_client(client->adapter, client);
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}

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;
+ int ret;

if (max_adapters > ATR_MAX_ADAPTERS)
return ERR_PTR(-EINVAL);
@@ -352,12 +390,20 @@ struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev,
atr->algo.smbus_xfer = i2c_atr_smbus_xfer;
atr->algo.functionality = i2c_atr_functionality;

+ atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call;
+ ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb);
+ if (ret) {
+ mutex_destroy(&atr->lock);
+ return ERR_PTR(ret);
+ }
+
return atr;
}
EXPORT_SYMBOL_NS_GPL(i2c_atr_new, I2C_ATR);

void i2c_atr_delete(struct i2c_atr *atr)
{
+ bus_unregister_notifier(&i2c_bus_type, &atr->i2c_nb);
mutex_destroy(&atr->lock);
}
EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, I2C_ATR);
@@ -400,7 +446,6 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
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));
@@ -423,6 +468,8 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
fwnode_handle_put(atr_node);
}

+ atr->adapter[chan_id] = &chan->adap;
+
ret = i2c_add_adapter(&chan->adap);
if (ret) {
dev_err(dev, "failed to add atr-adapter %u (error=%d)\n",
@@ -442,8 +489,6 @@ int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,

dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap));

- atr->adapter[chan_id] = &chan->adap;
-
return 0;

err_fwnode_put:
@@ -468,14 +513,15 @@ void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)

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);
+
+ atr->adapter[chan_id] = NULL;
+
fwnode_handle_put(fwnode);
mutex_destroy(&chan->orig_addrs_lock);
kfree(chan->orig_addrs);
diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c
index 8a0888ba89e5..7539b0740351 100644
--- a/drivers/i2c/i2c-core-base.c
+++ b/drivers/i2c/i2c-core-base.c
@@ -916,7 +916,6 @@ int i2c_dev_irq_from_resources(const struct resource *resources,
struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
- const struct i2c_attach_operations *attach_ops;
struct i2c_client *client;
int status;

@@ -968,24 +967,15 @@ i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *inf
}
}

- attach_ops = adap->attach_ops;
-
- if (attach_ops && attach_ops->attach_client &&
- attach_ops->attach_client(adap, info, client))
- goto out_remove_swnode;
-
status = device_register(&client->dev);
if (status)
- goto out_detach_client;
+ goto out_remove_swnode;

dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n",
client->name, dev_name(&client->dev));

return client;

-out_detach_client:
- if (attach_ops && attach_ops->detach_client)
- attach_ops->detach_client(adap, client);
out_remove_swnode:
device_remove_software_node(&client->dev);
out_err_put_of_node:
@@ -1007,18 +997,9 @@ EXPORT_SYMBOL_GPL(i2c_new_client_device);
*/
void i2c_unregister_device(struct i2c_client *client)
{
- const struct i2c_attach_operations *attach_ops;
- struct i2c_adapter *adap;
-
if (IS_ERR_OR_NULL(client))
return;

- adap = client->adapter;
- attach_ops = adap->attach_ops;
-
- if (attach_ops && attach_ops->detach_client)
- 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/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 29594c724307..0590312ec751 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1602,40 +1602,40 @@ endmenu
menu "Video serializers and deserializers"

config VIDEO_DS90UB913
- tristate "TI DS90UB913 Serializer"
+ tristate "TI DS90UB913 FPD-Link III Serializer"
depends on OF && I2C && VIDEO_DEV
+ select I2C_ATR
select MEDIA_CONTROLLER
- select VIDEO_V4L2_SUBDEV_API
- select V4L2_FWNODE
- select REGMAP_I2C
select OF_GPIO
- select I2C_ATR
+ select REGMAP_I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
help
Device driver for the Texas Instruments DS90UB913
FPD-Link III Serializer.

config VIDEO_DS90UB953
- tristate "TI DS90UB953 Serializer"
+ tristate "TI FPD-Link III/IV CSI-2 Serializers"
depends on OF && I2C && VIDEO_DEV
+ select I2C_ATR
select MEDIA_CONTROLLER
- select VIDEO_V4L2_SUBDEV_API
- select V4L2_FWNODE
- select REGMAP_I2C
select OF_GPIO
- select I2C_ATR
+ select REGMAP_I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
help
Device driver for the Texas Instruments DS90UB953
FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer.

config VIDEO_DS90UB960
- tristate "TI DS90UB960 Deserializer"
+ tristate "TI FPD-Link III/IV Deserializers"
depends on OF && I2C && VIDEO_DEV
+ select I2C_ATR
select MEDIA_CONTROLLER
- select VIDEO_V4L2_SUBDEV_API
- select V4L2_FWNODE
- select REGMAP_I2C
select OF_GPIO
- select I2C_ATR
+ select REGMAP_I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
help
Device driver for the Texas Instruments DS90UB960
FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
index 0a60afb09cd3..befa78128a9a 100644
--- a/drivers/media/i2c/ds90ub913.c
+++ b/drivers/media/i2c/ds90ub913.c
@@ -5,19 +5,19 @@
* Based on a driver from Luca Ceresoli <[email protected]>
*
* Copyright (c) 2019 Luca Ceresoli <[email protected]>
- * Copyright (c) 2022 Tomi Valkeinen <[email protected]>
+ * Copyright (c) 2023 Tomi Valkeinen <[email protected]>
*/

#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/fwnode.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/property.h>
#include <linux/regmap.h>

#include <media/i2c/ds90ub9xx.h>
@@ -41,8 +41,9 @@
#define UB913_REG_GENERAL_CFG_PCLK_RISING BIT(0)

#define UB913_REG_MODE_SEL 0x05
-#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4)
#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5)
+#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4)
+#define UB913_REG_MODE_SEL_MODE_MASK GENMASK(3, 0)

#define UB913_REG_CRC_ERRORS_LSB 0x0a
#define UB913_REG_CRC_ERRORS_MSB 0x0b
@@ -67,7 +68,6 @@ struct ub913_data {
struct clk *clkin;

struct gpio_chip gpio_chip;
- char gpio_chip_name[64];

struct v4l2_subdev sd;
struct media_pad pads[2];
@@ -75,11 +75,10 @@ struct ub913_data {
struct v4l2_async_notifier notifier;

struct v4l2_subdev *source_sd;
+ u16 source_sd_pad;

u64 enabled_source_streams;

- struct device_node *tx_ep_np;
-
struct clk_hw *clkout_clk_hw;

struct ds90ub9xx_platform_data *plat_data;
@@ -132,20 +131,14 @@ 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;
+ unsigned int reg_idx = offset / 2;
+ unsigned int field_idx = offset % 2;
+
+ return 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));
}

static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
@@ -172,20 +165,16 @@ static int ub913_gpiochip_probe(struct ub913_data *priv)
/* 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->label = dev_name(dev);
gc->parent = dev;
gc->owner = THIS_MODULE;
gc->base = -1;
- gc->can_sleep = 1;
+ gc->can_sleep = true;
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);
@@ -219,19 +208,14 @@ static int ub913_enable_streams(struct v4l2_subdev *sd,
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]);
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
+ UB913_PAD_SINK,
+ &streams_mask);

- ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index,
+ ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
sink_streams);
if (ret)
return ret;
@@ -246,19 +230,14 @@ static int ub913_disable_streams(struct v4l2_subdev *sd,
u64 streams_mask)
{
struct ub913_data *priv = sd_to_ub913(sd);
- struct media_pad *remote_pad;
- int ret;
u64 sink_streams;
+ int ret;

- 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]);
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
+ UB913_PAD_SINK,
+ &streams_mask);

- ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index,
+ ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
sink_streams);
if (ret)
return ret;
@@ -317,24 +296,6 @@ static int ub913_set_routing(struct v4l2_subdev *sd,
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)
{
@@ -348,27 +309,29 @@ static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
if (pad != UB913_PAD_SOURCE)
return -EINVAL;

- ret = ub913_get_source_frame_desc(priv, &source_fd);
+ ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
+ priv->source_sd_pad, &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;

+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ routing = &state->routing;
+
for_each_active_route(routing, route) {
unsigned int i;

if (route->source_pad != pad)
continue;

- for (i = 0; i < source_fd.num_entries; ++i)
+ for (i = 0; i < source_fd.num_entries; ++i) {
if (source_fd.entry[i].stream == route->sink_stream)
break;
+ }

if (i == source_fd.num_entries) {
dev_err(&priv->client->dev,
@@ -454,23 +417,24 @@ static int ub913_log_status(struct v4l2_subdev *sd)
u8 v, v1, v2;

ub913_read(priv, UB913_REG_MODE_SEL, &v);
- dev_info(dev, "MODE_SEL %#x\n", v);
+ dev_info(dev, "MODE_SEL %#02x\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 | UB913_REG_GENERAL_CFG_CRC_ERR_RESET);
+ ub913_write(priv, UB913_REG_GENERAL_CFG,
+ v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET);
ub913_write(priv, UB913_REG_GENERAL_CFG, v);

+ ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
+ dev_info(dev, "GENERAL_STATUS %#02x\n", v);
+
+ ub913_read(priv, UB913_REG_PLL_OVR, &v);
+ dev_info(dev, "PLL_OVR %#02x\n", v);
+
return 0;
}

@@ -482,7 +446,7 @@ 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_frame_desc = ub913_get_frame_desc,
.get_fmt = v4l2_subdev_get_fmt,
.set_fmt = ub913_set_fmt,
.init_cfg = ub913_init_cfg,
@@ -503,7 +467,6 @@ static int ub913_notify_bound(struct v4l2_async_notifier *notifier,
{
struct ub913_data *priv = sd_to_ub913(notifier->sd);
struct device *dev = &priv->client->dev;
- unsigned int src_pad;
int ret;

ret = media_entity_get_fwnode_pad(&source_subdev->entity,
@@ -516,15 +479,16 @@ static int ub913_notify_bound(struct v4l2_async_notifier *notifier,
}

priv->source_sd = source_subdev;
- src_pad = ret;
+ priv->source_sd_pad = ret;

- ret = media_create_pad_link(&source_subdev->entity, src_pad,
+ ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
&priv->sd.entity, UB913_PAD_SINK,
MEDIA_LNK_FL_ENABLED |
- MEDIA_LNK_FL_IMMUTABLE);
+ 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);
+ source_subdev->name, priv->source_sd_pad,
+ priv->sd.name);
return ret;
}

@@ -539,22 +503,22 @@ 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;
+ struct fwnode_handle *ep_fwnode;
int ret;

- ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
- if (!ep_node) {
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB913_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
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),
+ asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
struct v4l2_async_subdev);

- of_node_put(ep_node);
+ fwnode_handle_put(ep_fwnode);

if (IS_ERR(asd)) {
dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
@@ -587,6 +551,8 @@ static int ub913_register_clkout(struct ub913_data *priv)
int ret;

name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev));
+ if (!name)
+ return -ENOMEM;

priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name,
__clk_get_name(priv->clkin), 0, 1, 2);
@@ -653,25 +619,21 @@ static int ub913_add_i2c_adapter(struct ub913_data *priv)

static int ub913_parse_dt(struct ub913_data *priv)
{
- struct device_node *np = priv->client->dev.of_node;
struct device *dev = &priv->client->dev;
+ struct fwnode_handle *ep_fwnode;
int ret;
- struct device_node *ep_np;

- if (!np) {
- dev_err(dev, "OF: no device tree node!\n");
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB913_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
+ dev_err(dev, "No sink endpoint\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_read_u32(ep_np, "pclk-sample", &priv->pclk_polarity);
+ ret = fwnode_property_read_u32(ep_fwnode, "pclk-sample",
+ &priv->pclk_polarity);

- of_node_put(ep_np);
+ fwnode_handle_put(ep_fwnode);

if (ret) {
dev_err(dev, "OF: failed to parse pclk-sample: %d\n", ret);
@@ -681,84 +643,45 @@ static int ub913_parse_dt(struct ub913_data *priv)
return 0;
}

-static int ub913_probe(struct i2c_client *client)
+static int ub913_hw_init(struct ub913_data *priv)
{
- struct device *dev = &client->dev;
- struct ub913_data *priv;
- int ret;
- u8 v;
+ struct device *dev = &priv->client->dev;
bool mode_override;
u8 mode;
-
- 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;
+ int ret;
+ u8 v;

ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
if (ret)
return ret;

- if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE)) {
- dev_err(dev, "Mode value not stabilized\n");
- return -ENODEV;
- }
+ if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE))
+ return dev_err_probe(dev, -ENODEV,
+ "Mode value not stabilized\n");

mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE;
- mode = v & 0xf;
+ mode = v & UB913_REG_MODE_SEL_MODE_MASK;

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;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "i2c master init failed\n");

ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
v &= ~UB913_REG_GENERAL_CFG_PCLK_RISING;
v |= priv->pclk_polarity ? UB913_REG_GENERAL_CFG_PCLK_RISING : 0;
ub913_write(priv, UB913_REG_GENERAL_CFG, v);

+ return 0;
+}
+
+static int ub913_subdev_init(struct ub913_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ int ret;
+
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;
@@ -768,14 +691,18 @@ static int ub913_probe(struct i2c_client *client)
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;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init pads\n");

- 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);
+ priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB913_PAD_SOURCE, 0,
+ 0);
+
+ if (!priv->sd.fwnode) {
+ ret = -ENODEV;
+ dev_err_probe(dev, ret, "Missing TX endpoint\n");
+ goto err_entity_cleanup;
+ }

ret = v4l2_subdev_init_finalize(&priv->sd);
if (ret)
@@ -783,35 +710,99 @@ static int ub913_probe(struct i2c_client *client)

ret = ub913_v4l2_notifier_register(priv);
if (ret) {
- dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
- goto err_free_state;
+ dev_err_probe(dev, ret,
+ "v4l2 subdev notifier register failed\n");
+ goto err_subdev_cleanup;
}

ret = v4l2_async_register_subdev(&priv->sd);
if (ret) {
- dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
+ dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
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;
- }
-
return 0;

-err_unreg_async_subdev:
- v4l2_async_unregister_subdev(&priv->sd);
err_unreg_notif:
ub913_v4l2_nf_unregister(priv);
-err_free_state:
+err_subdev_cleanup:
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);
+
+ return ret;
+}

+static void ub913_subdev_uninit(struct ub913_data *priv)
+{
+ v4l2_async_unregister_subdev(&priv->sd);
+ ub913_v4l2_nf_unregister(priv);
+ v4l2_subdev_cleanup(&priv->sd);
media_entity_cleanup(&priv->sd.entity);
+}
+
+static int ub913_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ub913_data *priv;
+ int ret;
+
+ 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)
+ return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
+
+ priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "Failed to init regmap\n");
+
+ /*
+ * ub913 can also work without ext clock, but that is not supported by
+ * the driver yet
+ */
+ priv->clkin = devm_clk_get(dev, "clkin");
+ if (IS_ERR(priv->clkin))
+ return dev_err_probe(dev, PTR_ERR(priv->clkin),
+ "Cannot get CLKIN\n");
+
+ ret = ub913_parse_dt(priv);
+ if (ret)
+ return ret;
+
+ ret = ub913_hw_init(priv);
+ if (ret)
+ return ret;
+
+ ret = ub913_gpiochip_probe(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init gpiochip\n");
+
+ ret = ub913_register_clkout(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register clkout\n");
+ goto err_gpiochip_remove;
+ }
+
+ ret = ub913_subdev_init(priv);
+ if (ret)
+ goto err_gpiochip_remove;
+
+ ret = ub913_add_i2c_adapter(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
+ goto err_subdev_uninit;
+ }
+
+ return 0;
+
+err_subdev_uninit:
+ ub913_subdev_uninit(priv);
err_gpiochip_remove:
ub913_gpiochip_remove(priv);

@@ -823,19 +814,9 @@ static void ub913_remove(struct i2c_client *client)
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ub913_data *priv = sd_to_ub913(sd);

- i2c_atr_del_adapter(priv->plat_data->atr,
- priv->plat_data->port);
-
- v4l2_async_unregister_subdev(&priv->sd);
-
- ub913_v4l2_nf_unregister(priv);
-
- v4l2_subdev_cleanup(&priv->sd);
+ i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port);

- if (priv->tx_ep_np)
- of_node_put(priv->tx_ep_np);
-
- media_entity_cleanup(&priv->sd.entity);
+ ub913_subdev_uninit(priv);

ub913_gpiochip_remove(priv);
}
@@ -843,13 +824,11 @@ static void ub913_remove(struct i2c_client *client)
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", },
+ { .compatible = "ti,ds90ub913a-q1" },
{}
};
MODULE_DEVICE_TABLE(of, ub913_dt_ids);
-#endif

static struct i2c_driver ds90ub913_driver = {
.probe_new = ub913_probe,
@@ -857,15 +836,13 @@ static struct i2c_driver ds90ub913_driver = {
.id_table = ub913_id,
.driver = {
.name = "ds90ub913a",
- .owner = THIS_MODULE,
- .of_match_table = of_match_ptr(ub913_dt_ids),
+ .of_match_table = ub913_dt_ids,
},
};
-
module_i2c_driver(ds90ub913_driver);

MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Texas Instruments DS90UB913 serializer driver");
+MODULE_DESCRIPTION("Texas Instruments DS90UB913 FPD-Link III Serializer Driver");
MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
MODULE_IMPORT_NS(I2C_ATR);
diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c
index 251dc8e8adfa..ec33e16da3d1 100644
--- a/drivers/media/i2c/ds90ub953.c
+++ b/drivers/media/i2c/ds90ub953.c
@@ -5,21 +5,21 @@
* Based on a driver from Luca Ceresoli <[email protected]>
*
* Copyright (c) 2019 Luca Ceresoli <[email protected]>
- * Copyright (c) 2022 Tomi Valkeinen <[email protected]>
+ * Copyright (c) 2023 Tomi Valkeinen <[email protected]>
*/

#include <linux/clk-provider.h>
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/fwnode.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/property.h>
#include <linux/rational.h>
#include <linux/regmap.h>

@@ -126,7 +126,7 @@ enum ub953_mode {

struct ub953_hw_data {
const char *model;
- bool ub971;
+ bool is_ub971;
};

struct ub953_data {
@@ -138,7 +138,6 @@ struct ub953_data {
u32 num_data_lanes;

struct gpio_chip gpio_chip;
- char gpio_chip_name[64];

struct v4l2_subdev sd;
struct media_pad pads[2];
@@ -146,13 +145,12 @@ struct ub953_data {
struct v4l2_async_notifier notifier;

struct v4l2_subdev *source_sd;
+ u16 source_sd_pad;

struct v4l2_ctrl_handler ctrl_handler;

u64 enabled_source_streams;

- struct device_node *tx_ep_np;
-
/* lock for register access */
struct mutex reg_lock;

@@ -183,8 +181,8 @@ static int ub953_read(struct ub953_data *priv, u8 reg, u8 *val)

ret = regmap_read(priv->regmap, reg, &v);
if (ret)
- dev_err(&priv->client->dev,
- "Cannot read register 0x%02x: %d!\n", reg, ret);
+ dev_err(&priv->client->dev, "Cannot read register 0x%02x: %d\n",
+ reg, ret);
else
*val = v;

@@ -202,7 +200,7 @@ static int ub953_write(struct ub953_data *priv, u8 reg, u8 val)
ret = regmap_write(priv->regmap, reg, val);
if (ret)
dev_err(&priv->client->dev,
- "Cannot write register 0x%02x: %d!\n", reg, ret);
+ "Cannot write register 0x%02x: %d\n", reg, ret);

mutex_unlock(&priv->reg_lock);

@@ -219,7 +217,7 @@ static int _ub953_select_ind_reg_block(struct ub953_data *priv, u8 block)

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",
+ dev_err(dev, "%s: cannot select indirect target %u (%d)\n",
__func__, block, ret);
return ret;
}
@@ -229,8 +227,8 @@ static int _ub953_select_ind_reg_block(struct ub953_data *priv, u8 block)
return 0;
}

-__maybe_unused
-static int ub953_read_ind(struct ub953_data *priv, u8 block, u8 reg, u8 *val)
+__maybe_unused static int ub953_read_ind(struct ub953_data *priv, u8 block,
+ u8 reg, u8 *val)
{
unsigned int v;
int ret;
@@ -242,12 +240,20 @@ static int ub953_read_ind(struct ub953_data *priv, u8 block, u8 reg, u8 *val)
goto out;

ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
+ block, reg, ret);
goto out;
+ }

ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
+ block, reg, ret);
goto out;
+ }

*val = v;

@@ -268,10 +274,19 @@ static int ub953_write_ind(struct ub953_data *priv, u8 block, u8 reg, u8 val)
goto out;

ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n",
+ block, reg, ret);
goto out;
+ }

ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n",
+ block, reg, ret);
+ }

out:
mutex_unlock(&priv->reg_lock);
@@ -334,11 +349,10 @@ 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));
+ 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,
@@ -347,19 +361,18 @@ static int ub953_gpio_direction_out(struct gpio_chip *gc, unsigned int offset,
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);
+ 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));
+ 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)
@@ -379,10 +392,10 @@ 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);
+ 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,
@@ -405,14 +418,11 @@ static int ub953_gpiochip_probe(struct ub953_data *priv)
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->label = dev_name(dev);
gc->parent = dev;
gc->owner = THIS_MODULE;
gc->base = -1;
- gc->can_sleep = 1;
+ gc->can_sleep = true;
gc->ngpio = UB953_NUM_GPIOS;
gc->get_direction = ub953_gpio_get_direction;
gc->direction_input = ub953_gpio_direction_in;
@@ -420,7 +430,6 @@ static int ub953_gpiochip_probe(struct ub953_data *priv)
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);
@@ -490,24 +499,6 @@ static int ub953_set_routing(struct v4l2_subdev *sd,
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)
{
@@ -521,18 +512,19 @@ static int ub953_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
if (pad != UB953_PAD_SOURCE)
return -EINVAL;

- ret = ub953_get_source_frame_desc(priv, &source_fd);
+ ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
+ priv->source_sd_pad, &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;

+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ routing = &state->routing;
+
for_each_active_route(routing, route) {
struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
unsigned int i;
@@ -540,11 +532,12 @@ static int ub953_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
if (route->source_pad != pad)
continue;

- for (i = 0; i < source_fd.num_entries; ++i)
+ for (i = 0; i < source_fd.num_entries; ++i) {
if (source_fd.entry[i].stream == route->sink_stream) {
source_entry = &source_fd.entry[i];
break;
}
+ }

if (!source_entry) {
dev_err(&priv->client->dev,
@@ -644,7 +637,7 @@ static int ub953_log_status(struct v4l2_subdev *sd)
dev_info(dev, "ID '%s'\n", id);

ub953_read(priv, UB953_REG_GENERAL_STATUS, &v);
- dev_info(dev, "GENERAL_STATUS %#x\n", v);
+ dev_info(dev, "GENERAL_STATUS %#02x\n", v);

ub953_read(priv, UB953_REG_CRC_ERR_CNT1, &v1);
ub953_read(priv, UB953_REG_CRC_ERR_CNT2, &v2);
@@ -654,16 +647,16 @@ static int ub953_log_status(struct v4l2_subdev *sd)
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);
+ dev_info(dev, "CSI_ERR_STATUS %#02x\n", v);

ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v);
- dev_info(dev, "CSI_ERR_DLANE01 %#x\n", v);
+ dev_info(dev, "CSI_ERR_DLANE01 %#02x\n", v);

ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v);
- dev_info(dev, "CSI_ERR_DLANE23 %#x\n", v);
+ dev_info(dev, "CSI_ERR_DLANE23 %#02x\n", v);

ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v);
- dev_info(dev, "CSI_ERR_CLK_LANE %#x\n", v);
+ dev_info(dev, "CSI_ERR_CLK_LANE %#02x\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);
@@ -673,7 +666,7 @@ static int ub953_log_status(struct v4l2_subdev *sd)
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);
+ dev_info(dev, "CSI ECC %#02x\n", v);

ub953_read(priv, UB953_REG_LOCAL_GPIO_DATA, &gpio_local_data);
ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &gpio_input_ctrl);
@@ -698,19 +691,14 @@ static int ub953_enable_streams(struct v4l2_subdev *sd,
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);
+ 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,
+ ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
sink_streams);
if (ret)
return ret;
@@ -725,19 +713,14 @@ static int ub953_disable_streams(struct v4l2_subdev *sd,
u64 streams_mask)
{
struct ub953_data *priv = sd_to_ub953(sd);
- struct media_pad *remote_pad;
- int ret;
u64 sink_streams;
+ int ret;

- 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]);
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
+ UB953_PAD_SINK,
+ &streams_mask);

- ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index,
+ ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
sink_streams);
if (ret)
return ret;
@@ -758,9 +741,9 @@ static const struct v4l2_subdev_pad_ops ub953_pad_ops = {
};

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,
+ .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 = {
@@ -896,7 +879,6 @@ static int ub953_notify_bound(struct v4l2_async_notifier *notifier,
{
struct ub953_data *priv = sd_to_ub953(notifier->sd);
struct device *dev = &priv->client->dev;
- unsigned int src_pad;
int ret;

ret = media_entity_get_fwnode_pad(&source_subdev->entity,
@@ -909,14 +891,16 @@ static int ub953_notify_bound(struct v4l2_async_notifier *notifier,
}

priv->source_sd = source_subdev;
- src_pad = ret;
+ priv->source_sd_pad = ret;

- ret = media_create_pad_link(
- &source_subdev->entity, src_pad, &priv->sd.entity, 0,
- MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
+ ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_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);
+ source_subdev->name, priv->source_sd_pad,
+ priv->sd.name);
return ret;
}

@@ -931,22 +915,22 @@ 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;
+ struct fwnode_handle *ep_fwnode;
int ret;

- ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
- if (!ep_node) {
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB953_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
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),
+ asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
struct v4l2_async_subdev);

- of_node_put(ep_node);
+ fwnode_handle_put(ep_fwnode);

if (IS_ERR(asd)) {
dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd));
@@ -1000,23 +984,15 @@ static int ub953_i2c_master_init(struct ub953_data *priv)

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;
- }
+ if (priv->mode != UB953_MODE_SYNC) {
+ /* Not supported */
+ return 0;
}
+
+ if (priv->hw_data->is_ub971)
+ return priv->plat_data->bc_rate * 160ull;
+ else
+ return priv->plat_data->bc_rate / 2 * 160ull;
}

static unsigned long ub953_calc_clkout_ub953(struct ub953_data *priv,
@@ -1105,7 +1081,7 @@ static unsigned long ub953_clkout_recalc_rate(struct clk_hw *hw,

fc_rate = ub953_get_fc_rate(priv);

- if (priv->hw_data->ub971) {
+ if (priv->hw_data->is_ub971) {
mul = ctrl0 & 0x1f;
div = ctrl1;

@@ -1145,17 +1121,15 @@ static long ub953_clkout_round_rate(struct clk_hw *hw, unsigned long rate,

fc_rate = ub953_get_fc_rate(priv);

- if (priv->hw_data->ub971) {
+ if (priv->hw_data->is_ub971) {
res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n);

- dev_dbg(dev,
- "%s %llu * %u / (8 * %u) = %lu (requested %lu)",
+ 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)",
+ dev_dbg(dev, "%s %llu / %u * %u / %u = %lu (requested %lu)",
__func__, fc_rate, hs_div, m, n, res, rate);
}

@@ -1172,7 +1146,7 @@ static int ub953_clkout_set_rate(struct clk_hw *hw, unsigned long rate,

fc_rate = ub953_get_fc_rate(priv);

- if (priv->hw_data->ub971) {
+ if (priv->hw_data->is_ub971) {
res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n);

ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m);
@@ -1184,8 +1158,8 @@ static int ub953_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n);
}

- dev_dbg(&priv->client->dev,
- "%s %lu (requested %lu)\n", __func__, res, rate);
+ dev_dbg(&priv->client->dev, "%s %lu (requested %lu)\n", __func__, res,
+ rate);

return 0;
}
@@ -1232,8 +1206,11 @@ static int ub953_register_clkout(struct ub953_data *priv)
};
int ret;

+ if (!init.name)
+ return -ENOMEM;
+
/* Initialize clkout to 25MHz by default */
- if (priv->hw_data->ub971)
+ if (priv->hw_data->is_ub971)
ub953_init_clkout_ub971(priv);
else
ub953_init_clkout_ub953(priv);
@@ -1285,33 +1262,29 @@ static const struct regmap_config ub953_regmap_config = {

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;
+ struct fwnode_handle *ep_fwnode;
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) {
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB953_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
dev_err(dev, "OF: no endpoint\n");
return -ENOENT;
}

- ret = of_property_count_u32_elems(ep_np, "data-lanes");
+ ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");

- of_node_put(ep_np);
+ fwnode_handle_put(ep_fwnode);

if (ret <= 0) {
- dev_err(dev, "OF: failed to parse data-lanes: %d\n", ret);
+ dev_err(dev, "failed to parse property '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);
+ dev_err(dev, "bad number of data-lanes: %d\n", ret);
return -EINVAL;
}

@@ -1320,58 +1293,19 @@ static int ub953_parse_dt(struct ub953_data *priv)
return 0;
}

-static int ub953_probe(struct i2c_client *client)
+static int ub953_hw_init(struct ub953_data *priv)
{
- struct device *dev = &client->dev;
- struct ub953_data *priv;
+ struct device *dev = &priv->client->dev;
+ bool mode_override;
int ret;
u8 v;
- bool mode_override;
-
- 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;
+ return ret;

- if (!(v & UB953_REG_MODE_SEL_MODE_DONE)) {
- dev_err(dev, "Mode value not stabilized\n");
- ret = -ENODEV;
- goto err_mutex_destroy;
- }
+ if (!(v & UB953_REG_MODE_SEL_MODE_DONE))
+ return dev_err_probe(dev, -EIO, "Mode value not stabilized\n");

mode_override = v & UB953_REG_MODE_SEL_MODE_OVERRIDE;

@@ -1389,57 +1323,46 @@ static int ub953_probe(struct i2c_client *client)
priv->mode = UB953_MODE_DVP;
break;
default:
- dev_err(dev, "Illegal mode in mode register\n");
- ret = -ENODEV;
- goto err_mutex_destroy;
+ return dev_err_probe(dev, -EIO,
+ "Illegal mode in mode register\n");
}

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;
- }
+ if (priv->mode != UB953_MODE_SYNC)
+ return dev_err_probe(dev, -ENODEV,
+ "Only synchronous mode supported\n");

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;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read revision");

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;
+ return ret;

dev_dbg(dev, "i2c strap setting %s V\n",
(v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "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;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "i2c init failed\n");

ub953_write(priv, UB953_REG_GENERAL_CFG,
UB953_REG_GENERAL_CFG_CONT_CLK |
((priv->num_data_lanes - 1) << UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT) |
UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE);

- ret = ub953_register_clkout(priv);
- if (ret) {
- dev_err(dev, "Failed to register clkout\n");
- goto err_gpiochip_remove;
- }
+ return 0;
+}
+
+static int ub953_subdev_init(struct ub953_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ int ret;

v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops);

@@ -1452,13 +1375,12 @@ static int ub953_probe(struct i2c_client *client)
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;
- }
+ if (priv->ctrl_handler.error)
+ return dev_err_probe(dev, priv->ctrl_handler.error,
+ "Failed to set up v4l2 controls\n");

- priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
- V4L2_SUBDEV_FL_STREAMS;
+ 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;

@@ -1467,12 +1389,18 @@ static int ub953_probe(struct i2c_client *client)

ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads);
if (ret) {
- dev_err(dev, "Failed to init pads\n");
+ dev_err_probe(dev, ret, "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.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB953_PAD_SOURCE, 0,
+ 0);
+ if (!priv->sd.fwnode) {
+ ret = -ENODEV;
+ dev_err_probe(dev, ret, "Missing TX endpoint\n");
+ goto err_entity_cleanup;
+ }

priv->sd.state_lock = priv->sd.ctrl_handler->lock;

@@ -1482,37 +1410,109 @@ static int ub953_probe(struct i2c_client *client)

ret = ub953_v4l2_notifier_register(priv);
if (ret) {
- dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret);
+ dev_err_probe(dev, ret,
+ "v4l2 subdev notifier register failed\n");
goto err_free_state;
}

ret = v4l2_async_register_subdev(&priv->sd);
if (ret) {
- dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret);
+ dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
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;
- }
-
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);
+
+ return ret;
+}
+
+static void ub953_subdev_uninit(struct ub953_data *priv)
+{
+ v4l2_async_unregister_subdev(&priv->sd);
+ ub953_v4l2_notifier_unregister(priv);
+ v4l2_subdev_cleanup(&priv->sd);
+ media_entity_cleanup(&priv->sd.entity);
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+}
+
+static int ub953_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ub953_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;
+
+ priv->plat_data = dev_get_platdata(&client->dev);
+ if (!priv->plat_data)
+ return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
+
+ 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)) {
+ ret = PTR_ERR(priv->regmap);
+ dev_err_probe(dev, ret, "Failed to init regmap\n");
+ goto err_mutex_destroy;
+ }
+
+ ret = ub953_parse_dt(priv);
+ if (ret)
+ goto err_mutex_destroy;
+
+ ret = ub953_hw_init(priv);
+ if (ret)
+ goto err_mutex_destroy;
+
+ ret = ub953_gpiochip_probe(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to init gpiochip\n");
+ goto err_mutex_destroy;
+ }
+
+ ret = ub953_register_clkout(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register clkout\n");
+ goto err_gpiochip_remove;
+ }
+
+ ret = ub953_subdev_init(priv);
+ if (ret)
+ goto err_gpiochip_remove;
+
+ ret = ub953_add_i2c_adapter(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
+ goto err_subdev_uninit;
+ }
+
+ return 0;
+
+err_subdev_uninit:
+ ub953_subdev_uninit(priv);
err_gpiochip_remove:
ub953_gpiochip_remove(priv);
err_mutex_destroy:
@@ -1526,20 +1526,9 @@ static void ub953_remove(struct i2c_client *client)
struct v4l2_subdev *sd = i2c_get_clientdata(client);
struct ub953_data *priv = sd_to_ub953(sd);

- i2c_atr_del_adapter(priv->plat_data->atr,
- priv->plat_data->port);
-
- v4l2_async_unregister_subdev(&priv->sd);
-
- ub953_v4l2_notifier_unregister(priv);
-
- v4l2_subdev_cleanup(&priv->sd);
-
- of_node_put(priv->tx_ep_np);
+ i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port);

- media_entity_cleanup(&priv->sd.entity);
-
- v4l2_ctrl_handler_free(&priv->ctrl_handler);
+ ub953_subdev_uninit(priv);

ub953_gpiochip_remove(priv);
mutex_destroy(&priv->reg_lock);
@@ -1551,7 +1540,7 @@ static const struct ub953_hw_data ds90ub953_hw = {

static const struct ub953_hw_data ds90ub971_hw = {
.model = "ub971",
- .ub971 = true,
+ .is_ub971 = true,
};

static const struct i2c_device_id ub953_id[] = {
@@ -1561,14 +1550,12 @@ static const struct i2c_device_id ub953_id[] = {
};
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,
@@ -1577,14 +1564,13 @@ static struct i2c_driver ds90ub953_driver = {
.driver = {
.name = "ds90ub953",
.owner = THIS_MODULE,
- .of_match_table = of_match_ptr(ub953_dt_ids),
+ .of_match_table = ub953_dt_ids,
},
};
-
module_i2c_driver(ds90ub953_driver);

MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Texas Instruments DS90UB953 serializer driver");
+MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV CSI-2 Serializers Driver");
MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
MODULE_IMPORT_NS(I2C_ATR);
diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c
index b532a93f3da7..2cefb3744901 100644
--- a/drivers/media/i2c/ds90ub960.c
+++ b/drivers/media/i2c/ds90ub960.c
@@ -3,12 +3,13 @@
* Driver for the Texas Instruments DS90UB960-Q1 video deserializer
*
* Copyright (c) 2019 Luca Ceresoli <[email protected]>
- * Copyright (c) 2022 Tomi Valkeinen <[email protected]>
+ * Copyright (c) 2023 Tomi Valkeinen <[email protected]>
*/

#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/delay.h>
+#include <linux/fwnode.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c-atr.h>
#include <linux/i2c.h>
@@ -18,9 +19,8 @@
#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/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
@@ -348,8 +348,8 @@ struct ub960_hw_data {
const char *model;
u8 num_rxports;
u8 num_txports;
- bool ub9702;
- bool fpdlink4;
+ bool is_ub9702;
+ bool is_fpdlink4;
};

enum ub960_rxport_mode {
@@ -371,13 +371,14 @@ 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;
+ struct v4l2_subdev *source_sd; /* Connected subdev */
+ u16 source_sd_pad;
+ struct fwnode_handle *source_ep_fwnode;

enum ub960_rxport_mode rx_mode;
enum ub960_rxport_cdr cdr_mode;

- struct device_node *remote_of_node; /* 'serializer' OF node */
+ struct fwnode_handle *remote_fwnode; /* 'serializer' fwnode */
struct i2c_client *ser_client; /* Serializer */
unsigned short ser_alias; /* Serializer i2c alias (lower 7 bits) */

@@ -507,7 +508,7 @@ enum {
TEST_PATTERN_V_COLOR_BARS_8,
};

-static const char * const ub960_tpg_qmenu[] = {
+static const char *const ub960_tpg_qmenu[] = {
"Disabled",
"1 vertical color bar",
"2 vertical color bars",
@@ -832,12 +833,20 @@ static int ub960_read_ind(struct ub960_data *priv, u8 block, u8 reg, u8 *val)
goto out;

ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
+ block, reg, ret);
goto out;
+ }

ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
+ block, reg, ret);
goto out;
+ }

*val = v;

@@ -854,12 +863,20 @@ static int ub960_write_ind(struct ub960_data *priv, u8 block, u8 reg, u8 val)
mutex_lock(&priv->reg_lock);

ret = _ub960_select_ind_reg_block(priv, block);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n",
+ block, reg, ret);
goto out;
+ }

ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
- if (ret)
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n",
+ block, reg, ret);
goto out;
+ }

ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val);

@@ -937,7 +954,6 @@ static int ub960_ind_update_bits(struct ub960_data *priv, u8 block, u8 reg,
*/

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)
{
@@ -951,7 +967,7 @@ static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id,
int ret = 0;
u8 port_reg_idx_mask = 0;

- dev_dbg(dev, "rx%d: %s\n", chan_id, __func__);
+ dev_dbg(dev, "rx%u: %s\n", chan_id, __func__);

mutex_lock(&priv->atr_alias_table.lock);

@@ -974,13 +990,13 @@ static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id,
}

if (!entry) {
- dev_err(dev, "rx%d: alias pool exhausted\n", rxport->nport);
+ dev_err(dev, "rx%u: 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);
+ dev_err(dev, "rx%u: all aliases in use\n", rxport->nport);
ret = -EADDRNOTAVAIL;
goto out;
}
@@ -1003,7 +1019,7 @@ static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id,

*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",
+ dev_dbg(dev, "rx%u: client 0x%02x mapped at alias 0x%02x (%s)\n",
rxport->nport, client->addr, alias, client->name);

out:
@@ -1022,7 +1038,7 @@ static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id,
unsigned int pool_idx;
u16 alias = 0;

- dev_dbg(dev, "rx%d: %s\n", chan_id, __func__);
+ dev_dbg(dev, "rx%u: %s\n", chan_id, __func__);

mutex_lock(&priv->atr_alias_table.lock);

@@ -1037,7 +1053,7 @@ static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id,
}

if (pool_idx == priv->atr_alias_table.num_entries) {
- dev_err(dev, "rx%d: client 0x%02x is not mapped!\n",
+ dev_err(dev, "rx%u: client 0x%02x is not mapped!\n",
rxport->nport, client->addr);
goto out;
}
@@ -1052,7 +1068,7 @@ static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id,

entry->reserved = false;

- dev_dbg(dev, "rx%d: client 0x%02x unmapped from alias 0x%02x (%s)\n",
+ dev_dbg(dev, "rx%u: client 0x%02x unmapped from alias 0x%02x (%s)\n",
rxport->nport, client->addr, alias, client->name);

out:
@@ -1069,19 +1085,14 @@ static const struct i2c_atr_ops ub960_atr_ops = {
*/

static int ub960_parse_dt_txport(struct ub960_data *priv,
- const struct device_node *np, u8 nport)
+ const struct fwnode_handle *ep_fwnode,
+ 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;
@@ -1091,42 +1102,36 @@ static int ub960_parse_dt_txport(struct ub960_data *priv,

priv->txports[nport] = txport;

- ret = of_property_count_u32_elems(np, "data-lanes");
-
+ ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
if (ret <= 0) {
- dev_err(dev, "OF: %s: failed to parse data-lanes: %d\n",
- of_node_full_name(np), ret);
+ dev_err(dev, "tx%u: failed to parse 'data-lanes': %d\n", nport,
+ ret);
goto err_free_txport;
}

txport->num_data_lanes = ret;

- ret = of_property_count_u64_elems(np, "link-frequencies");
+ ret = fwnode_property_count_u64(ep_fwnode, "link-frequencies");
if (ret < 0) {
- dev_err(dev, "OF: %s: failed to parse link-frequencies: %d\n",
- of_node_full_name(np), ret);
+ dev_err(dev, "tx%u: failed to parse 'link-frequencies': %d\n",
+ nport, 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);
+ dev_err(dev,
+ "tx%u: 'link-frequencies' must contain a single frequency: %d\n",
+ nport, ret);
goto err_free_txport;
}

- ret = of_property_read_u64(np, "link-frequencies", &freq);
+ ret = fwnode_property_read_u64(ep_fwnode, "link-frequencies", &freq);
if (ret) {
- dev_err(dev, "OF: %s: failed to read link frequency: %d\n",
- of_node_full_name(np), ret);
+ dev_err(dev, "tx%u: failed to read 'link-frequencies': %d\n",
+ nport, 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;

@@ -1134,12 +1139,11 @@ static int ub960_parse_dt_txport(struct ub960_data *priv,
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));
+ dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport);
return -EINVAL;
}

- dev_dbg(dev, "Nominal data rate: %u", priv->tx_data_rate);
+ dev_dbg(dev, "tx%u: nominal data rate: %u", nport, priv->tx_data_rate);

return 0;

@@ -1651,13 +1655,14 @@ static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 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,
+ .of_node = to_of_node(rxport->remote_fwnode),
+ .fwnode = rxport->remote_fwnode,
.platform_data = ser_pdata,
};

ser_pdata->port = nport;
ser_pdata->atr = priv->atr;
- if (priv->hw_data->ub9702)
+ if (priv->hw_data->is_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);
@@ -1671,12 +1676,12 @@ static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 nport)
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,
+ dev_err(dev, "rx%u: 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",
+ dev_dbg(dev, "rx%u: remote serializer at alias 0x%02x (%u-%04x)\n",
nport, rxport->ser_client->addr,
rxport->ser_client->adapter->nr, rxport->ser_client->addr);

@@ -1788,7 +1793,7 @@ static int ub960_init_tx_ports(struct ub960_data *priv)

ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select);

- if (priv->hw_data->ub9702) {
+ if (priv->hw_data->is_ub9702) {
ub960_write(priv, UB960_SR_CSI_PLL_DIV, pll_div);

switch (priv->tx_data_rate) {
@@ -1838,10 +1843,6 @@ static void ub960_init_rx_port_ub960(struct ub960_data *priv,
*/

switch (rxport->rx_mode) {
- default:
- WARN_ON(true);
- fallthrough;
-
case RXPORT_MODE_RAW10:
case RXPORT_MODE_RAW12_HF:
case RXPORT_MODE_RAW12_LF:
@@ -1855,6 +1856,10 @@ static void ub960_init_rx_port_ub960(struct ub960_data *priv,
case RXPORT_MODE_CSI2_SYNC:
bc_freq_val = 6;
break;
+
+ default:
+ WARN_ON(true);
+ return;
}

ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7,
@@ -1947,7 +1952,7 @@ static void ub960_init_rx_port_ub9702_fpd3(struct ub960_data *priv,

default:
WARN_ON(true);
- break;
+ return;
}

ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7,
@@ -2041,7 +2046,7 @@ static void ub960_init_rx_port_ub9702_fpd4(struct ub960_data *priv,

default:
WARN_ON(true);
- break;
+ return;
}

ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7,
@@ -2141,7 +2146,7 @@ static int ub960_init_rx_ports(struct ub960_data *priv)
if (!rxport)
continue;

- if (priv->hw_data->ub9702)
+ if (priv->hw_data->is_ub9702)
ub960_init_rx_port_ub9702(priv, rxport);
else
ub960_init_rx_port_ub960(priv, rxport);
@@ -2181,66 +2186,66 @@ static void ub960_rxport_handle_events(struct ub960_data *priv, u8 nport)

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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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);
+ 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,
+ dev_dbg(dev, "rx%u line len changed: %u\n", nport,
(v1 << 8) | v2);
}

@@ -2249,16 +2254,24 @@ static void ub960_rxport_handle_events(struct ub960_data *priv, u8 nport)

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

@@ -2340,8 +2353,6 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
struct v4l2_subdev_state *state)
{
const struct v4l2_subdev_krouting *routing = &state->routing;
- unsigned int i;
- unsigned int nport;
u8 fwd_ctl;
struct {
u32 num_streams;
@@ -2354,7 +2365,7 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,

ub960_get_vc_maps(priv, state, vc_map);

- for (i = 0; i < routing->num_routes; ++i) {
+ for (unsigned int i = 0; i < routing->num_routes; ++i) {
struct v4l2_subdev_route *route = &routing->routes[i];
struct ub960_rxport *rxport;
struct ub960_txport *txport;
@@ -2384,7 +2395,8 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
if (rx_data[nport].num_streams > 2)
return -EPIPE;

- fmt = v4l2_subdev_state_get_stream_format(state, route->sink_pad,
+ fmt = v4l2_subdev_state_get_stream_format(state,
+ route->sink_pad,
route->sink_stream);
if (!fmt)
return -EPIPE;
@@ -2396,8 +2408,8 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
if (ub960_fmt->meta) {
if (fmt->height > 3) {
dev_err(&priv->client->dev,
- "Unsupported metadata height %u\n",
- fmt->height);
+ "rx%u: unsupported metadata height %u\n",
+ nport, fmt->height);
return -EPIPE;
}

@@ -2412,7 +2424,7 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,

fwd_ctl = 0;

- for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+ for (unsigned int nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
struct ub960_rxport *rxport = priv->rxports[nport];
u8 vc = vc_map[nport];

@@ -2429,14 +2441,15 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
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);
+ 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) {
+ if (!priv->hw_data->is_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) |
@@ -2447,7 +2460,8 @@ static int ub960_configure_ports_for_streaming(struct ub960_data *priv,
/* 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);
+ UB960_RR_VC_ID_MAP(i),
+ nport);
}

break;
@@ -2545,9 +2559,6 @@ static int ub960_enable_streams(struct v4l2_subdev *sd,
}

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;

@@ -2565,11 +2576,10 @@ static int ub960_enable_streams(struct v4l2_subdev *sd,
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]);
+ priv->rxports[nport]->source_sd,
+ priv->rxports[nport]->source_sd_pad,
+ sink_streams[nport]);
if (ret) {
priv->stream_enable_mask[nport] &= ~sink_streams[nport];

@@ -2587,20 +2597,16 @@ static int ub960_enable_streams(struct v4l2_subdev *sd,

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]);
+ priv->rxports[nport]->source_sd,
+ priv->rxports[nport]->source_sd_pad,
+ sink_streams[nport]);
WARN_ON(ret);

priv->stream_enable_mask[nport] &= ~sink_streams[nport];
@@ -2665,20 +2671,16 @@ static int ub960_disable_streams(struct v4l2_subdev *sd,
}

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]);
+ priv->rxports[nport]->source_sd,
+ priv->rxports[nport]->source_sd_pad,
+ sink_streams[nport]);
WARN_ON(ret);

priv->stream_enable_mask[nport] &= ~sink_streams[nport];
@@ -2748,9 +2750,9 @@ static int ub960_s_stream(struct v4l2_subdev *sd, int enable)
if (pad_stream_masks[nport] == 0)
continue;

- ub960_disable_streams(
- sd, state, priv->hw_data->num_rxports + nport,
- pad_stream_masks[nport]);
+ ub960_disable_streams(sd, state,
+ priv->hw_data->num_rxports + nport,
+ pad_stream_masks[nport]);
}
}

@@ -2816,27 +2818,6 @@ static int ub960_set_routing(struct v4l2_subdev *sd,
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)
{
@@ -2851,37 +2832,41 @@ static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
if (!ub960_pad_is_source(priv, pad))
return -EINVAL;

+ memset(fd, 0, sizeof(*fd));
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
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;
+ unsigned int nport;
+ unsigned int i;

if (route->source_pad != pad)
continue;

- ret = ub960_get_source_frame_desc(priv, &source_fd,
- route->sink_pad);
+ nport = ub960_pad_to_port(priv, route->sink_pad);
+
+ ret = v4l2_subdev_call(priv->rxports[nport]->source_sd, pad,
+ get_frame_desc,
+ priv->rxports[nport]->source_sd_pad,
+ &source_fd);
if (ret) {
dev_err(dev,
- "Failed to get source frame desc for port %u\n",
+ "Failed to get source frame desc for pad %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];
+ for (i = 0; i < source_fd.num_entries; ++i)
+ if (source_fd.entry[i].stream == route->sink_stream) {
+ source_entry = &source_fd.entry[i];
break;
}

@@ -2897,9 +2882,7 @@ static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
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];
+ fd->entry[fd->num_entries].bus.csi2.vc = vc_map[nport];

if (source_fd.type == V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
fd->entry[fd->num_entries].bus.csi2.dt =
@@ -2908,8 +2891,8 @@ static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
const struct ub960_format_info *ub960_fmt;
struct v4l2_mbus_framefmt *fmt;

- fmt = v4l2_subdev_state_get_stream_format(
- state, pad, route->source_stream);
+ fmt = v4l2_subdev_state_get_stream_format(state, pad,
+ route->source_stream);

if (!fmt) {
ret = -EINVAL;
@@ -3012,12 +2995,11 @@ static int ub960_log_status(struct v4l2_subdev *sd)
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)
+ for (unsigned int i = 0; i < 6; ++i)
ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
id[6] = 0;

@@ -3034,7 +3016,8 @@ static int ub960_log_status(struct v4l2_subdev *sd)
}

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));
+ 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);
@@ -3073,13 +3056,14 @@ static int ub960_log_status(struct v4l2_subdev *sd)
else
dev_info(dev, "\tNot locked\n");

- dev_info(dev, "\trx_port_sts1 %#x\n", v);
+ dev_info(dev, "\trx_port_sts1 %#02x\n", v);
ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v);
- dev_info(dev, "\trx_port_sts2 %#x\n", v);
+ dev_info(dev, "\trx_port_sts2 %#02x\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);
+ 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);
@@ -3132,9 +3116,8 @@ static int ub960_log_status(struct v4l2_subdev *sd)
(v >> UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT) & 0xf);
}

- ub960_rxport_get_eq_level(priv, nport, &eq_level);
-
- dev_info(dev, "\tEQ level %u\n", eq_level);
+ if (ub960_rxport_get_eq_level(priv, nport, &eq_level) == 0)
+ dev_info(dev, "\tEQ level %u\n", eq_level);

/* GPIOs */
for (i = 0; i < UB960_NUM_BC_GPIOS; ++i) {
@@ -3157,15 +3140,15 @@ static int ub960_log_status(struct v4l2_subdev *sd)
}

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,
+ .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,
+ .core = &ub960_subdev_core_ops,
+ .video = &ub960_video_ops,
+ .pad = &ub960_pad_ops,
};

static const struct media_entity_operations ub960_entity_ops = {
@@ -3303,7 +3286,7 @@ static irqreturn_t ub960_handle_events(int irq, void *arg)

ub960_read(priv, UB960_SR_FWD_STS, &fwd_sts);

- dev_dbg(&priv->client->dev, "FWD_STS %#x\n", fwd_sts);
+ dev_dbg(&priv->client->dev, "FWD_STS %#02x\n", fwd_sts);

for (i = 0; i < priv->hw_data->num_txports; ++i) {
if (int_sts & UB960_SR_INTERRUPT_STS_IS_CSI_TX(i))
@@ -3359,8 +3342,8 @@ static void ub960_rxport_free_ports(struct ub960_data *priv)
if (!rxport)
continue;

- fwnode_handle_put(rxport->fwnode);
- of_node_put(rxport->remote_of_node);
+ fwnode_handle_put(rxport->source_ep_fwnode);
+ fwnode_handle_put(rxport->remote_fwnode);

kfree(rxport);
priv->rxports[nport] = NULL;
@@ -3369,13 +3352,12 @@ static void ub960_rxport_free_ports(struct ub960_data *priv)

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");
+ ret = fwnode_property_count_u16(dev_fwnode(dev), "i2c-alias-pool");
if (ret < 0) {
dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n",
ret);
@@ -3398,8 +3380,8 @@ static int ub960_parse_dt_base(struct ub960_data *priv)
if (!aliases)
return -ENOMEM;

- ret = of_property_read_u16_array(np, "i2c-alias-pool", aliases,
- table_size);
+ ret = fwnode_property_read_u16_array(dev_fwnode(dev), "i2c-alias-pool",
+ aliases, table_size);
if (ret < 0) {
dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n",
ret);
@@ -3417,47 +3399,50 @@ static int ub960_parse_dt_base(struct ub960_data *priv)
return 0;
}

-static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
- struct device_node *np,
- struct ub960_rxport *rxport)
+static int
+ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
+ struct fwnode_handle *link_fwnode,
+ struct ub960_rxport *rxport)
{
struct device *dev = &priv->client->dev;
- int ret;
+ unsigned int nport = rxport->nport;
u32 rx_mode;
u32 cdr_mode;
s32 strobe_pos;
u32 eq_level;
- unsigned int nport = rxport->nport;
+ u32 ser_i2c_alias;
+ int ret;

- ret = of_property_read_u32(np, "ti,cdr-mode", &cdr_mode);
+ ret = fwnode_property_read_u32(link_fwnode, "ti,cdr-mode", &cdr_mode);
if (ret == -EINVAL) {
cdr_mode = RXPORT_CDR_FPD3;
} else if (ret < 0) {
- dev_err(dev, "Failed to get RX port CDR mode: %d\n", ret);
+ dev_err(dev, "rx%u: failed to read 'ti,cdr-mode': %d\n", nport,
+ ret);
return ret;
}

if (cdr_mode > RXPORT_CDR_LAST) {
- dev_err(dev, "Bad RX port cdr %u\n", cdr_mode);
+ dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, 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);
+ if (!priv->hw_data->is_fpdlink4 && cdr_mode == RXPORT_CDR_FPD4) {
+ dev_err(dev, "rx%u: FPD-Link 4 CDR not supported\n", nport);
return -EINVAL;
}

rxport->cdr_mode = cdr_mode;

- ret = of_property_read_u32(np, "ti,rx-mode", &rx_mode);
+ ret = fwnode_property_read_u32(link_fwnode, "ti,rx-mode", &rx_mode);
if (ret < 0) {
- dev_err(dev, "Missing RX port RX mode: %d\n", ret);
+ dev_err(dev, "rx%u: failed to read 'ti,rx-mode': %d\n", nport,
+ ret);
return ret;
}

if (rx_mode > RXPORT_MODE_LAST) {
- dev_err(dev, "Bad RX port RX mode %u\n", rx_mode);
+ dev_err(dev, "rx%u: bad 'ti,rx-mode' %u\n", nport, rx_mode);
return -EINVAL;
}

@@ -3470,45 +3455,78 @@ static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv,
rxport->eq.aeq.eq_level_min = UB960_MIN_EQ_LEVEL;
rxport->eq.aeq.eq_level_max = UB960_MAX_EQ_LEVEL;

- ret = of_property_read_s32(np, "ti,strobe-pos", &strobe_pos);
+ ret = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos",
+ &strobe_pos);
if (ret) {
if (ret != -EINVAL) {
- dev_err(dev, "Failed to read 'ti,strobe-pos': %d\n", ret);
+ dev_err(dev,
+ "rx%u: failed to read 'ti,strobe-pos': %d\n",
+ nport, 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);
+ dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", nport,
+ strobe_pos);
} else {
// NOTE: ignored unless global manual strobe pos is set
rxport->eq.strobe_pos = strobe_pos;
if (!priv->strobe.manual)
- dev_warn(dev, "'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n");
+ dev_warn(dev,
+ "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
+ nport);
}

- ret = of_property_read_u32(np, "ti,eq-level", &eq_level);
+ ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
if (ret) {
if (ret != -EINVAL) {
- dev_err(dev, "Failed to read 'ti,eq-level': %d\n", ret);
+ dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
+ nport, ret);
return ret;
}
} else if (eq_level > UB960_MAX_EQ_LEVEL) {
- dev_err(dev, "Illegal 'ti,eq-level' value: %d\n", eq_level);
+ dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
+ eq_level);
} else {
rxport->eq.manual_eq = true;
rxport->eq.manual.eq_level = eq_level;
}

+ ret = fwnode_property_read_u32(link_fwnode, "i2c-alias",
+ &ser_i2c_alias);
+ if (ret) {
+ dev_err(dev, "rx%u: failed to read 'i2c-alias': %d\n", nport,
+ ret);
+ return ret;
+ }
+ rxport->ser_alias = ser_i2c_alias;
+
+ rxport->remote_fwnode = fwnode_get_named_child_node(link_fwnode, "serializer");
+ if (!rxport->remote_fwnode) {
+ dev_err(dev, "rx%u: missing 'serializer' node\n", nport);
+ return -EINVAL;
+ }
+
return 0;
}

-static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv, struct ub960_rxport *rxport)
+static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv,
+ struct fwnode_handle *ep_fwnode,
+ struct ub960_rxport *rxport)
{
struct device *dev = &priv->client->dev;
- struct device_node *ep_np;
+ unsigned int nport = rxport->nport;
int ret;
u32 v;

+ rxport->source_ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode);
+ if (!rxport->source_ep_fwnode) {
+ dev_err(dev, "rx%u: no remote endpoint\n", nport);
+ return -ENODEV;
+ }
+
+ /* We currently have properties only for RAW modes */
+
switch (rxport->rx_mode) {
case RXPORT_MODE_RAW10:
case RXPORT_MODE_RAW12_HF:
@@ -3518,59 +3536,40 @@ static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv, struct u
return 0;
}

- ep_np = of_graph_get_endpoint_by_regs(priv->client->dev.of_node,
- rxport->nport, 0);
- if (!ep_np)
- return -EINVAL;
-
- ret = of_property_read_u32(ep_np, "hsync-active", &v);
+ ret = fwnode_property_read_u32(ep_fwnode, "hsync-active", &v);
if (ret) {
- dev_err(dev, "Failed to parse 'hsync-active' for port %u: %d\n",
- rxport->nport, ret);
- goto err;
+ dev_err(dev, "rx%u: failed to parse 'hsync-active': %d\n",
+ nport, ret);
+ goto err_put_source_ep_fwnode;
}

rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_LV_POL_LOW : 0;

- ret = of_property_read_u32(ep_np, "vsync-active", &v);
+ ret = fwnode_property_read_u32(ep_fwnode, "vsync-active", &v);
if (ret) {
- dev_err(dev, "Failed to parse 'vsync-active' for port %u: %d\n",
- rxport->nport, ret);
- goto err;
+ dev_err(dev, "rx%u: failed to parse 'vsync-active': %d\n",
+ nport, ret);
+ goto err_put_source_ep_fwnode;
}

rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_FV_POL_LOW : 0;

return 0;
-err:
- of_node_put(ep_np);
+
+err_put_source_ep_fwnode:
+ fwnode_handle_put(rxport->source_ep_fwnode);
return ret;
}

-static int ub960_parse_dt_rxport(struct ub960_data *priv,
- struct device_node *np)
+static int ub960_parse_dt_rxport(struct ub960_data *priv, unsigned int nport,
+ struct fwnode_handle *link_fwnode,
+ struct fwnode_handle *ep_fwnode)
{
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;
- }
+ int ret;

rxport = kzalloc(sizeof(*rxport), GFP_KERNEL);
if (!rxport)
@@ -3581,92 +3580,56 @@ static int ub960_parse_dt_rxport(struct ub960_data *priv,
rxport->nport = nport;
rxport->priv = priv;

- ret = ub960_parse_dt_rxport_link_properties(priv, np, rxport);
+ ret = ub960_parse_dt_rxport_link_properties(priv, link_fwnode, 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;
+ dev_err(dev, "rx%u: failed to get VPOC supply: %d\n",
+ nport, ret);
+ goto err_put_remote_fwnode;
}
}

- 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;
-
- ret = ub960_parse_dt_rxport_ep_properties(priv, rxport);
+ ret = ub960_parse_dt_rxport_ep_properties(priv, ep_fwnode, rxport);
if (ret)
- goto err_remote_ep_node_put;
+ goto err_put_remote_fwnode;

return 0;

-err_remote_ep_node_put:
- fwnode_handle_put(rxport->fwnode);
-err_ser_node_put:
- of_node_put(rxport->remote_of_node);
+err_put_remote_fwnode:
+ fwnode_handle_put(rxport->remote_fwnode);
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)
+static struct fwnode_handle *
+ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode,
+ unsigned int nport)
{
- struct device_node *link_np;
+ struct fwnode_handle *link_fwnode;
int ret;

- for_each_child_of_node(links_np, link_np) {
+ fwnode_for_each_child_node(links_fwnode, link_fwnode) {
u32 link_num;

- if (!of_node_name_eq(link_np, "link"))
+ if (strncmp(fwnode_get_name(link_fwnode), "link@", 5) != 0)
continue;

- ret = of_property_read_u32(link_np, "reg", &link_num);
+ ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num);
if (ret)
return NULL;

if (nport == link_num) {
- of_node_get(link_np);
- return link_np;
+ fwnode_handle_put(link_fwnode);
+ return link_fwnode;
}
}

@@ -3675,15 +3638,14 @@ ub960_of_get_link_by_regs(struct device_node *links_np, unsigned int nport)

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;
+ struct fwnode_handle *links_fwnode;
unsigned int nport;
int ret;

- links_np = of_get_child_by_name(np, "links");
- if (!links_np) {
- dev_err(dev, "links node missing\n");
+ links_fwnode = fwnode_get_named_child_node(dev_fwnode(dev), "links");
+ if (!links_fwnode) {
+ dev_err(dev, "'links' node missing\n");
return -ENODEV;
}

@@ -3691,51 +3653,62 @@ static int ub960_parse_dt_rxports(struct ub960_data *priv)
priv->strobe.min = 2;
priv->strobe.max = 3;

- priv->strobe.manual = of_property_read_bool(links_np, "ti,manual-strobe");
+ priv->strobe.manual = fwnode_property_read_bool(links_fwnode, "ti,manual-strobe");

for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
- struct device_node *link_np;
+ struct fwnode_handle *link_fwnode;
+ struct fwnode_handle *ep_fwnode;

- link_np = ub960_of_get_link_by_regs(links_np, nport);
- if (!link_np)
+ link_fwnode = ub960_fwnode_get_link_by_regs(links_fwnode, nport);
+ if (!link_fwnode)
continue;

- ret = ub960_parse_dt_rxport(priv, link_np);
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ nport, 0, 0);
+ if (!ep_fwnode) {
+ fwnode_handle_put(link_fwnode);
+ continue;
+ }
+
+ ret = ub960_parse_dt_rxport(priv, nport, link_fwnode,
+ ep_fwnode);

- of_node_put(link_np);
+ fwnode_handle_put(link_fwnode);
+ fwnode_handle_put(ep_fwnode);

if (ret) {
- dev_err(dev, "Failed to parse link node\n");
+ dev_err(dev, "rx%u: failed to parse RX port\n", nport);
goto err_put_links;
}
}

- of_node_put(links_np);
+ fwnode_handle_put(links_fwnode);

return 0;

err_put_links:
- of_node_put(links_np);
+ fwnode_handle_put(links_fwnode);

return ret;
}

static int ub960_parse_dt_txports(struct ub960_data *priv)
{
- struct device_node *np = priv->client->dev.of_node;
+ struct device *dev = &priv->client->dev;
u32 nport;
int ret;

for (nport = 0; nport < priv->hw_data->num_txports; ++nport) {
- struct device_node *ep_np;
+ struct fwnode_handle *ep_fwnode;

- ep_np = of_graph_get_endpoint_by_regs(np, nport + priv->hw_data->num_rxports, 0);
- if (!ep_np)
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ nport + priv->hw_data->num_rxports, 0, 0);
+ if (!ep_fwnode)
continue;

- ret = ub960_parse_dt_txport(priv, ep_np, nport);
+ ret = ub960_parse_dt_txport(priv, ep_fwnode, nport);

- of_node_put(ep_np);
+ fwnode_handle_put(ep_fwnode);

if (ret)
break;
@@ -3746,15 +3719,8 @@ static int ub960_parse_dt_txports(struct ub960_data *priv)

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;
@@ -3783,35 +3749,37 @@ static int ub960_notify_bound(struct v4l2_async_notifier *notifier,
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;

- ret = media_entity_get_fwnode_pad(&subdev->entity, rxport->fwnode,
+ ret = media_entity_get_fwnode_pad(&subdev->entity,
+ rxport->source_ep_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;
+ rxport->source_sd = subdev;
+ rxport->source_sd_pad = ret;

- ret = media_create_pad_link(&rxport->sd->entity, src_pad,
- &priv->sd.entity, nport,
+ ret = media_create_pad_link(&rxport->source_sd->entity,
+ rxport->source_sd_pad, &priv->sd.entity,
+ nport,
MEDIA_LNK_FL_ENABLED |
- MEDIA_LNK_FL_IMMUTABLE);
+ 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);
+ rxport->source_sd->name, rxport->source_sd_pad,
+ priv->sd.name, nport);
return ret;
}

- dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name, src_pad,
- nport);
+ dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name,
+ rxport->source_sd_pad, nport);

for (i = 0; i < priv->hw_data->num_rxports; ++i) {
- if (priv->rxports[i] && !priv->rxports[i]->sd) {
+ if (priv->rxports[i] && !priv->rxports[i]->source_sd) {
dev_dbg(dev, "Waiting for more subdevs to be bound\n");
return 0;
}
@@ -3828,7 +3796,7 @@ static void ub960_notify_unbind(struct v4l2_async_notifier *notifier,
{
struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport;

- rxport->sd = NULL;
+ rxport->source_sd = NULL;
}

static const struct v4l2_async_notifier_operations ub960_notify_ops = {
@@ -3851,7 +3819,8 @@ static int ub960_v4l2_notifier_register(struct ub960_data *priv)
if (!rxport)
continue;

- asd = v4l2_async_nf_add_fwnode(&priv->notifier, rxport->fwnode,
+ asd = v4l2_async_nf_add_fwnode(&priv->notifier,
+ rxport->source_ep_fwnode,
struct ub960_asd);
if (IS_ERR(asd)) {
dev_err(dev, "Failed to add subdev for source %u: %ld",
@@ -3906,14 +3875,15 @@ static int ub960_create_subdev(struct ub960_data *priv)
goto err_free_ctrl;
}

- priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS |
- V4L2_SUBDEV_FL_STREAMS;
+ 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;
+ MEDIA_PAD_FL_SINK :
+ MEDIA_PAD_FL_SOURCE;
}

ret = media_entity_pads_init(&priv->sd.entity,
@@ -4007,35 +3977,27 @@ static void ub960_reset(struct ub960_data *priv, bool reset_regs)
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);
- }
+ if (IS_ERR(priv->vddio))
+ return dev_err_probe(dev, PTR_ERR(priv->vddio),
+ "cannot get VDDIO regulator\n");

/* 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;
- }
+ if (IS_ERR(priv->pd_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->pd_gpio),
+ "Cannot get powerdown GPIO\n");

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;
- }
+ if (IS_ERR(priv->refclk))
+ return dev_err_probe(dev, PTR_ERR(priv->refclk),
+ "Cannot get REFCLK\n");

return 0;
}
@@ -4056,7 +4018,7 @@ static int ub960_enable_core_hw(struct ub960_data *priv)

ret = clk_prepare_enable(priv->refclk);
if (ret) {
- dev_err(dev, "Failed to renable refclk: %d\n", ret);
+ dev_err(dev, "Failed to enable refclk: %d\n", ret);
goto err_disable_vddio;
}

@@ -4159,7 +4121,7 @@ static int ub960_probe(struct i2c_client *client)
goto err_mutex_destroy;

/* release GPIO lock */
- if (priv->hw_data->ub9702)
+ if (priv->hw_data->is_ub9702)
ub960_update_bits(priv, UB960_SR_RESET,
UB960_SR_RESET_GPIO_LOCK_RELEASE,
UB960_SR_RESET_GPIO_LOCK_RELEASE);
@@ -4255,25 +4217,23 @@ static const struct ub960_hw_data ds90ub9702_hw = {
.model = "ub9702",
.num_rxports = 4,
.num_txports = 2,
- .ub9702 = true,
- .fpdlink4 = true,
+ .is_ub9702 = true,
+ .is_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,
@@ -4282,14 +4242,13 @@ static struct i2c_driver ds90ub960_driver = {
.driver = {
.name = "ds90ub960",
.owner = THIS_MODULE,
- .of_match_table = of_match_ptr(ub960_dt_ids),
+ .of_match_table = ub960_dt_ids,
},
};
-
module_i2c_driver(ds90ub960_driver);

MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("Texas Instruments DS90UB960-Q1 FPD-Link deserializer driver");
+MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV Deserializers Driver");
MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
MODULE_IMPORT_NS(I2C_ATR);
diff --git a/include/linux/i2c-atr.h b/include/linux/i2c-atr.h
index 5b879115dfc6..10a313ab2105 100644
--- a/include/linux/i2c-atr.h
+++ b/include/linux/i2c-atr.h
@@ -35,7 +35,6 @@ 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);
diff --git a/include/linux/i2c.h b/include/linux/i2c.h
index 9a385b6de388..f7c49bbdb8a1 100644
--- a/include/linux/i2c.h
+++ b/include/linux/i2c.h
@@ -584,21 +584,6 @@ 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
@@ -741,7 +726,6 @@ 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;


base-commit: 76dcd734eca23168cb008912c0f69ff408905235
prerequisite-patch-id: 341bbdcbd14f0b6f38c88f1390f9437873a03430
prerequisite-patch-id: e5d34269150d94ecb7fd670ad31e14aaae78e099
prerequisite-patch-id: 24ff7b1c013fa9acd8b93c2506eae39eb2a1021c
prerequisite-patch-id: 15edf6091d7c2d340f25711961bb8459addc8d58
prerequisite-patch-id: 9beff6fd1d22dbfe565931de81b27739e45ced05
prerequisite-patch-id: 37bc00c7315371c9a906af0d718ecedd871a43e0
prerequisite-patch-id: 8218595c50635ab48a9c8cd8c580da9f431d539f
prerequisite-patch-id: 7f163276a04d212d009ee6e96fb545d5b781cc44
prerequisite-patch-id: 0db299d92565612520411ef624e097131feb2d97
prerequisite-patch-id: 6b521ad1868b9a507930dd6a2e0e2d19b5947456
prerequisite-patch-id: d32e1105a1285cceac8fd8e84649070174a6b23e
prerequisite-patch-id: 2927a4ef079c55e7807cda150d4b9eb29978136a
prerequisite-patch-id: 77546a2e202557910e9a3635534163cc80134ea4
prerequisite-patch-id: 462fabb2b21019636e1f7e793dafc51caf40400e
prerequisite-patch-id: 604c8c3f3e7fab7698eb73795bae573f88b808db
prerequisite-patch-id: 75a3d7b072e718fb2bd854743b9b2023d68a822b
prerequisite-patch-id: a9c64a3360938ac230261ffc3e0cbd8cb66550d3
prerequisite-patch-id: a5c57341047e53ecaeb28181319700dd3ace62aa
prerequisite-patch-id: e9b8da97c9fea3cd3268adf8387cb3df179649e5
prerequisite-patch-id: 2767b8af9e0742b52b81b2dbce3b5ce5203e3610
--
2.34.1


2023-01-18 13:31:36

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 6/7] media: i2c: add DS90UB913 driver

Add driver for TI DS90UB913 FPD-Link III Serializer.

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

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index dc1c7c80dc1c..e0a1c2a5f3bf 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1601,6 +1601,19 @@ endmenu

menu "Video serializers and deserializers"

+config VIDEO_DS90UB913
+ tristate "TI DS90UB913 FPD-Link III Serializer"
+ depends on OF && I2C && VIDEO_DEV
+ select I2C_ATR
+ select MEDIA_CONTROLLER
+ select OF_GPIO
+ select REGMAP_I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ help
+ Device driver for the Texas Instruments DS90UB913
+ FPD-Link III Serializer.
+
config VIDEO_DS90UB960
tristate "TI FPD-Link III/IV Deserializers"
depends on OF && I2C && VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 4cd680f3b953..c4875ec8c3b9 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -142,4 +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_DS90UB913) += ds90ub913.o
obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o
diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
new file mode 100644
index 000000000000..befa78128a9a
--- /dev/null
+++ b/drivers/media/i2c/ds90ub913.c
@@ -0,0 +1,848 @@
+// 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) 2023 Tomi Valkeinen <[email protected]>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/fwnode.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/property.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_GENERAL_CFG_CRC_ERR_RESET BIT(5)
+#define UB913_REG_GENERAL_CFG_PCLK_RISING BIT(0)
+
+#define UB913_REG_MODE_SEL 0x05
+#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5)
+#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4)
+#define UB913_REG_MODE_SEL_MODE_MASK GENMASK(3, 0)
+
+#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;
+
+ struct gpio_chip gpio_chip;
+
+ struct v4l2_subdev sd;
+ struct media_pad pads[2];
+
+ struct v4l2_async_notifier notifier;
+
+ struct v4l2_subdev *source_sd;
+ u16 source_sd_pad;
+
+ u64 enabled_source_streams;
+
+ struct clk_hw *clkout_clk_hw;
+
+ struct ds90ub9xx_platform_data *plat_data;
+
+ u32 pclk_polarity;
+};
+
+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 = offset / 2;
+ unsigned int field_idx = offset % 2;
+
+ return 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));
+}
+
+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);
+
+ gc->label = dev_name(dev);
+ gc->parent = dev;
+ gc->owner = THIS_MODULE;
+ gc->base = -1;
+ gc->can_sleep = true;
+ 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_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 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);
+ u64 sink_streams;
+ int ret;
+
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
+ UB913_PAD_SINK,
+ &streams_mask);
+
+ ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
+ 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);
+ u64 sink_streams;
+ int ret;
+
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
+ UB913_PAD_SINK,
+ &streams_mask);
+
+ ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
+ 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)
+{
+ static 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_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;
+
+ if (pad != UB913_PAD_SOURCE)
+ return -EINVAL;
+
+ ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
+ priv->source_sd_pad, &source_fd);
+ if (ret)
+ return ret;
+
+ memset(fd, 0, sizeof(*fd));
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ routing = &state->routing;
+
+ for_each_active_route(routing, route) {
+ unsigned int i;
+
+ if (route->source_pad != pad)
+ continue;
+
+ for (i = 0; i < source_fd.num_entries; ++i) {
+ if (source_fd.entry[i].stream == route->sink_stream)
+ break;
+ }
+
+ if (i == 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 = source_fd.entry[i].flags;
+ fd->entry[fd->num_entries].length = source_fd.entry[i].length;
+ fd->entry[fd->num_entries].pixelcode =
+ source_fd.entry[i].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 == UB913_PAD_SOURCE)
+ return v4l2_subdev_get_fmt(sd, state, format);
+
+ /* Set sink format */
+ fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
+ format->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = format->format;
+
+ /* Propagate to source format */
+ fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+ format->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = format->format;
+
+ return 0;
+}
+
+static int ub913_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct v4l2_subdev_route routes[] = {
+ {
+ .sink_pad = UB913_PAD_SINK,
+ .sink_stream = 0,
+ .source_pad = UB913_PAD_SOURCE,
+ .source_stream = 0,
+ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
+ },
+ };
+
+ struct v4l2_subdev_krouting routing = {
+ .num_routes = ARRAY_SIZE(routes),
+ .routes = routes,
+ };
+
+ return _ub913_set_routing(sd, state, &routing);
+}
+
+static int ub913_log_status(struct v4l2_subdev *sd)
+{
+ struct ub913_data *priv = sd_to_ub913(sd);
+ struct device *dev = &priv->client->dev;
+ u8 v, v1, v2;
+
+ ub913_read(priv, UB913_REG_MODE_SEL, &v);
+ dev_info(dev, "MODE_SEL %#02x\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));
+
+ /* clear CRC errors */
+ ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
+ ub913_write(priv, UB913_REG_GENERAL_CFG,
+ v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET);
+ ub913_write(priv, UB913_REG_GENERAL_CFG, v);
+
+ ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
+ dev_info(dev, "GENERAL_STATUS %#02x\n", v);
+
+ ub913_read(priv, UB913_REG_PLL_OVR, &v);
+ dev_info(dev, "PLL_OVR %#02x\n", 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;
+ int ret;
+
+ 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;
+ priv->source_sd_pad = ret;
+
+ ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
+ &priv->sd.entity, UB913_PAD_SINK,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+ if (ret) {
+ dev_err(dev, "Unable to link %s:%u -> %s:0\n",
+ source_subdev->name, priv->source_sd_pad,
+ priv->sd.name);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_async_notifier_operations ub913_notify_ops = {
+ .bound = ub913_notify_bound,
+};
+
+static int ub913_v4l2_notifier_register(struct ub913_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ struct v4l2_async_subdev *asd;
+ struct fwnode_handle *ep_fwnode;
+ int ret;
+
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB913_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
+ dev_err(dev, "No graph endpoint\n");
+ return -ENODEV;
+ }
+
+ v4l2_async_nf_init(&priv->notifier);
+
+ asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
+ struct v4l2_async_subdev);
+
+ fwnode_handle_put(ep_fwnode);
+
+ 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)
+{
+ 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));
+ if (!name)
+ return -ENOMEM;
+
+ 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;
+
+ return 0;
+}
+
+static int ub913_parse_dt(struct ub913_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ struct fwnode_handle *ep_fwnode;
+ int ret;
+
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB913_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
+ dev_err(dev, "No sink endpoint\n");
+ return -ENOENT;
+ }
+
+ ret = fwnode_property_read_u32(ep_fwnode, "pclk-sample",
+ &priv->pclk_polarity);
+
+ fwnode_handle_put(ep_fwnode);
+
+ if (ret) {
+ dev_err(dev, "OF: failed to parse pclk-sample: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ub913_hw_init(struct ub913_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ bool mode_override;
+ u8 mode;
+ int ret;
+ u8 v;
+
+ ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
+ if (ret)
+ return ret;
+
+ if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE))
+ return dev_err_probe(dev, -ENODEV,
+ "Mode value not stabilized\n");
+
+ mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE;
+ mode = v & UB913_REG_MODE_SEL_MODE_MASK;
+
+ dev_dbg(dev, "mode from %s: %#x\n",
+ mode_override ? "reg" : "deserializer", mode);
+
+ ret = ub913_i2c_master_init(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "i2c master init failed\n");
+
+ ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
+ v &= ~UB913_REG_GENERAL_CFG_PCLK_RISING;
+ v |= priv->pclk_polarity ? UB913_REG_GENERAL_CFG_PCLK_RISING : 0;
+ ub913_write(priv, UB913_REG_GENERAL_CFG, v);
+
+ return 0;
+}
+
+static int ub913_subdev_init(struct ub913_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ int ret;
+
+ 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)
+ return dev_err_probe(dev, ret, "Failed to init pads\n");
+
+ priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB913_PAD_SOURCE, 0,
+ 0);
+
+ if (!priv->sd.fwnode) {
+ ret = -ENODEV;
+ dev_err_probe(dev, ret, "Missing TX endpoint\n");
+ goto err_entity_cleanup;
+ }
+
+ ret = v4l2_subdev_init_finalize(&priv->sd);
+ if (ret)
+ goto err_entity_cleanup;
+
+ ret = ub913_v4l2_notifier_register(priv);
+ if (ret) {
+ dev_err_probe(dev, ret,
+ "v4l2 subdev notifier register failed\n");
+ goto err_subdev_cleanup;
+ }
+
+ ret = v4l2_async_register_subdev(&priv->sd);
+ if (ret) {
+ dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
+ goto err_unreg_notif;
+ }
+
+ return 0;
+
+err_unreg_notif:
+ ub913_v4l2_nf_unregister(priv);
+err_subdev_cleanup:
+ v4l2_subdev_cleanup(&priv->sd);
+err_entity_cleanup:
+ media_entity_cleanup(&priv->sd.entity);
+
+ return ret;
+}
+
+static void ub913_subdev_uninit(struct ub913_data *priv)
+{
+ v4l2_async_unregister_subdev(&priv->sd);
+ ub913_v4l2_nf_unregister(priv);
+ v4l2_subdev_cleanup(&priv->sd);
+ media_entity_cleanup(&priv->sd.entity);
+}
+
+static int ub913_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ub913_data *priv;
+ int ret;
+
+ 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)
+ return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
+
+ priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
+ if (IS_ERR(priv->regmap))
+ return dev_err_probe(dev, PTR_ERR(priv->regmap),
+ "Failed to init regmap\n");
+
+ /*
+ * ub913 can also work without ext clock, but that is not supported by
+ * the driver yet
+ */
+ priv->clkin = devm_clk_get(dev, "clkin");
+ if (IS_ERR(priv->clkin))
+ return dev_err_probe(dev, PTR_ERR(priv->clkin),
+ "Cannot get CLKIN\n");
+
+ ret = ub913_parse_dt(priv);
+ if (ret)
+ return ret;
+
+ ret = ub913_hw_init(priv);
+ if (ret)
+ return ret;
+
+ ret = ub913_gpiochip_probe(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init gpiochip\n");
+
+ ret = ub913_register_clkout(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register clkout\n");
+ goto err_gpiochip_remove;
+ }
+
+ ret = ub913_subdev_init(priv);
+ if (ret)
+ goto err_gpiochip_remove;
+
+ ret = ub913_add_i2c_adapter(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
+ goto err_subdev_uninit;
+ }
+
+ return 0;
+
+err_subdev_uninit:
+ ub913_subdev_uninit(priv);
+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);
+
+ i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port);
+
+ ub913_subdev_uninit(priv);
+
+ ub913_gpiochip_remove(priv);
+}
+
+static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} };
+MODULE_DEVICE_TABLE(i2c, ub913_id);
+
+static const struct of_device_id ub913_dt_ids[] = {
+ { .compatible = "ti,ds90ub913a-q1" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ub913_dt_ids);
+
+static struct i2c_driver ds90ub913_driver = {
+ .probe_new = ub913_probe,
+ .remove = ub913_remove,
+ .id_table = ub913_id,
+ .driver = {
+ .name = "ds90ub913a",
+ .of_match_table = ub913_dt_ids,
+ },
+};
+module_i2c_driver(ds90ub913_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments DS90UB913 FPD-Link III Serializer Driver");
+MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
+MODULE_IMPORT_NS(I2C_ATR);
--
2.34.1

2023-01-18 13:34:47

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 2/7] dt-bindings: media: add TI DS90UB913 FPD-Link III Serializer

Add DT bindings for TI DS90UB913 FPD-Link III Serializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
Reviewed-by: Rob Herring <[email protected]>
---
.../bindings/media/i2c/ti,ds90ub913.yaml | 133 ++++++++++++++++++
1 file changed, 133 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..f6612bb0f667
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml
@@ -0,0 +1,133 @@
+# 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 III Serializer
+
+maintainers:
+ - Tomi Valkeinen <[email protected]>
+
+description:
+ The TI DS90UB913 is an FPD-Link III video serializer for parallel video.
+
+properties:
+ compatible:
+ enum:
+ - ti,ds90ub913a-q1
+
+ '#gpio-cells':
+ const: 2
+ description:
+ First cell is the GPO pin number, second cell is the flags. The GPO pin
+ number must be in range of [0, 3]. Note that GPOs 2 and 3 are not
+ available in external oscillator mode.
+
+ gpio-controller: true
+
+ clocks:
+ maxItems: 1
+ description:
+ Reference clock connected to the CLKIN pin.
+
+ clock-names:
+ items:
+ - const: clkin
+
+ '#clock-cells':
+ const: 0
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: Parallel input port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ required:
+ - pclk-sample
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ unevaluatedProperties: false
+ description: FPD-Link III output port
+
+ required:
+ - port@0
+ - port@1
+
+ 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>;
+ pclk-sample = <1>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ endpoint {
+ remote-endpoint = <&deser_fpd_in>;
+ };
+ };
+ };
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sensor@48 {
+ compatible = "aptina,mt9v111";
+ reg = <0x48>;
+
+ clocks = <&fixed_clock>;
+
+ port {
+ sensor_out: endpoint {
+ remote-endpoint = <&ub913_in>;
+ };
+ };
+ };
+ };
+ };
+...
--
2.34.1

2023-01-18 13:38:18

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

Add driver for TI DS90UB960 FPD-Link III Deserializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
---
MAINTAINERS | 8 +
drivers/media/i2c/Kconfig | 21 +
drivers/media/i2c/Makefile | 1 +
drivers/media/i2c/ds90ub960.c | 4254 +++++++++++++++++++++++++++++++++
include/media/i2c/ds90ub9xx.h | 16 +
5 files changed, 4300 insertions(+)
create mode 100644 drivers/media/i2c/ds90ub960.c
create mode 100644 include/media/i2c/ds90ub9xx.h

diff --git a/MAINTAINERS b/MAINTAINERS
index ba716f2861cf..d0dc8572191d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20626,6 +20626,14 @@ F: drivers/misc/tifm*
F: drivers/mmc/host/tifm_sd.c
F: include/linux/tifm.h

+TI FPD-LINK DRIVERS
+M: Tomi Valkeinen <[email protected]>
+L: [email protected]
+S: Maintained
+F: Documentation/devicetree/bindings/media/i2c/ti,ds90*
+F: drivers/media/i2c/ds90*
+F: include/media/i2c/ds90*
+
TI KEYSTONE MULTICORE NAVIGATOR DRIVERS
M: Nishanth Menon <[email protected]>
M: Santosh Shilimkar <[email protected]>
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index 7806d4b81716..dc1c7c80dc1c 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1595,4 +1595,25 @@ config VIDEO_THS7303

endmenu

+#
+# Video serializers and deserializers (e.g. FPD-Link)
+#
+
+menu "Video serializers and deserializers"
+
+config VIDEO_DS90UB960
+ tristate "TI FPD-Link III/IV Deserializers"
+ depends on OF && I2C && VIDEO_DEV
+ select I2C_ATR
+ select MEDIA_CONTROLLER
+ select OF_GPIO
+ select REGMAP_I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ help
+ Device driver for the Texas Instruments DS90UB960
+ FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer.
+
+endmenu
+
endif # VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index 0a2933103dd9..4cd680f3b953 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -142,3 +142,4 @@ 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..2cefb3744901
--- /dev/null
+++ b/drivers/media/i2c/ds90ub960.c
@@ -0,0 +1,4254 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Texas Instruments DS90UB960-Q1 video deserializer
+ *
+ * Copyright (c) 2019 Luca Ceresoli <[email protected]>
+ * Copyright (c) 2023 Tomi Valkeinen <[email protected]>
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/fwnode.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_device.h>
+#include <linux/property.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_RESET_DIGITAL_RESET1 BIT(1)
+#define UB960_SR_RESET_DIGITAL_RESET0 BIT(0)
+#define UB960_SR_RESET_GPIO_LOCK_RELEASE BIT(5)
+
+#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_GPIO_PIN_CTL_GPIO_OUT_SEL 5
+#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SRC_SHIFT 2
+#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_EN BIT(0)
+
+#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_SFILTER_CFG_SFILTER_MAX_SHIFT 4
+#define UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT 0
+
+#define UB960_XR_AEQ_CTL1 0x42
+#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_SHIFT 4
+#define UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN BIT(0)
+
+#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_CONFIG2_LV_POL_LOW BIT(1)
+#define UB960_RR_PORT_CONFIG2_FV_POL_LOW BIT(0)
+
+#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_CTL2_SET_AEQ_FLOOR BIT(2)
+
+#define UB960_RR_AEQ_STATUS 0xD3
+
+#define UB960_RR_AEQ_BYPASS 0xD4
+#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT 5
+#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK GENMASK(7, 5)
+#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT 1
+#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK GENMASK(3, 1)
+#define UB960_RR_AEQ_BYPASS_ENABLE BIT(0)
+
+#define UB960_RR_AEQ_MIN_MAX 0xD5
+#define UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT 4
+#define UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT 0
+
+#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_CLK_NO_EXTRA_DELAY BIT(3)
+#define UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK GENMASK(2, 0)
+
+#define UB960_IR_RX_ANA_STROBE_SET_DATA 0x09
+#define UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(3)
+#define UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK GENMASK(2, 0)
+
+/* 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 is_ub9702;
+ bool is_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 *source_sd; /* Connected subdev */
+ u16 source_sd_pad;
+ struct fwnode_handle *source_ep_fwnode;
+
+ enum ub960_rxport_mode rx_mode;
+ enum ub960_rxport_cdr cdr_mode;
+
+ struct fwnode_handle *remote_fwnode; /* 'serializer' fwnode */
+ struct i2c_client *ser_client; /* Serializer */
+ unsigned short ser_alias; /* Serializer i2c alias (lower 7 bits) */
+
+ u8 lv_fv_pol; /* LV and FV polarities */
+
+ 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) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
+ block, reg, ret);
+ goto out;
+ }
+
+ ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
+ block, reg, 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) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n",
+ block, reg, ret);
+ goto out;
+ }
+
+ ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n",
+ block, reg, 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_client *client,
+ u16 *alias_id)
+{
+ struct ub960_data *priv = i2c_atr_get_driver_data(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%u: %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%u: 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%u: 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%u: 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_driver_data(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%u: %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%u: 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%u: 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 fwnode_handle *ep_fwnode,
+ u8 nport)
+{
+ struct device *dev = &priv->client->dev;
+ struct ub960_txport *txport;
+ int ret;
+ u64 freq;
+
+ txport = kzalloc(sizeof(*txport), GFP_KERNEL);
+ if (!txport)
+ return -ENOMEM;
+
+ txport->priv = priv;
+ txport->nport = nport;
+
+ priv->txports[nport] = txport;
+
+ ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
+ if (ret <= 0) {
+ dev_err(dev, "tx%u: failed to parse 'data-lanes': %d\n", nport,
+ ret);
+ goto err_free_txport;
+ }
+
+ txport->num_data_lanes = ret;
+
+ ret = fwnode_property_count_u64(ep_fwnode, "link-frequencies");
+ if (ret < 0) {
+ dev_err(dev, "tx%u: failed to parse 'link-frequencies': %d\n",
+ nport, ret);
+ goto err_free_txport;
+ }
+
+ if (ret != 1) {
+ dev_err(dev,
+ "tx%u: 'link-frequencies' must contain a single frequency: %d\n",
+ nport, ret);
+ goto err_free_txport;
+ }
+
+ ret = fwnode_property_read_u64(ep_fwnode, "link-frequencies", &freq);
+ if (ret) {
+ dev_err(dev, "tx%u: failed to read 'link-frequencies': %d\n",
+ nport, ret);
+ goto err_free_txport;
+ }
+
+ 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, "tx%u: invalid 'link-frequencies' value\n", nport);
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "tx%u: nominal data rate: %u", nport, 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 int ub960_rxport_enable_vpocs(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 || !rxport->vpoc)
+ continue;
+
+ ret = regulator_enable(rxport->vpoc);
+ if (ret)
+ goto err_disable_vpocs;
+ }
+
+ return 0;
+
+err_disable_vpocs:
+ for (; nport > 0; --nport) {
+ struct ub960_rxport *rxport = priv->rxports[nport - 1];
+
+ if (!rxport || !rxport->vpoc)
+ continue;
+
+ regulator_disable(rxport->vpoc);
+ }
+
+ return ret;
+}
+
+static void ub960_rxport_disable_vpocs(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 || !rxport->vpoc)
+ continue;
+
+ regulator_disable(rxport->vpoc);
+ }
+}
+
+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 & UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY ? 0 : 6;
+
+ ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport),
+ UB960_IR_RX_ANA_STROBE_SET_DATA, &v);
+
+ data_delay = v & UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY ? 0 : 6;
+
+ ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v);
+ if (ret)
+ return ret;
+
+ clk_delay += v & UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK;
+
+ ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_1, &v);
+ if (ret)
+ return ret;
+
+ data_delay += v & UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK;
+
+ *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 = UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
+ data_delay = UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
+
+ 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) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
+ else if (strobe_pos > 0)
+ data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
+
+ 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 << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) |
+ ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT));
+}
+
+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 &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
+ UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK);
+ v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT;
+ v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT;
+ v |= UB960_RR_AEQ_BYPASS_ENABLE; /* 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 << UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) |
+ (eq_max << UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT));
+
+ /* Enable AEQ min setting */
+ ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_CTL2,
+ UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR,
+ UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR);
+}
+
+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,
+ UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN, 0);
+ } else {
+ /* Enable SFILTER and error control */
+ ub960_write(priv, UB960_XR_AEQ_CTL1,
+ (0x7 << UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_SHIFT) |
+ UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN);
+
+ /* 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,
+ UB960_RR_AEQ_BYPASS_ENABLE,
+ UB960_RR_AEQ_BYPASS_ENABLE);
+ } 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,
+ UB960_RR_AEQ_BYPASS_ENABLE, 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_driver_data(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 = to_of_node(rxport->remote_fwnode),
+ .fwnode = rxport->remote_fwnode,
+ .platform_data = ser_pdata,
+ };
+
+ ser_pdata->port = nport;
+ ser_pdata->atr = priv->atr;
+ if (priv->hw_data->is_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%u: cannot add %s i2c device", nport,
+ ser_info.type);
+ return -EIO;
+ }
+
+ dev_dbg(dev, "rx%u: 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->is_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) {
+ 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;
+
+ default:
+ WARN_ON(true);
+ return;
+ }
+
+ 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,
+ rxport->lv_fv_pol);
+
+ /* 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);
+ return;
+ }
+
+ 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);
+ return;
+ }
+
+ 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->is_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;
+ u8 fwd_ctl;
+ struct {
+ u32 num_streams;
+ u8 pixel_dt;
+ u8 meta_dt;
+ u32 meta_lines;
+ u32 tx_port;
+ } rx_data[UB960_MAX_RX_NPORTS] = { 0 };
+ u8 vc_map[UB960_MAX_RX_NPORTS] = { 0 };
+
+ ub960_get_vc_maps(priv, state, vc_map);
+
+ for (unsigned int i = 0; i < routing->num_routes; ++i) {
+ struct v4l2_subdev_route *route = &routing->routes[i];
+ struct ub960_rxport *rxport;
+ struct ub960_txport *txport;
+ struct v4l2_mbus_framefmt *fmt;
+ const struct ub960_format_info *ub960_fmt;
+ unsigned int nport;
+
+ nport = ub960_pad_to_port(priv, route->sink_pad);
+
+ rxport = priv->rxports[nport];
+ if (!rxport)
+ return -EINVAL;
+
+ txport = priv->txports[ub960_pad_to_port(priv, route->source_pad)];
+ if (!txport)
+ return -EINVAL;
+
+ rx_data[nport].tx_port = ub960_pad_to_port(priv, route->source_pad);
+
+ rx_data[nport].num_streams++;
+
+ /* For the rest, we are only interested in parallel busses */
+ if (rxport->rx_mode == RXPORT_MODE_CSI2_SYNC ||
+ rxport->rx_mode == RXPORT_MODE_CSI2_ASYNC)
+ continue;
+
+ if (rx_data[nport].num_streams > 2)
+ return -EPIPE;
+
+ fmt = v4l2_subdev_state_get_stream_format(state,
+ route->sink_pad,
+ route->sink_stream);
+ if (!fmt)
+ return -EPIPE;
+
+ ub960_fmt = ub960_find_format(fmt->code);
+ if (!ub960_fmt)
+ return -EPIPE;
+
+ if (ub960_fmt->meta) {
+ if (fmt->height > 3) {
+ dev_err(&priv->client->dev,
+ "rx%u: unsupported metadata height %u\n",
+ nport, 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 (unsigned int 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->is_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) {
+ 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]);
+
+ ret = v4l2_subdev_enable_streams(
+ priv->rxports[nport]->source_sd,
+ priv->rxports[nport]->source_sd_pad,
+ 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) {
+ if (!sink_streams[nport])
+ continue;
+
+ dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport,
+ sink_streams[nport]);
+
+ ret = v4l2_subdev_disable_streams(
+ priv->rxports[nport]->source_sd,
+ priv->rxports[nport]->source_sd_pad,
+ 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) {
+ if (!sink_streams[nport])
+ continue;
+
+ dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport,
+ sink_streams[nport]);
+
+ ret = v4l2_subdev_disable_streams(
+ priv->rxports[nport]->source_sd,
+ priv->rxports[nport]->source_sd_pad,
+ 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_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 };
+
+ if (!ub960_pad_is_source(priv, pad))
+ return -EINVAL;
+
+ memset(fd, 0, sizeof(*fd));
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+ state = v4l2_subdev_lock_and_get_active_state(&priv->sd);
+
+ ub960_get_vc_maps(priv, state, vc_map);
+
+ routing = &state->routing;
+
+ for_each_active_route(routing, route) {
+ struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
+ struct v4l2_mbus_frame_desc source_fd;
+ unsigned int nport;
+ unsigned int i;
+
+ if (route->source_pad != pad)
+ continue;
+
+ nport = ub960_pad_to_port(priv, route->sink_pad);
+
+ ret = v4l2_subdev_call(priv->rxports[nport]->source_sd, pad,
+ get_frame_desc,
+ priv->rxports[nport]->source_sd_pad,
+ &source_fd);
+ if (ret) {
+ dev_err(dev,
+ "Failed to get source frame desc for pad %u\n",
+ route->sink_pad);
+ goto out;
+ }
+
+ for (i = 0; i < source_fd.num_entries; ++i)
+ if (source_fd.entry[i].stream == route->sink_stream) {
+ source_entry = &source_fd.entry[i];
+ 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 = source_entry->flags;
+ 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 = vc_map[nport];
+
+ 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;
+ char id[7];
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ for (unsigned int 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 %#02x\n", v);
+ ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v);
+ dev_info(dev, "\trx_port_sts2 %#02x\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 & UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN) ? "Adaptive" :
+ "Manual");
+
+ if (v & UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN) {
+ ub960_read(priv, UB960_XR_SFILTER_CFG, &v);
+
+ dev_info(dev, "\tStrobe range [%d, %d]\n",
+ ((v >> UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) & 0xf) - 7,
+ ((v >> UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT) & 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 & UB960_RR_AEQ_BYPASS_ENABLE) ? "Manual" :
+ "Adaptive");
+
+ if (!(v & UB960_RR_AEQ_BYPASS_ENABLE)) {
+ ub960_rxport_read(priv, nport, UB960_RR_AEQ_MIN_MAX, &v);
+
+ dev_info(dev, "\tEQ range [%u, %u]\n",
+ (v >> UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) & 0xf,
+ (v >> UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT) & 0xf);
+ }
+
+ if (ub960_rxport_get_eq_level(priv, nport, &eq_level) == 0)
+ 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 %#02x\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;
+
+ fwnode_handle_put(rxport->source_ep_fwnode);
+ fwnode_handle_put(rxport->remote_fwnode);
+
+ kfree(rxport);
+ priv->rxports[nport] = NULL;
+ }
+}
+
+static int ub960_parse_dt_base(struct ub960_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ size_t table_size;
+ u16 *aliases;
+ int ret;
+
+ ret = fwnode_property_count_u16(dev_fwnode(dev), "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 = fwnode_property_read_u16_array(dev_fwnode(dev), "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 fwnode_handle *link_fwnode,
+ struct ub960_rxport *rxport)
+{
+ struct device *dev = &priv->client->dev;
+ unsigned int nport = rxport->nport;
+ u32 rx_mode;
+ u32 cdr_mode;
+ s32 strobe_pos;
+ u32 eq_level;
+ u32 ser_i2c_alias;
+ int ret;
+
+ ret = fwnode_property_read_u32(link_fwnode, "ti,cdr-mode", &cdr_mode);
+ if (ret == -EINVAL) {
+ cdr_mode = RXPORT_CDR_FPD3;
+ } else if (ret < 0) {
+ dev_err(dev, "rx%u: failed to read 'ti,cdr-mode': %d\n", nport,
+ ret);
+ return ret;
+ }
+
+ if (cdr_mode > RXPORT_CDR_LAST) {
+ dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, cdr_mode);
+ return -EINVAL;
+ }
+
+ if (!priv->hw_data->is_fpdlink4 && cdr_mode == RXPORT_CDR_FPD4) {
+ dev_err(dev, "rx%u: FPD-Link 4 CDR not supported\n", nport);
+ return -EINVAL;
+ }
+
+ rxport->cdr_mode = cdr_mode;
+
+ ret = fwnode_property_read_u32(link_fwnode, "ti,rx-mode", &rx_mode);
+ if (ret < 0) {
+ dev_err(dev, "rx%u: failed to read 'ti,rx-mode': %d\n", nport,
+ ret);
+ return ret;
+ }
+
+ if (rx_mode > RXPORT_MODE_LAST) {
+ dev_err(dev, "rx%u: bad 'ti,rx-mode' %u\n", nport, 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 = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos",
+ &strobe_pos);
+ if (ret) {
+ if (ret != -EINVAL) {
+ dev_err(dev,
+ "rx%u: failed to read 'ti,strobe-pos': %d\n",
+ nport, ret);
+ return ret;
+ }
+ } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
+ strobe_pos > UB960_MAX_MANUAL_STROBE_POS) {
+ dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", nport,
+ 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,
+ "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
+ nport);
+ }
+
+ ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
+ if (ret) {
+ if (ret != -EINVAL) {
+ dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
+ nport, ret);
+ return ret;
+ }
+ } else if (eq_level > UB960_MAX_EQ_LEVEL) {
+ dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
+ eq_level);
+ } else {
+ rxport->eq.manual_eq = true;
+ rxport->eq.manual.eq_level = eq_level;
+ }
+
+ ret = fwnode_property_read_u32(link_fwnode, "i2c-alias",
+ &ser_i2c_alias);
+ if (ret) {
+ dev_err(dev, "rx%u: failed to read 'i2c-alias': %d\n", nport,
+ ret);
+ return ret;
+ }
+ rxport->ser_alias = ser_i2c_alias;
+
+ rxport->remote_fwnode = fwnode_get_named_child_node(link_fwnode, "serializer");
+ if (!rxport->remote_fwnode) {
+ dev_err(dev, "rx%u: missing 'serializer' node\n", nport);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv,
+ struct fwnode_handle *ep_fwnode,
+ struct ub960_rxport *rxport)
+{
+ struct device *dev = &priv->client->dev;
+ unsigned int nport = rxport->nport;
+ int ret;
+ u32 v;
+
+ rxport->source_ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode);
+ if (!rxport->source_ep_fwnode) {
+ dev_err(dev, "rx%u: no remote endpoint\n", nport);
+ return -ENODEV;
+ }
+
+ /* We currently have properties only for RAW modes */
+
+ switch (rxport->rx_mode) {
+ case RXPORT_MODE_RAW10:
+ case RXPORT_MODE_RAW12_HF:
+ case RXPORT_MODE_RAW12_LF:
+ break;
+ default:
+ return 0;
+ }
+
+ ret = fwnode_property_read_u32(ep_fwnode, "hsync-active", &v);
+ if (ret) {
+ dev_err(dev, "rx%u: failed to parse 'hsync-active': %d\n",
+ nport, ret);
+ goto err_put_source_ep_fwnode;
+ }
+
+ rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_LV_POL_LOW : 0;
+
+ ret = fwnode_property_read_u32(ep_fwnode, "vsync-active", &v);
+ if (ret) {
+ dev_err(dev, "rx%u: failed to parse 'vsync-active': %d\n",
+ nport, ret);
+ goto err_put_source_ep_fwnode;
+ }
+
+ rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_FV_POL_LOW : 0;
+
+ return 0;
+
+err_put_source_ep_fwnode:
+ fwnode_handle_put(rxport->source_ep_fwnode);
+ return ret;
+}
+
+static int ub960_parse_dt_rxport(struct ub960_data *priv, unsigned int nport,
+ struct fwnode_handle *link_fwnode,
+ struct fwnode_handle *ep_fwnode)
+{
+ const char *vpoc_names[UB960_MAX_RX_NPORTS] = { "vpoc0", "vpoc1",
+ "vpoc2", "vpoc3" };
+ struct device *dev = &priv->client->dev;
+ struct ub960_rxport *rxport;
+ int ret;
+
+ 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, link_fwnode, rxport);
+ if (ret)
+ goto err_free_rxport;
+
+ 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, "rx%u: failed to get VPOC supply: %d\n",
+ nport, ret);
+ goto err_put_remote_fwnode;
+ }
+ }
+
+ ret = ub960_parse_dt_rxport_ep_properties(priv, ep_fwnode, rxport);
+ if (ret)
+ goto err_put_remote_fwnode;
+
+ return 0;
+
+err_put_remote_fwnode:
+ fwnode_handle_put(rxport->remote_fwnode);
+err_free_rxport:
+ priv->rxports[nport] = NULL;
+ kfree(rxport);
+ return ret;
+}
+
+static struct fwnode_handle *
+ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode,
+ unsigned int nport)
+{
+ struct fwnode_handle *link_fwnode;
+ int ret;
+
+ fwnode_for_each_child_node(links_fwnode, link_fwnode) {
+ u32 link_num;
+
+ if (strncmp(fwnode_get_name(link_fwnode), "link@", 5) != 0)
+ continue;
+
+ ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num);
+ if (ret)
+ return NULL;
+
+ if (nport == link_num) {
+ fwnode_handle_put(link_fwnode);
+ return link_fwnode;
+ }
+ }
+
+ return NULL;
+}
+
+static int ub960_parse_dt_rxports(struct ub960_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ struct fwnode_handle *links_fwnode;
+ unsigned int nport;
+ int ret;
+
+ links_fwnode = fwnode_get_named_child_node(dev_fwnode(dev), "links");
+ if (!links_fwnode) {
+ dev_err(dev, "'links' node missing\n");
+ return -ENODEV;
+ }
+
+ /* Defaults, recommended by TI */
+ priv->strobe.min = 2;
+ priv->strobe.max = 3;
+
+ priv->strobe.manual = fwnode_property_read_bool(links_fwnode, "ti,manual-strobe");
+
+ for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
+ struct fwnode_handle *link_fwnode;
+ struct fwnode_handle *ep_fwnode;
+
+ link_fwnode = ub960_fwnode_get_link_by_regs(links_fwnode, nport);
+ if (!link_fwnode)
+ continue;
+
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ nport, 0, 0);
+ if (!ep_fwnode) {
+ fwnode_handle_put(link_fwnode);
+ continue;
+ }
+
+ ret = ub960_parse_dt_rxport(priv, nport, link_fwnode,
+ ep_fwnode);
+
+ fwnode_handle_put(link_fwnode);
+ fwnode_handle_put(ep_fwnode);
+
+ if (ret) {
+ dev_err(dev, "rx%u: failed to parse RX port\n", nport);
+ goto err_put_links;
+ }
+ }
+
+ fwnode_handle_put(links_fwnode);
+
+ return 0;
+
+err_put_links:
+ fwnode_handle_put(links_fwnode);
+
+ return ret;
+}
+
+static int ub960_parse_dt_txports(struct ub960_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ u32 nport;
+ int ret;
+
+ for (nport = 0; nport < priv->hw_data->num_txports; ++nport) {
+ struct fwnode_handle *ep_fwnode;
+
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ nport + priv->hw_data->num_rxports, 0, 0);
+ if (!ep_fwnode)
+ continue;
+
+ ret = ub960_parse_dt_txport(priv, ep_fwnode, nport);
+
+ fwnode_handle_put(ep_fwnode);
+
+ if (ret)
+ break;
+ }
+
+ return 0;
+}
+
+static int ub960_parse_dt(struct ub960_data *priv)
+{
+ int ret;
+
+ 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 i;
+ int ret;
+
+ ret = media_entity_get_fwnode_pad(&subdev->entity,
+ rxport->source_ep_fwnode,
+ MEDIA_PAD_FL_SOURCE);
+ if (ret < 0) {
+ dev_err(dev, "Failed to find pad for %s\n", subdev->name);
+ return ret;
+ }
+
+ rxport->source_sd = subdev;
+ rxport->source_sd_pad = ret;
+
+ ret = media_create_pad_link(&rxport->source_sd->entity,
+ rxport->source_sd_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->source_sd->name, rxport->source_sd_pad,
+ priv->sd.name, nport);
+ return ret;
+ }
+
+ dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name,
+ rxport->source_sd_pad, nport);
+
+ for (i = 0; i < priv->hw_data->num_rxports; ++i) {
+ if (priv->rxports[i] && !priv->rxports[i]->source_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_rxport *rxport = to_ub960_asd(asd)->rxport;
+
+ rxport->source_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->source_ep_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)
+{
+ 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 ? UB960_SR_RESET_DIGITAL_RESET1 :
+ UB960_SR_RESET_DIGITAL_RESET0;
+
+ 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;
+
+ 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))
+ return dev_err_probe(dev, PTR_ERR(priv->vddio),
+ "cannot get VDDIO regulator\n");
+
+ /* get power-down pin from DT */
+ priv->pd_gpio =
+ devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->pd_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->pd_gpio),
+ "Cannot get powerdown GPIO\n");
+
+ priv->refclk = devm_clk_get(dev, "refclk");
+ if (IS_ERR(priv->refclk))
+ return dev_err_probe(dev, PTR_ERR(priv->refclk),
+ "Cannot get REFCLK\n");
+
+ 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 enable 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->is_ub9702)
+ ub960_update_bits(priv, UB960_SR_RESET,
+ UB960_SR_RESET_GPIO_LOCK_RELEASE,
+ UB960_SR_RESET_GPIO_LOCK_RELEASE);
+
+ 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_rxport_enable_vpocs(priv);
+ if (ret)
+ goto err_free_ports;
+
+ ret = ub960_init_rx_ports(priv);
+ if (ret)
+ goto err_disable_vpocs;
+
+ 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_disable_vpocs;
+
+ 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_warn(dev, "irq support not implemented, using polling\n");
+
+ schedule_delayed_work(&priv->poll_work,
+ msecs_to_jiffies(UB960_POLL_TIME_MS));
+
+ return 0;
+
+err_free_sers:
+ ub960_rxport_remove_serializers(priv);
+err_uninit_atr:
+ ub960_uninit_atr(priv);
+err_disable_vpocs:
+ ub960_rxport_disable_vpocs(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);
+
+ cancel_delayed_work_sync(&priv->poll_work);
+
+ ub960_destroy_subdev(priv);
+ ub960_rxport_remove_serializers(priv);
+ ub960_uninit_atr(priv);
+ ub960_rxport_disable_vpocs(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);
+}
+
+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,
+ .is_ub9702 = true,
+ .is_fpdlink4 = true,
+};
+
+static const struct i2c_device_id ub960_id[] = {
+ { "ds90ub960-q1", 0 },
+ { "ds90ub9702-q1", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ub960_id);
+
+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);
+
+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 = ub960_dt_ids,
+ },
+};
+module_i2c_driver(ds90ub960_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV Deserializers Driver");
+MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
+MODULE_IMPORT_NS(I2C_ATR);
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

2023-01-18 13:53:48

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 3/7] dt-bindings: media: add TI DS90UB953 FPD-Link III Serializer

Add DT bindings for TI DS90UB953 FPD-Link III Serializer.

Signed-off-by: Tomi Valkeinen <[email protected]>
Reviewed-by: Laurent Pinchart <[email protected]>
Reviewed-by: Rob Herring <[email protected]>
---
.../bindings/media/i2c/ti,ds90ub953.yaml | 134 ++++++++++++++++++
1 file changed, 134 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..2030366994d1
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml
@@ -0,0 +1,134 @@
+# 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 III Serializer
+
+maintainers:
+ - Tomi Valkeinen <[email protected]>
+
+description:
+ The TI DS90UB953 is an FPD-Link III video serializer for MIPI CSI-2.
+
+properties:
+ compatible:
+ enum:
+ - ti,ds90ub953-q1
+ - ti,ds90ub971-q1
+
+ '#gpio-cells':
+ const: 2
+ description:
+ First cell is the GPIO pin number, second cell is the flags. The GPIO pin
+ number must be in range of [0, 3].
+
+ gpio-controller: true
+
+ clocks:
+ maxItems: 1
+ description:
+ Reference clock connected to the CLKIN pin.
+
+ clock-names:
+ items:
+ - const: clkin
+
+ '#clock-cells':
+ const: 0
+
+ ports:
+ $ref: /schemas/graph.yaml#/properties/ports
+
+ properties:
+ port@0:
+ $ref: /schemas/graph.yaml#/$defs/port-base
+ unevaluatedProperties: false
+ description: CSI-2 input port
+
+ properties:
+ endpoint:
+ $ref: /schemas/media/video-interfaces.yaml#
+ unevaluatedProperties: false
+
+ required:
+ - data-lanes
+
+ port@1:
+ $ref: /schemas/graph.yaml#/properties/port
+ unevaluatedProperties: false
+ description: FPD-Link III output port
+
+ required:
+ - port@0
+ - port@1
+
+ i2c:
+ $ref: /schemas/i2c/i2c-controller.yaml#
+ unevaluatedProperties: false
+
+required:
+ - compatible
+ - '#gpio-cells'
+ - gpio-controller
+ - '#clock-cells'
+ - ports
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ serializer {
+ compatible = "ti,ds90ub953-q1";
+
+ gpio-controller;
+ #gpio-cells = <2>;
+
+ #clock-cells = <0>;
+
+ ports {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ port@0 {
+ reg = <0>;
+ ub953_in: endpoint {
+ clock-lanes = <0>;
+ data-lanes = <1 2 3 4>;
+ remote-endpoint = <&sensor_out>;
+ };
+ };
+
+ port@1 {
+ reg = <1>;
+ endpoint {
+ remote-endpoint = <&deser_fpd_in>;
+ };
+ };
+ };
+
+ i2c {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ sensor@1a {
+ compatible = "sony,imx274";
+ reg = <0x1a>;
+
+ reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>;
+
+ clocks = <&serializer>;
+ clock-names = "inck";
+
+ port {
+ sensor_out: endpoint {
+ remote-endpoint = <&ub953_in>;
+ };
+ };
+ };
+ };
+ };
+...
--
2.34.1

2023-01-18 13:53:58

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 7/7] media: i2c: add DS90UB953 driver

Add driver for TI DS90UB953 FPD-Link III Serializer.

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

diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
index e0a1c2a5f3bf..0590312ec751 100644
--- a/drivers/media/i2c/Kconfig
+++ b/drivers/media/i2c/Kconfig
@@ -1614,6 +1614,19 @@ config VIDEO_DS90UB913
Device driver for the Texas Instruments DS90UB913
FPD-Link III Serializer.

+config VIDEO_DS90UB953
+ tristate "TI FPD-Link III/IV CSI-2 Serializers"
+ depends on OF && I2C && VIDEO_DEV
+ select I2C_ATR
+ select MEDIA_CONTROLLER
+ select OF_GPIO
+ select REGMAP_I2C
+ select V4L2_FWNODE
+ select VIDEO_V4L2_SUBDEV_API
+ help
+ Device driver for the Texas Instruments DS90UB953
+ FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer.
+
config VIDEO_DS90UB960
tristate "TI FPD-Link III/IV Deserializers"
depends on OF && I2C && VIDEO_DEV
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
index c4875ec8c3b9..efd5f717a5f7 100644
--- a/drivers/media/i2c/Makefile
+++ b/drivers/media/i2c/Makefile
@@ -143,4 +143,5 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
obj-$(CONFIG_VIDEO_DS90UB913) += ds90ub913.o
+obj-$(CONFIG_VIDEO_DS90UB953) += ds90ub953.o
obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o
diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c
new file mode 100644
index 000000000000..ec33e16da3d1
--- /dev/null
+++ b/drivers/media/i2c/ds90ub953.c
@@ -0,0 +1,1576 @@
+// 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) 2023 Tomi Valkeinen <[email protected]>
+ */
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/fwnode.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_device.h>
+#include <linux/property.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_GENERAL_CFG_CONT_CLK BIT(6)
+#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT 4
+#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_MASK GENMASK(5, 4)
+#define UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE BIT(1)
+#define UB953_REG_GENERAL_CFG_I2C_STRAP_MODE BIT(0)
+
+#define UB953_REG_MODE_SEL 0x03
+#define UB953_REG_MODE_SEL_MODE_DONE BIT(3)
+#define UB953_REG_MODE_SEL_MODE_OVERRIDE BIT(4)
+#define UB953_REG_MODE_SEL_MODE_MASK GENMASK(2, 0)
+
+#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 III CSI-2 synchronous mode */
+ UB953_MODE_SYNC,
+ /* FPD-Link III CSI-2 non-synchronous mode, external ref clock */
+ UB953_MODE_NONSYNC_EXT,
+ /* FPD-Link III CSI-2 non-synchronous mode, internal ref clock */
+ UB953_MODE_NONSYNC_INT,
+ /* FPD-Link III DVP mode */
+ UB953_MODE_DVP,
+};
+
+struct ub953_hw_data {
+ const char *model;
+ bool is_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;
+
+ struct v4l2_subdev sd;
+ struct media_pad pads[2];
+
+ struct v4l2_async_notifier notifier;
+
+ struct v4l2_subdev *source_sd;
+ u16 source_sd_pad;
+
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ u64 enabled_source_streams;
+
+ /* 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;
+};
+
+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) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
+ block, reg, ret);
+ goto out;
+ }
+
+ ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
+ block, reg, 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) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n",
+ block, reg, ret);
+ goto out;
+ }
+
+ ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n",
+ block, reg, ret);
+ }
+
+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);
+
+ gc->label = dev_name(dev);
+ gc->parent = dev;
+ gc->owner = THIS_MODULE;
+ gc->base = -1;
+ gc->can_sleep = true;
+ 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_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)
+{
+ static const struct v4l2_mbus_framefmt format = {
+ .width = 640,
+ .height = 480,
+ .code = MEDIA_BUS_FMT_UYVY8_1X16,
+ .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_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;
+
+ if (pad != UB953_PAD_SOURCE)
+ return -EINVAL;
+
+ ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
+ priv->source_sd_pad, &source_fd);
+ if (ret)
+ return ret;
+
+ memset(fd, 0, sizeof(*fd));
+
+ fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
+
+ state = v4l2_subdev_lock_and_get_active_state(sd);
+
+ routing = &state->routing;
+
+ for_each_active_route(routing, route) {
+ struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
+ unsigned int i;
+
+ if (route->source_pad != pad)
+ continue;
+
+ for (i = 0; i < source_fd.num_entries; ++i) {
+ if (source_fd.entry[i].stream == route->sink_stream) {
+ source_entry = &source_fd.entry[i];
+ 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 = source_entry->flags;
+ 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 == UB953_PAD_SOURCE)
+ return v4l2_subdev_get_fmt(sd, state, format);
+
+ /* Set sink format */
+ fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
+ format->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = format->format;
+
+ /* Propagate to source format */
+ fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
+ format->stream);
+ if (!fmt)
+ return -EINVAL;
+
+ *fmt = format->format;
+
+ return 0;
+}
+
+static int ub953_init_cfg(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state)
+{
+ struct v4l2_subdev_route routes[] = {
+ {
+ .sink_pad = UB953_PAD_SINK,
+ .sink_stream = 0,
+ .source_pad = UB953_PAD_SOURCE,
+ .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 %#02x\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 %#02x\n", v);
+
+ ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v);
+ dev_info(dev, "CSI_ERR_DLANE01 %#02x\n", v);
+
+ ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v);
+ dev_info(dev, "CSI_ERR_DLANE23 %#02x\n", v);
+
+ ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v);
+ dev_info(dev, "CSI_ERR_CLK_LANE %#02x\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 %#02x\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);
+ u64 sink_streams;
+ int ret;
+
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
+ UB953_PAD_SINK,
+ &streams_mask);
+
+ ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
+ 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);
+ u64 sink_streams;
+ int ret;
+
+ sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
+ UB953_PAD_SINK,
+ &streams_mask);
+
+ ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
+ 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 int 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);
+
+ if (state->routing.num_routes != 1)
+ return -EINVAL;
+
+ fmt = v4l2_subdev_state_get_stream_format(state, UB953_PAD_SOURCE, 0);
+ if (!fmt)
+ return -EINVAL;
+
+ if (fmt->code != MEDIA_BUS_FMT_UYVY8_1X16)
+ return -EINVAL;
+
+ 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);
+
+ return 0;
+}
+
+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);
+ int ret = 0;
+
+ switch (ctrl->id) {
+ case V4L2_CID_TEST_PATTERN:
+ if (ctrl->val == 0)
+ ub953_disable_tpg(priv);
+ else
+ ret = ub953_enable_tpg(priv, ctrl->val);
+ break;
+ }
+
+ return ret;
+}
+
+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;
+ int ret;
+
+ 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;
+ priv->source_sd_pad = ret;
+
+ ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_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, priv->source_sd_pad,
+ priv->sd.name);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct v4l2_async_notifier_operations ub953_notify_ops = {
+ .bound = ub953_notify_bound,
+};
+
+static int ub953_v4l2_notifier_register(struct ub953_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ struct v4l2_async_subdev *asd;
+ struct fwnode_handle *ep_fwnode;
+ int ret;
+
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB953_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
+ dev_err(dev, "No graph endpoint\n");
+ return -ENODEV;
+ }
+
+ v4l2_async_nf_init(&priv->notifier);
+
+ asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
+ struct v4l2_async_subdev);
+
+ fwnode_handle_put(ep_fwnode);
+
+ 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)
+{
+ 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 u64 ub953_get_fc_rate(struct ub953_data *priv)
+{
+ if (priv->mode != UB953_MODE_SYNC) {
+ /* Not supported */
+ return 0;
+ }
+
+ if (priv->hw_data->is_ub971)
+ return priv->plat_data->bc_rate * 160ull;
+ else
+ return priv->plat_data->bc_rate / 2 * 160ull;
+}
+
+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->is_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->is_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->is_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;
+
+ if (!init.name)
+ return -ENOMEM;
+
+ /* Initialize clkout to 25MHz by default */
+ if (priv->hw_data->is_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;
+
+ 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 int ub953_parse_dt(struct ub953_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ struct fwnode_handle *ep_fwnode;
+ int ret;
+
+ ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB953_PAD_SINK, 0, 0);
+ if (!ep_fwnode) {
+ dev_err(dev, "OF: no endpoint\n");
+ return -ENOENT;
+ }
+
+ ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
+
+ fwnode_handle_put(ep_fwnode);
+
+ if (ret <= 0) {
+ dev_err(dev, "failed to parse property 'data-lanes': %d\n",
+ ret);
+ return ret;
+ }
+
+ if (ret != 1 && ret != 2 && ret != 4) {
+ dev_err(dev, "bad number of data-lanes: %d\n", ret);
+ return -EINVAL;
+ }
+
+ priv->num_data_lanes = ret;
+
+ return 0;
+}
+
+static int ub953_hw_init(struct ub953_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ bool mode_override;
+ int ret;
+ u8 v;
+
+ ret = ub953_read(priv, UB953_REG_MODE_SEL, &v);
+ if (ret)
+ return ret;
+
+ if (!(v & UB953_REG_MODE_SEL_MODE_DONE))
+ return dev_err_probe(dev, -EIO, "Mode value not stabilized\n");
+
+ mode_override = v & UB953_REG_MODE_SEL_MODE_OVERRIDE;
+
+ switch (v & UB953_REG_MODE_SEL_MODE_MASK) {
+ 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:
+ return dev_err_probe(dev, -EIO,
+ "Illegal mode in mode register\n");
+ }
+
+ dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap",
+ priv->mode);
+
+ if (priv->mode != UB953_MODE_SYNC)
+ return dev_err_probe(dev, -ENODEV,
+ "Only synchronous mode supported\n");
+
+ ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read revision");
+
+ 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)
+ return ret;
+
+ dev_dbg(dev, "i2c strap setting %s V\n",
+ (v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "1.8" : "3.3");
+
+ ret = ub953_i2c_master_init(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "i2c init failed\n");
+
+ ub953_write(priv, UB953_REG_GENERAL_CFG,
+ UB953_REG_GENERAL_CFG_CONT_CLK |
+ ((priv->num_data_lanes - 1) << UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT) |
+ UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE);
+
+ return 0;
+}
+
+static int ub953_subdev_init(struct ub953_data *priv)
+{
+ struct device *dev = &priv->client->dev;
+ int ret;
+
+ 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)
+ return dev_err_probe(dev, priv->ctrl_handler.error,
+ "Failed to set up v4l2 controls\n");
+
+ 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_probe(dev, ret, "Failed to init pads\n");
+ goto err_remove_ctrls;
+ }
+
+ priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
+ UB953_PAD_SOURCE, 0,
+ 0);
+ if (!priv->sd.fwnode) {
+ ret = -ENODEV;
+ dev_err_probe(dev, ret, "Missing TX endpoint\n");
+ goto err_entity_cleanup;
+ }
+
+ 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_probe(dev, ret,
+ "v4l2 subdev notifier register failed\n");
+ goto err_free_state;
+ }
+
+ ret = v4l2_async_register_subdev(&priv->sd);
+ if (ret) {
+ dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
+ goto err_unreg_notif;
+ }
+
+ return 0;
+
+err_unreg_notif:
+ ub953_v4l2_notifier_unregister(priv);
+err_free_state:
+ v4l2_subdev_cleanup(&priv->sd);
+err_entity_cleanup:
+ media_entity_cleanup(&priv->sd.entity);
+err_remove_ctrls:
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+
+ return ret;
+}
+
+static void ub953_subdev_uninit(struct ub953_data *priv)
+{
+ v4l2_async_unregister_subdev(&priv->sd);
+ ub953_v4l2_notifier_unregister(priv);
+ v4l2_subdev_cleanup(&priv->sd);
+ media_entity_cleanup(&priv->sd.entity);
+ v4l2_ctrl_handler_free(&priv->ctrl_handler);
+}
+
+static int ub953_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ub953_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;
+
+ priv->plat_data = dev_get_platdata(&client->dev);
+ if (!priv->plat_data)
+ return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
+
+ 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)) {
+ ret = PTR_ERR(priv->regmap);
+ dev_err_probe(dev, ret, "Failed to init regmap\n");
+ goto err_mutex_destroy;
+ }
+
+ ret = ub953_parse_dt(priv);
+ if (ret)
+ goto err_mutex_destroy;
+
+ ret = ub953_hw_init(priv);
+ if (ret)
+ goto err_mutex_destroy;
+
+ ret = ub953_gpiochip_probe(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to init gpiochip\n");
+ goto err_mutex_destroy;
+ }
+
+ ret = ub953_register_clkout(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register clkout\n");
+ goto err_gpiochip_remove;
+ }
+
+ ret = ub953_subdev_init(priv);
+ if (ret)
+ goto err_gpiochip_remove;
+
+ ret = ub953_add_i2c_adapter(priv);
+ if (ret) {
+ dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
+ goto err_subdev_uninit;
+ }
+
+ return 0;
+
+err_subdev_uninit:
+ ub953_subdev_uninit(priv);
+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);
+
+ i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port);
+
+ ub953_subdev_uninit(priv);
+
+ 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",
+ .is_ub971 = true,
+};
+
+static const struct i2c_device_id ub953_id[] = {
+ { "ds90ub953-q1", 0 },
+ { "ds90ub971-q1", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ub953_id);
+
+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);
+
+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 = ub953_dt_ids,
+ },
+};
+module_i2c_driver(ds90ub953_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV CSI-2 Serializers Driver");
+MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
+MODULE_IMPORT_NS(I2C_ATR);
--
2.34.1

2023-01-18 14:09:49

by Tomi Valkeinen

[permalink] [raw]
Subject: [PATCH v7 1/7] 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 | 83 +++++
MAINTAINERS | 8 +
drivers/i2c/Kconfig | 9 +
drivers/i2c/Makefile | 1 +
drivers/i2c/i2c-atr.c | 547 ++++++++++++++++++++++++++++
include/linux/i2c-atr.h | 117 ++++++
7 files changed, 766 insertions(+)
create mode 100644 Documentation/i2c/muxes/i2c-atr.rst
create mode 100644 drivers/i2c/i2c-atr.c
create mode 100644 include/linux/i2c-atr.h

diff --git a/Documentation/i2c/index.rst b/Documentation/i2c/index.rst
index 6270f1fd7d4e..aaf33d1315f4 100644
--- a/Documentation/i2c/index.rst
+++ b/Documentation/i2c/index.rst
@@ -16,6 +16,7 @@ Introduction
instantiating-devices
busses/index
i2c-topology
+ muxes/i2c-atr
muxes/i2c-mux-gpio
i2c-sysfs

diff --git a/Documentation/i2c/muxes/i2c-atr.rst b/Documentation/i2c/muxes/i2c-atr.rst
new file mode 100644
index 000000000000..c7e060ca682d
--- /dev/null
+++ b/Documentation/i2c/muxes/i2c-atr.rst
@@ -0,0 +1,83 @@
+.. 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
+
+I2C ATR functions and data structures
+-------------------------------------
+
+.. kernel-doc:: include/linux/i2c-atr.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 1daadaa4d48b..ba716f2861cf 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9563,6 +9563,14 @@ L: [email protected]
S: Maintained
F: drivers/i2c/i2c-core-acpi.c

+I2C ADDRESS TRANSLATOR (ATR)
+M: Tomi Valkeinen <[email protected]>
+R: 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..1d43cf3824eb
--- /dev/null
+++ b/drivers/i2c/i2c-atr.c
@@ -0,0 +1,547 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019,2022 Luca Ceresoli <[email protected]>
+ * Copyright (c) 2022,2023 Tomi Valkeinen <[email protected]>
+ *
+ * Originally based on i2c-mux.c
+ */
+
+#include <linux/fwnode.h>
+#include <linux/i2c-atr.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#define ATR_MAX_ADAPTERS 99 /* Just a sanity limit */
+#define ATR_MAX_SYMLINK_LEN 16 /* Longest name is 10 chars: "channel-99" */
+
+/**
+ * struct i2c_atr_cli2alias_pair - Holds 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;
+};
+
+/**
+ * struct i2c_atr_chan - Data for a channel.
+ * @adap: The &struct i2c_adapter for the channel
+ * @atr: The parent I2C ATR
+ * @chan_id: The ID of this channel
+ * @alias_list: List of @struct i2c_atr_cli2alias_pair containing the
+ * assigned aliases
+ * @orig_addrs_lock: Mutex protecting @orig_addrs
+ * @orig_addrs: Buffer used to store the original addresses during transmit
+ * @orig_addrs_size: Size of @orig_addrs
+ */
+struct i2c_atr_chan {
+ struct i2c_adapter adap;
+ struct i2c_atr *atr;
+ u32 chan_id;
+
+ struct list_head alias_list;
+
+ /* Lock orig_addrs during xfer */
+ struct mutex orig_addrs_lock;
+ u16 *orig_addrs;
+ unsigned int orig_addrs_size;
+};
+
+/**
+ * struct i2c_atr - The I2C ATR instance
+ * @parent: The parent &struct i2c_adapter
+ * @dev: The device that owns the I2C ATR instance
+ * @ops: &struct i2c_atr_ops
+ * @priv: Private driver data, set with i2c_atr_set_driver_data()
+ * @algo: The &struct i2c_algorithm for adapters
+ * @lock: Lock for the I2C bus segment (see &struct i2c_lock_operations)
+ * @max_adapters: Maximum number of adapters this I2C ATR can have
+ * @adapter: Array of adapters
+ */
+struct i2c_atr {
+ struct i2c_adapter *parent;
+ struct device *dev;
+ const struct i2c_atr_ops *ops;
+
+ void *priv;
+
+ struct i2c_algorithm algo;
+ /* lock for the I2C bus segment (see struct i2c_lock_operations) */
+ struct mutex lock;
+ int max_adapters;
+
+ struct notifier_block i2c_nb;
+
+ struct i2c_adapter *adapter[];
+};
+
+static struct i2c_atr_cli2alias_pair *
+i2c_atr_find_mapping_by_client(const struct list_head *list,
+ const struct i2c_client *client)
+{
+ struct i2c_atr_cli2alias_pair *c2a;
+
+ list_for_each_entry(c2a, list, node) {
+ if (c2a->client == client)
+ return c2a;
+ }
+
+ return NULL;
+}
+
+static struct i2c_atr_cli2alias_pair *
+i2c_atr_find_mapping_by_addr(const struct list_head *list, u16 phys_addr)
+{
+ struct i2c_atr_cli2alias_pair *c2a;
+
+ list_for_each_entry(c2a, list, node) {
+ if (c2a->client->addr == phys_addr)
+ return c2a;
+ }
+
+ return NULL;
+}
+
+/*
+ * Replace all message addresses with their aliases, saving the original
+ * addresses.
+ *
+ * This function is internal for use in i2c_atr_master_xfer(). It must be
+ * followed by i2c_atr_unmap_msgs() to restore the original addresses.
+ */
+static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
+ int num)
+{
+ struct i2c_atr *atr = chan->atr;
+ static struct i2c_atr_cli2alias_pair *c2a;
+ int i;
+
+ /* Ensure we have enough room to save the original addresses */
+ if (unlikely(chan->orig_addrs_size < num)) {
+ u16 *new_buf;
+
+ new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);
+ if (!new_buf)
+ return -ENOMEM;
+
+ kfree(chan->orig_addrs);
+ chan->orig_addrs = new_buf;
+ chan->orig_addrs_size = num;
+ }
+
+ for (i = 0; i < num; i++) {
+ chan->orig_addrs[i] = msgs[i].addr;
+
+ c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list,
+ msgs[i].addr);
+ if (!c2a) {
+ dev_err(atr->dev, "client 0x%02x not mapped!\n",
+ msgs[i].addr);
+ return -ENXIO;
+ }
+
+ msgs[i].addr = c2a->alias;
+ }
+
+ return 0;
+}
+
+/*
+ * Restore all message address aliases with the original addresses. This
+ * function is internal for use in i2c_atr_master_xfer().
+ *
+ * @see i2c_atr_map_msgs()
+ */
+static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs,
+ int num)
+{
+ int i;
+
+ for (i = 0; i < num; i++)
+ msgs[i].addr = chan->orig_addrs[i];
+}
+
+static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
+ int num)
+{
+ struct i2c_atr_chan *chan = adap->algo_data;
+ struct i2c_atr *atr = chan->atr;
+ struct i2c_adapter *parent = atr->parent;
+ int ret;
+
+ /* Switch to the right atr port */
+ if (atr->ops->select) {
+ ret = atr->ops->select(atr, chan->chan_id);
+ if (ret < 0)
+ goto out_deselect;
+ }
+
+ /* Translate addresses */
+ mutex_lock(&chan->orig_addrs_lock);
+ ret = i2c_atr_map_msgs(chan, msgs, num);
+ if (ret < 0)
+ goto out_unlock_deselect;
+
+ /* Perform the transfer */
+ ret = i2c_transfer(parent, msgs, num);
+
+ /* Restore addresses */
+ i2c_atr_unmap_msgs(chan, msgs, num);
+
+out_unlock_deselect:
+ mutex_unlock(&chan->orig_addrs_lock);
+
+out_deselect:
+ if (atr->ops->deselect)
+ atr->ops->deselect(atr, chan->chan_id);
+
+ return ret;
+}
+
+static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr,
+ unsigned short flags, char read_write, u8 command,
+ int size, union i2c_smbus_data *data)
+{
+ struct i2c_atr_chan *chan = adap->algo_data;
+ struct i2c_atr *atr = chan->atr;
+ struct i2c_adapter *parent = atr->parent;
+ struct i2c_atr_cli2alias_pair *c2a;
+ int ret = 0;
+
+ c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, addr);
+ if (!c2a) {
+ dev_err(atr->dev, "client 0x%02x not mapped!\n", addr);
+ return -ENXIO;
+ }
+
+ if (atr->ops->select)
+ ret = atr->ops->select(atr, chan->chan_id);
+ if (!ret)
+ ret = i2c_smbus_xfer(parent, c2a->alias, flags, read_write,
+ command, size, data);
+ if (atr->ops->deselect)
+ atr->ops->deselect(atr, chan->chan_id);
+
+ return ret;
+}
+
+static u32 i2c_atr_functionality(struct i2c_adapter *adap)
+{
+ struct i2c_atr_chan *chan = adap->algo_data;
+ struct i2c_adapter *parent = chan->atr->parent;
+
+ return parent->algo->functionality(parent);
+}
+
+static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int flags)
+{
+ struct i2c_atr_chan *chan = adapter->algo_data;
+ struct i2c_atr *atr = chan->atr;
+
+ mutex_lock(&atr->lock);
+}
+
+static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int flags)
+{
+ struct i2c_atr_chan *chan = adapter->algo_data;
+ struct i2c_atr *atr = chan->atr;
+
+ return mutex_trylock(&atr->lock);
+}
+
+static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int flags)
+{
+ struct i2c_atr_chan *chan = adapter->algo_data;
+ struct i2c_atr *atr = chan->atr;
+
+ mutex_unlock(&atr->lock);
+}
+
+static const struct i2c_lock_operations i2c_atr_lock_ops = {
+ .lock_bus = i2c_atr_lock_bus,
+ .trylock_bus = i2c_atr_trylock_bus,
+ .unlock_bus = i2c_atr_unlock_bus,
+};
+
+static int i2c_atr_attach_client(struct i2c_adapter *adapter,
+ const struct i2c_client *client)
+{
+ struct i2c_atr_chan *chan = adapter->algo_data;
+ struct i2c_atr *atr = chan->atr;
+ struct i2c_atr_cli2alias_pair *c2a;
+ u16 alias_id;
+ int ret;
+
+ c2a = kzalloc(sizeof(*c2a), GFP_KERNEL);
+ if (!c2a)
+ return -ENOMEM;
+
+ ret = atr->ops->attach_client(atr, chan->chan_id, client, &alias_id);
+ if (ret)
+ 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 int i2c_atr_bus_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *device)
+{
+ struct i2c_atr *atr = container_of(nb, struct i2c_atr, i2c_nb);
+ struct device *dev = device;
+ struct i2c_client *client;
+ u32 chan_id;
+ int ret;
+
+ client = i2c_verify_client(dev);
+ if (!client)
+ return NOTIFY_DONE;
+
+ /* Is the client in one of our adapters? */
+ for (chan_id = 0; chan_id < atr->max_adapters; ++chan_id) {
+ if (client->adapter == atr->adapter[chan_id])
+ break;
+ }
+
+ if (chan_id == atr->max_adapters)
+ return NOTIFY_DONE;
+
+ switch (event) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ ret = i2c_atr_attach_client(client->adapter, client);
+ if (ret)
+ dev_err(atr->dev,
+ "Failed to attach remote client '%s': %d\n",
+ dev_name(dev), ret);
+ break;
+
+ case BUS_NOTIFY_DEL_DEVICE:
+ i2c_atr_detach_client(client->adapter, client);
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+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;
+ int ret;
+
+ if (max_adapters > ATR_MAX_ADAPTERS)
+ return ERR_PTR(-EINVAL);
+
+ if (!ops || !ops->attach_client || !ops->detach_client)
+ return ERR_PTR(-EINVAL);
+
+ atr = devm_kzalloc(dev, struct_size(atr, adapter, max_adapters),
+ GFP_KERNEL);
+ if (!atr)
+ return ERR_PTR(-ENOMEM);
+
+ mutex_init(&atr->lock);
+
+ atr->parent = parent;
+ atr->dev = dev;
+ atr->ops = ops;
+ atr->max_adapters = max_adapters;
+
+ if (parent->algo->master_xfer)
+ atr->algo.master_xfer = i2c_atr_master_xfer;
+ if (parent->algo->smbus_xfer)
+ atr->algo.smbus_xfer = i2c_atr_smbus_xfer;
+ atr->algo.functionality = i2c_atr_functionality;
+
+ atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call;
+ ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb);
+ if (ret) {
+ mutex_destroy(&atr->lock);
+ return ERR_PTR(ret);
+ }
+
+ return atr;
+}
+EXPORT_SYMBOL_NS_GPL(i2c_atr_new, I2C_ATR);
+
+void i2c_atr_delete(struct i2c_atr *atr)
+{
+ bus_unregister_notifier(&i2c_bus_type, &atr->i2c_nb);
+ mutex_destroy(&atr->lock);
+}
+EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, I2C_ATR);
+
+int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id,
+ struct fwnode_handle *bus_handle)
+{
+ struct i2c_adapter *parent = atr->parent;
+ struct device *dev = atr->dev;
+ struct i2c_atr_chan *chan;
+ char symlink_name[ATR_MAX_SYMLINK_LEN];
+ int ret;
+
+ if (chan_id >= atr->max_adapters) {
+ dev_err(dev, "No room for more i2c-atr adapters\n");
+ return -EINVAL;
+ }
+
+ if (atr->adapter[chan_id]) {
+ dev_err(dev, "Adapter %d already present\n", chan_id);
+ return -EEXIST;
+ }
+
+ chan = kzalloc(sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ chan->atr = atr;
+ chan->chan_id = chan_id;
+ INIT_LIST_HEAD(&chan->alias_list);
+ mutex_init(&chan->orig_addrs_lock);
+
+ snprintf(chan->adap.name, sizeof(chan->adap.name), "i2c-%d-atr-%d",
+ i2c_adapter_id(parent), chan_id);
+ chan->adap.owner = THIS_MODULE;
+ chan->adap.algo = &atr->algo;
+ chan->adap.algo_data = chan;
+ chan->adap.dev.parent = dev;
+ chan->adap.retries = parent->retries;
+ chan->adap.timeout = parent->timeout;
+ chan->adap.quirks = parent->quirks;
+ chan->adap.lock_ops = &i2c_atr_lock_ops;
+
+ 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);
+ }
+
+ atr->adapter[chan_id] = &chan->adap;
+
+ 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_fwnode_put;
+ }
+
+ snprintf(symlink_name, sizeof(symlink_name), "channel-%u",
+ chan->chan_id);
+
+ ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device");
+ if (ret)
+ dev_warn(dev, "can't create symlink to atr device\n");
+ ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name);
+ if (ret)
+ dev_warn(dev, "can't create symlink for channel %u\n", chan_id);
+
+ dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap));
+
+ return 0;
+
+err_fwnode_put:
+ fwnode_handle_put(dev_fwnode(&chan->adap.dev));
+ mutex_destroy(&chan->orig_addrs_lock);
+ kfree(chan);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(i2c_atr_add_adapter, I2C_ATR);
+
+void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
+{
+ char symlink_name[ATR_MAX_SYMLINK_LEN];
+
+ struct i2c_adapter *adap = atr->adapter[chan_id];
+ struct i2c_atr_chan *chan = adap->algo_data;
+ struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
+ struct device *dev = atr->dev;
+
+ if (!adap)
+ return;
+
+ dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap));
+
+ 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);
+
+ atr->adapter[chan_id] = NULL;
+
+ fwnode_handle_put(fwnode);
+ mutex_destroy(&chan->orig_addrs_lock);
+ kfree(chan->orig_addrs);
+ kfree(chan);
+}
+EXPORT_SYMBOL_NS_GPL(i2c_atr_del_adapter, I2C_ATR);
+
+void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
+{
+ atr->priv = data;
+}
+EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
+
+void *i2c_atr_get_driver_data(struct i2c_atr *atr)
+{
+ return atr->priv;
+}
+EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
+
+MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
+MODULE_AUTHOR("Tomi Valkeinen <[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..10a313ab2105
--- /dev/null
+++ b/include/linux/i2c-atr.h
@@ -0,0 +1,117 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * I2C Address Translator
+ *
+ * Copyright (c) 2019,2022 Luca Ceresoli <[email protected]>
+ * Copyright (c) 2022,2023 Tomi Valkeinen <[email protected]>
+ *
+ * Based on i2c-mux.h
+ */
+
+#ifndef _LINUX_I2C_ATR_H
+#define _LINUX_I2C_ATR_H
+
+#include <linux/i2c.h>
+#include <linux/types.h>
+
+struct device;
+struct fwnode_handle;
+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_client *client, u16 *alias_id);
+ void (*detach_client)(struct i2c_atr *atr, u32 chan_id,
+ const struct i2c_client *client);
+};
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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
+ * &i2c_atr_ops->attach_client and &i2c_atr_ops->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);
+
+/**
+ * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by
+ * i2c_atr_add_adapter(). If no I2C bus has been added
+ * this function is a no-op.
+ * @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);
+
+/**
+ * i2c_atr_set_driver_data - Set private driver data to the i2c-atr instance.
+ * @atr: The I2C ATR
+ * @data: Pointer to the data to store
+ */
+void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data);
+
+/**
+ * i2c_atr_get_driver_data - Get the stored drive data.
+ * @atr: The I2C ATR
+ *
+ * Return: Pointer to the stored data
+ */
+void *i2c_atr_get_driver_data(struct i2c_atr *atr);
+
+#endif /* _LINUX_I2C_ATR_H */
--
2.34.1

2023-01-18 15:23:02

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On Wed, Jan 18, 2023 at 02:40:25PM +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

is is ?

> 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.

...

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

I'm not sure I got the real / virtual status of the adapters. Are the B and C
virtual ones, while A is the real?

...

> +#define ATR_MAX_ADAPTERS 99 /* Just a sanity limit */

Hmm... It's not clear why this is not 100, for example, and how 99 below is
related to that, assuming channel numbering is started from 0.

> +#define ATR_MAX_SYMLINK_LEN 16 /* Longest name is 10 chars: "channel-99" */

...

> + /* Ensure we have enough room to save the original addresses */
> + if (unlikely(chan->orig_addrs_size < num)) {
> + u16 *new_buf;
> +
> + new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);

I remember that I asked why we don't use krealloc_array() here... Perhaps
that we don't need to copy the old mapping table? Can we put a short comment
to clarify this in the code?

> + if (!new_buf)
> + return -ENOMEM;
> +
> + kfree(chan->orig_addrs);
> + chan->orig_addrs = new_buf;
> + chan->orig_addrs_size = num;
> + }

...

> +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;
> + int ret;
> +
> + if (max_adapters > ATR_MAX_ADAPTERS)
> + return ERR_PTR(-EINVAL);
> +
> + if (!ops || !ops->attach_client || !ops->detach_client)
> + return ERR_PTR(-EINVAL);

> + atr = devm_kzalloc(dev, struct_size(atr, adapter, max_adapters),
> + GFP_KERNEL);

How do you know (or why do we limit) that the scope of this function will be
only in ->probe()? Even though, I would replace devm_ by non-devm_ since there
is the tear-down function has to be called by the user anyway.

> + 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;
> +
> + atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call;
> + ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb);
> + if (ret) {
> + mutex_destroy(&atr->lock);
> + return ERR_PTR(ret);
> + }
> +
> + return atr;
> +}

...

> +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
> +{
> + char symlink_name[ATR_MAX_SYMLINK_LEN];

> +

Redundant blank line.

> + struct i2c_adapter *adap = atr->adapter[chan_id];
> + struct i2c_atr_chan *chan = adap->algo_data;
> + struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
> + struct device *dev = atr->dev;

> + if (!adap)
> + return;

Redundant check (it will be optimized out by compiler) or wrong assignments
above.

> + dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap));
> +
> + 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);
> +
> + atr->adapter[chan_id] = NULL;
> +
> + fwnode_handle_put(fwnode);
> + mutex_destroy(&chan->orig_addrs_lock);
> + kfree(chan->orig_addrs);
> + kfree(chan);
> +}

...

> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> +{
> + atr->priv = data;
> +}
> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> +
> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> +{
> + return atr->priv;
> +}
> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);

Just to be sure: Is it really _driver_ data and not _device instance_ data?

--
With Best Regards,
Andy Shevchenko


2023-01-18 16:20:44

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
> Hi,
>
> You can find the v6 from:
>
> https://lore.kernel.org/all/[email protected]/
>
> Main changes:
>
> * i2c-atr: Use bus notifier. This allows us to drop the patch that adds
> the attach_client/detach_client callbacks. On the downside, it removes
> the option for error handling if the translation setup fails, and also
> doesn't provide us the pointer to the i2c_board_info. I belive both
> are acceptable downsides.
>
> * Use fwnode in the fpdlink drivers instead of OF
>
> * Addressed all the review comments (I hope)
>
> * Lots of cosmetic or minor fixes which I came up while doing the fwnode
> change

I believe my comments to the first driver applies to the next two, so please
address them whenever you are agree / it's possible / it makes sense.

About ATR implementation. We have the i2c bus (Linux representation of
the driver model) and i2c_adapter and i2c_client objects there. Can't we
have an i2c_client_aliased in similar way and be transparent with users?

It's just a thought which may be well far from the possibility
to be implemented if even possible...

--
With Best Regards,
Andy Shevchenko


2023-01-18 16:46:47

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Wed, Jan 18, 2023 at 02:40:29PM +0200, Tomi Valkeinen wrote:
> Add driver for TI DS90UB960 FPD-Link III Deserializer.

...

> +#define UB960_MIN_AEQ_STROBE_POS -7

I believe it might need parentheses due to theoretical possibilities to be used
in the expression.

...

> +#define UB960_MIN_MANUAL_STROBE_POS -(7 + 6)

Ditto.

...

> + ret = regmap_read(priv->regmap, reg, &v);
> + if (ret)
> + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n",
> + __func__, reg, ret);

Not sure how this messages are useful and esp. parameters, since regmap has
already trace events. Maybe it's possible to narrow regmap traces to the
dedicated functions?

> + else
> + *val = v;

...

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

Any reason why not BIT() here, while above and below it's being used?

> + 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_csiport_select(struct ub960_data *priv, u8 nport)
> +{

Same comment as above.

> +}

...

> +out:

out_unlock: ?

> + mutex_unlock(&priv->reg_lock);
> +
> + return ret;

Also in some cases you are using 'else' (as 'if (ret) ... else') in some goto
approach. Can it be unified?

...

> + v &= ~mask;
> + v |= val;

Usual pattern we use is

v = (v & ~mask) | (val & mask);

But can you use regmap_update_bits()? And why not?

...

> + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
> + if (ret <= 0) {
> + dev_err(dev, "tx%u: failed to parse 'data-lanes': %d\n", nport,
> + ret);

Message is not consistent with the case ret == 0.

> + goto err_free_txport;
> + }

...

> + if (ret != 1) {
> + dev_err(dev,
> + "tx%u: 'link-frequencies' must contain a single frequency: %d\n",
> + nport, ret);
> + goto err_free_txport;
> + }

Shouldn't be here.
As Rob Herring told at least once that driver must not replace DT validator.

...

> + if (priv->tx_data_rate != 1600000000 &&
> + priv->tx_data_rate != 1200000000 &&
> + priv->tx_data_rate != 800000000 &&
> + priv->tx_data_rate != 400000000) {
> + dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport);
> + return -EINVAL;
> + }

Ditto.

...

> + dev_dbg(dev, "tx%u: nominal data rate: %u", nport, priv->tx_data_rate);

All these kind of debugging are needed for production?

...

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

Redundant blank line.

> + if (!ret) {

What's wrong with the positive and traditional check, i.e.

if (ret)
return;

?

> + 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
> + */

Multi-line comment is not in the style.

...

> + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {

Post-increment is good enough, no? Ditto for other places.
Esp. taking into account that some of them are using actually
post-inc. Why this difference?

> + struct ub960_rxport *rxport = priv->rxports[nport];
> +
> + if (!rxport || !rxport->vpoc)
> + continue;
> +
> + ret = regulator_enable(rxport->vpoc);
> + if (ret)
> + goto err_disable_vpocs;
> + }

...

> +err_disable_vpocs:
> + for (; nport > 0; --nport) {

while (nport--) {

> + struct ub960_rxport *rxport = priv->rxports[nport - 1];
> +
> + if (!rxport || !rxport->vpoc)
> + continue;
> +
> + regulator_disable(rxport->vpoc);
> + }

...

> + if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS))
> + return;

Always be careful about WARN*() APIs because with a little trick they may
become equivalent to BUG() which is a beast that nobody likes. I.o.w.
you have to have justify why this is needed and can't be replaced with
dev_*() or analogue.

Same for the other places with WARN*().

...

> + 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) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
> + else if (strobe_pos > 0)
> + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;

I'm wondering if clamp_t()/clamp_val() can be utilised here... And maybe in other
places in the driver.

...

> + ub960_write(priv, UB960_XR_SFILTER_CFG,

> + ((u8)strobe_min << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) |
> + ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT));

Why castings are needed?

...

> + *eq_level = (v & 0x7) + ((v >> 3) & 0x7);

GENMASK()?


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

A lot of magic 7 in the code. Are they all of the same semantic? Are they can
be converted to use a macro (including respective MIN/MAX macros)?

...

> + WARN_ON(eq_stage_1_select_value > 7);
> + WARN_ON(eq_stage_2_select_value > 7);

Why WARN()?

...

> + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
> +
> + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK);
> + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT;
> + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT;
> + v |= UB960_RR_AEQ_BYPASS_ENABLE; /* Enable AEQ Bypass */
> +
> + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v);

Can't you provide ub960_rxport_update_bits() ?

...

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

Can this be read at once as BE16/LE16 value?
Or if the stream of bytes, you can use le/be16_to_cpu().

> + parity_errors = (v1 << 8) | v2;

...

> + errors = (rx_port_sts1 & 0x2c) || (rx_port_sts2 & 0x20) ||
> + (bcc_sts & 0x3f) || (csi_rx_sts & 0xf) || csi_err_cnt ||

BIT()? GENMASK()?

At bare minimum this needs a good comment to explain all these magics.

> + parity_errors;

...

> + *ok = !errors;

How this is different to the something like returning 1 here (and 0 above)?
You may save some code by dropping redundant parameter.

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

You can wrap the body into readx_poll_timeout() from iopoll.h.

...

> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);

Same Q, can these be unified to some kind of bulk read?

...

> + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
> + nport, strobe_pos, eq_level,
> + v1 * 1000000 + v2 * 1000000 / 256);

Even this will be simpler with above suggestion.

...

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

Post-inc?

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

while(nport--)

(and drop those -1:s below)

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

...

> + if (priv->tx_data_rate == 1600000000)

Easy to make a mistake, perhaps something from units.h / time.h can be used?

> + csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN;

...

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

Ditto, but maybe defines for them?

...

> + switch (rxport->rx_mode) {
> + default:
> + WARN_ON(true);
> + fallthrough;

Maybe you can drop default completely and rely on compiler to produce a
warning?

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

...

> + u8 cur_vc[UB960_MAX_TX_NPORTS] = { 0 };

0 is not needed.

...

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

> +

Redundant blank line.

> + if (BIT(rx) & handled_mask)
> + continue;
> +
> + tx = ub960_pad_to_port(priv, route->source_pad);
> +
> + vc[rx] = cur_vc[tx]++;
> + handled_mask |= BIT(rx);
> + }

...

> + if (rx_data[nport].tx_port == 1)
> + fwd_ctl |= BIT(nport); /* forward to TX1 */
> + else
> + fwd_ctl &= ~BIT(nport); /* forward to TX0 */

This and many other similar places can be replaced by __assign_bit()
if the lvalue is type of unsigned long or can be made that type.

...

> +static void ub960_update_streaming_status(struct ub960_data *priv)
> +{
> + unsigned int i;

> + bool streaming = false;

Redundant

> + for (i = 0; i < UB960_MAX_NPORTS; ++i) {
> + if (priv->stream_enable_mask[i]) {
> + streaming = true;
> + break;
> + }
> + }

> + priv->streaming = streaming;

priv->streaming = i < UB960_MAX_NPORTS;

> +}

...

> + for (; nport > 0; --nport) {

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

...

> + char id[7];

u8?

> + for (unsigned int i = 0; i < 6; ++i)
> + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
> + id[6] = 0;

If it's only for printing, the 0 is not needed...

> + dev_info(dev, "ID '%s'\n", id);

...as you may put it as

dev_info(dev, "ID: '%.*s'\n", (int)sizeof(id), id);

(I wrote from the top of my head, maybe not compilable as is).

...

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

> +

Redundant blank line. I guess you may decrease your code by ~2.5% by removing
such unneeded blank lines here and there.

> + if (!ret && int_sts) {

if (ret)
return ...

if (!int_sts)
return IRQ_NONE; // No?

> + 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 %#02x\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;
> +}

...

> + if (cdr_mode > RXPORT_CDR_LAST) {
> + dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, cdr_mode);
> + return -EINVAL;
> + }

No DT validation if it's not used in (memory) allocation.

...

> + ret = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos",
> + &strobe_pos);
> + if (ret) {
> + if (ret != -EINVAL) {
> + dev_err(dev,
> + "rx%u: failed to read 'ti,strobe-pos': %d\n",
> + nport, ret);
> + return ret;
> + }
> + } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) {
> + dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", nport,
> + strobe_pos);
> + } else {
> + // NOTE: ignored unless global manual strobe pos is set

Style?

> + rxport->eq.strobe_pos = strobe_pos;
> + if (!priv->strobe.manual)
> + dev_warn(dev,
> + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
> + nport);
> + }

This and below looks a bit different to the above in the same function. Perhaps
these can be refactored to be less LoCs.

> + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
> + if (ret) {
> + if (ret != -EINVAL) {
> + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
> + nport, ret);
> + return ret;
> + }
> + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
> + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
> + eq_level);
> + } else {
> + rxport->eq.manual_eq = true;
> + rxport->eq.manual.eq_level = eq_level;
> + }
> +
> + ret = fwnode_property_read_u32(link_fwnode, "i2c-alias",
> + &ser_i2c_alias);
> + if (ret) {
> + dev_err(dev, "rx%u: failed to read 'i2c-alias': %d\n", nport,
> + ret);
> + return ret;
> + }

...

> +static struct fwnode_handle *
> +ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode,
> + unsigned int nport)
> +{
> + struct fwnode_handle *link_fwnode;
> + int ret;
> +
> + fwnode_for_each_child_node(links_fwnode, link_fwnode) {
> + u32 link_num;
> +
> + if (strncmp(fwnode_get_name(link_fwnode), "link@", 5) != 0)
> + continue;

str_has_prefix()

> +
> + ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num);
> + if (ret)

Refcount imbalance.

> + return NULL;
> +
> + if (nport == link_num) {
> + fwnode_handle_put(link_fwnode);
> + return link_fwnode;
> + }
> + }
> +
> + return NULL;
> +}

...

> + asd = v4l2_async_nf_add_fwnode(&priv->notifier,
> + rxport->source_ep_fwnode,
> + struct ub960_asd);
> + if (IS_ERR(asd)) {
> + dev_err(dev, "Failed to add subdev for source %u: %ld",

%pe ?

> + i, PTR_ERR(asd));
> + v4l2_async_nf_cleanup(&priv->notifier);
> + return PTR_ERR(asd);
> + }

...

> +err_pd_gpio:
> + if (priv->pd_gpio)

Dup test.

> + gpiod_set_value_cansleep(priv->pd_gpio, 1);

...

> + if (priv->pd_gpio)
> + gpiod_set_value_cansleep(priv->pd_gpio, 1);

Ditto.

...

> + priv->hw_data = of_device_get_match_data(dev);

Why of_ out of the blue?!

> + if (!priv->hw_data)
> + return -ENODEV;

...

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

GENMASK()

...

> + ub960_rxport_wait_locks(priv, 0xf, NULL);

Ditto?

...

> +static const struct i2c_device_id ub960_id[] = {
> + { "ds90ub960-q1", 0 },
> + { "ds90ub9702-q1", 0 },

Why driver data is different to OF case?

> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, ub960_id);
> +
> +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);

> +static struct i2c_driver ds90ub960_driver = {
> + .probe_new = ub960_probe,
> + .remove = ub960_remove,
> + .id_table = ub960_id,
> + .driver = {
> + .name = "ds90ub960",

> + .owner = THIS_MODULE,

Set by macro from the beginning of its, macro, existence.

> + .of_match_table = ub960_dt_ids,
> + },
> +};

...

> +struct ds90ub9xx_platform_data {
> + u32 port;
> + struct i2c_atr *atr;
> + unsigned long bc_rate;

Not sure why we need this to be public except, probably, atr...

--
With Best Regards,
Andy Shevchenko


2023-01-18 17:31:00

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hello Andy,

On Wed, 18 Jan 2023 16:23:53 +0200
Andy Shevchenko <[email protected]> wrote:

> On Wed, Jan 18, 2023 at 02:40:25PM +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
>
> is is ?
>
> > 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.
>
> ...
>
> > +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
>
> I'm not sure I got the real / virtual status of the adapters. Are the B and C
> virtual ones, while A is the real?

Let me reply, as I wrote these docs back at the times and thus I feel
guilty in case that's unclear. :)

I don't like the word "virtual" in this situation. A, B and C are all
physical busses, made of copper and run by electrons on PCBs. B and C
are the "remote" or "downstream" busses (w.r.t. the CPU), where the i2c
devices are and where transactions happen using the address that the
chip responds to. A is the "local" or "upstream" bus that is driven
directly by the CPU (*) and where address aliases are used. Using
aliases there is necessary because using address 0x10 would be
ambiguous as there are two 0x10 chips out there.

(*) There could be more layers of course, but still A is "closer to the
CPU than B and C", for the sake of completeness.

...

> > +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> > +{
> > + atr->priv = data;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> > +
> > +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> > +{
> > + return atr->priv;
> > +}
> > +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
>
> Just to be sure: Is it really _driver_ data and not _device instance_ data?

It is device instance data indeed. I don't remember why this got
changed, but in v3 it was i2c_atr_set_clientdata().

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

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

2023-01-18 17:56:07

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On Wed, Jan 18, 2023 at 06:17:53PM +0100, Luca Ceresoli wrote:
> On Wed, 18 Jan 2023 16:23:53 +0200
> Andy Shevchenko <[email protected]> wrote:

...

> > > +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
> >
> > I'm not sure I got the real / virtual status of the adapters. Are the B and C
> > virtual ones, while A is the real?
>
> Let me reply, as I wrote these docs back at the times and thus I feel
> guilty in case that's unclear. :)
>
> I don't like the word "virtual" in this situation. A, B and C are all
> physical busses, made of copper and run by electrons on PCBs. B and C
> are the "remote" or "downstream" busses (w.r.t. the CPU), where the i2c
> devices are and where transactions happen using the address that the
> chip responds to. A is the "local" or "upstream" bus that is driven
> directly by the CPU (*) and where address aliases are used. Using
> aliases there is necessary because using address 0x10 would be
> ambiguous as there are two 0x10 chips out there.
>
> (*) There could be more layers of course, but still A is "closer to the
> CPU than B and C", for the sake of completeness.

Can the diagram and/or text be updated to elaborate this?

...

> > > +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> > > +{
> > > + atr->priv = data;
> > > +}
> > > +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> > > +
> > > +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> > > +{
> > > + return atr->priv;
> > > +}
> > > +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
> >
> > Just to be sure: Is it really _driver_ data and not _device instance_ data?
>
> It is device instance data indeed. I don't remember why this got
> changed, but in v3 it was i2c_atr_set_clientdata().

It's me who was and is against calling it clientdata due to possible
confusion with i2c_set/get_clientdata() that is about *driver data*.
I missed that time the fact that this is about device instance data.
I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?

--
With Best Regards,
Andy Shevchenko


2023-01-18 18:17:18

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

On 18/01/2023 18:01, Andy Shevchenko wrote:
> On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
>> Hi,
>>
>> You can find the v6 from:
>>
>> https://lore.kernel.org/all/[email protected]/
>>
>> Main changes:
>>
>> * i2c-atr: Use bus notifier. This allows us to drop the patch that adds
>> the attach_client/detach_client callbacks. On the downside, it removes
>> the option for error handling if the translation setup fails, and also
>> doesn't provide us the pointer to the i2c_board_info. I belive both
>> are acceptable downsides.
>>
>> * Use fwnode in the fpdlink drivers instead of OF
>>
>> * Addressed all the review comments (I hope)
>>
>> * Lots of cosmetic or minor fixes which I came up while doing the fwnode
>> change
>
> I believe my comments to the first driver applies to the next two, so please
> address them whenever you are agree / it's possible / it makes sense.
>
> About ATR implementation. We have the i2c bus (Linux representation of
> the driver model) and i2c_adapter and i2c_client objects there. Can't we
> have an i2c_client_aliased in similar way and be transparent with users?

Can you clarify what you mean here?

The i2c_clients are not aware of the i2c-atr. They are normal i2c
clients. The FPD-Link drivers are aware of the ATR, as the FPD-Link
hardware contains the ATR support.

Tomi

2023-01-18 18:46:04

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
> On 18/01/2023 18:01, Andy Shevchenko wrote:
> > On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
> > > Hi,
> > >
> > > You can find the v6 from:
> > >
> > > https://lore.kernel.org/all/[email protected]/
> > >
> > > Main changes:
> > >
> > > * i2c-atr: Use bus notifier. This allows us to drop the patch that adds
> > > the attach_client/detach_client callbacks. On the downside, it removes
> > > the option for error handling if the translation setup fails, and also
> > > doesn't provide us the pointer to the i2c_board_info. I belive both
> > > are acceptable downsides.
> > >
> > > * Use fwnode in the fpdlink drivers instead of OF
> > >
> > > * Addressed all the review comments (I hope)
> > >
> > > * Lots of cosmetic or minor fixes which I came up while doing the fwnode
> > > change
> >
> > I believe my comments to the first driver applies to the next two, so please
> > address them whenever you are agree / it's possible / it makes sense.
> >
> > About ATR implementation. We have the i2c bus (Linux representation of
> > the driver model) and i2c_adapter and i2c_client objects there. Can't we
> > have an i2c_client_aliased in similar way and be transparent with users?

> Can you clarify what you mean here?
>
> The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
> The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
> the ATR support.

Can't that hardware be represented as I2C adapter? In such case the ATR specifics
can be hidden from the client (drivers).

I'm worrying about code duplication and other things that leak into drivers as
ATR callbacks. It might be that I didn't get how hw exactly functioning on this
level and why we need those callbacks.

--
With Best Regards,
Andy Shevchenko


2023-01-19 09:04:16

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hi Andy,

On Wed, 18 Jan 2023 19:39:46 +0200
Andy Shevchenko <[email protected]> wrote:

> On Wed, Jan 18, 2023 at 06:17:53PM +0100, Luca Ceresoli wrote:
> > On Wed, 18 Jan 2023 16:23:53 +0200
> > Andy Shevchenko <[email protected]> wrote:
>
> ...
>
> > > > +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
> > >
> > > I'm not sure I got the real / virtual status of the adapters. Are the B and C
> > > virtual ones, while A is the real?
> >
> > Let me reply, as I wrote these docs back at the times and thus I feel
> > guilty in case that's unclear. :)
> >
> > I don't like the word "virtual" in this situation. A, B and C are all
> > physical busses, made of copper and run by electrons on PCBs. B and C
> > are the "remote" or "downstream" busses (w.r.t. the CPU), where the i2c
> > devices are and where transactions happen using the address that the
> > chip responds to. A is the "local" or "upstream" bus that is driven
> > directly by the CPU (*) and where address aliases are used. Using
> > aliases there is necessary because using address 0x10 would be
> > ambiguous as there are two 0x10 chips out there.
> >
> > (*) There could be more layers of course, but still A is "closer to the
> > CPU than B and C", for the sake of completeness.
>
> Can the diagram and/or text be updated to elaborate this?

Let's see whether the text below is better. I haven't changed the
image, I don't think we can do much more in ASCII, but maybe we can
replace it with an SVG [0]?

[0]
https://github.com/lucaceresoli/docs/blob/master/video-serdes-linux/images/i2c-ti.svg

A typical example follows.

Topology::

Slave X @ 0x10
.-----. |
.-----. | |---+---- B
| CPU |--A--| ATR |
`-----' | |---+---- C
`-----' |
Slave Y @ 0x10

Alias table:

A, B and C are three physical I2C busses, electrically independent from
each other. The ATR receives the transactions initiated on bus A and
propagates them on bus B or bus C or none depending on the device address
in the transaction and based on the alias table.

Alias table:

.. table::

=============== =====
Client Alias
=============== =====
X (bus B, 0x10) 0x20
Y (bus C, 0x10) 0x30
=============== =====

Transaction:

- Slave X driver sends a transaction (on adapter B), slave address 0x10
- ATR driver finds slave X is on bus B and has alias 0x20, rewrites
messages with address 0x20, forwards to adapter A
- Physical I2C transaction on bus A, slave address 0x20
- ATR chip detects transaction on address 0x20, finds it in table,
propagates transaction on bus B with address translated to 0x10,
keeps clock streched on bus A waiting for reply
- Slave X chip (on bus B) detects transaction at its own physical
address 0x10 and replies normally
- ATR chip stops clock stretching and forwards reply on bus A,
with address translated back to 0x20
- ATR driver receives the reply, rewrites messages with address 0x10
as they were initially
- Slave X driver gets back the msgs[], with reply and address 0x10

Let me know whether this sounds better. And perhaps Tomi can further
improve it.


> > > > +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> > > > +{
> > > > + atr->priv = data;
> > > > +}
> > > > +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> > > > +
> > > > +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> > > > +{
> > > > + return atr->priv;
> > > > +}
> > > > +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
> > >
> > > Just to be sure: Is it really _driver_ data and not _device instance_ data?
> >
> > It is device instance data indeed. I don't remember why this got
> > changed, but in v3 it was i2c_atr_set_clientdata().
>
> It's me who was and is against calling it clientdata due to possible
> confusion with i2c_set/get_clientdata() that is about *driver data*.
> I missed that time the fact that this is about device instance data.
> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?

Not sure I'm following you here. The i2c_atr_set_clientdata() name was
given for similarity with i2c_set_clientdata(). The latter wraps
dev_set_drvdata(), which sets `struct device`->driver_data. There is
one driver_data per each `struct device` instance, not per each driver.
The same goes for i2c_atr_set_driver_data(): there is one priv pointer
per each `struct i2c_atr` instance.

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

2023-01-19 09:27:50

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

Hi Andy,

On Wed, 18 Jan 2023 19:43:23 +0200
Andy Shevchenko <[email protected]> wrote:

> On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
> > On 18/01/2023 18:01, Andy Shevchenko wrote:
> > > On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
> > > > Hi,
> > > >
> > > > You can find the v6 from:
> > > >
> > > > https://lore.kernel.org/all/[email protected]/
> > > >
> > > > Main changes:
> > > >
> > > > * i2c-atr: Use bus notifier. This allows us to drop the patch that adds
> > > > the attach_client/detach_client callbacks. On the downside, it removes
> > > > the option for error handling if the translation setup fails, and also
> > > > doesn't provide us the pointer to the i2c_board_info. I belive both
> > > > are acceptable downsides.
> > > >
> > > > * Use fwnode in the fpdlink drivers instead of OF
> > > >
> > > > * Addressed all the review comments (I hope)
> > > >
> > > > * Lots of cosmetic or minor fixes which I came up while doing the fwnode
> > > > change
> > >
> > > I believe my comments to the first driver applies to the next two, so please
> > > address them whenever you are agree / it's possible / it makes sense.
> > >
> > > About ATR implementation. We have the i2c bus (Linux representation of
> > > the driver model) and i2c_adapter and i2c_client objects there. Can't we
> > > have an i2c_client_aliased in similar way and be transparent with users?
>
> > Can you clarify what you mean here?
> >
> > The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
> > The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
> > the ATR support.
>
> Can't that hardware be represented as I2C adapter? In such case the ATR specifics
> can be hidden from the client (drivers).
>
> I'm worrying about code duplication and other things that leak into drivers as
> ATR callbacks.

Which callbacks do you refer to? i2c_atr_ops? I don't think we can do
without the attach/detach_client ones, it's where the driver-specific
implementation is hooked for the generic ATR infra to call it.

However now I noticed the select/deselect ops are still there. IIRC
they are not used by any driver and in the past the plan was to just
remove them. Tomi, do you think there is a good reason to keep them?

> It might be that I didn't get how hw exactly functioning on this
> level and why we need those callbacks.

As far as "how hw exactly works", in case you haven't seen that, the
best explanation I was able to give is in my ELCE 2019 talk, at minute
~22. It's a 2-3 minute watch. The slides have pointers to other talks
and discussion.

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

2023-01-19 10:34:40

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On 19/01/2023 10:21, Luca Ceresoli wrote:

<snip>

>>>>> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
>>>>> +{
>>>>> + atr->priv = data;
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
>>>>> +
>>>>> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
>>>>> +{
>>>>> + return atr->priv;
>>>>> +}
>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
>>>>
>>>> Just to be sure: Is it really _driver_ data and not _device instance_ data?
>>>
>>> It is device instance data indeed. I don't remember why this got
>>> changed, but in v3 it was i2c_atr_set_clientdata().
>>
>> It's me who was and is against calling it clientdata due to possible
>> confusion with i2c_set/get_clientdata() that is about *driver data*.
>> I missed that time the fact that this is about device instance data.
>> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?
>
> Not sure I'm following you here. The i2c_atr_set_clientdata() name was
> given for similarity with i2c_set_clientdata(). The latter wraps
> dev_set_drvdata(), which sets `struct device`->driver_data. There is
> one driver_data per each `struct device` instance, not per each driver.
> The same goes for i2c_atr_set_driver_data(): there is one priv pointer
> per each `struct i2c_atr` instance.

I'm a bit confused. What is "driver data" and what is "device instance
data"?

This deals with the driver's private data, where the "driver" is the
owner/creator of the i2c-atr. The i2c-atr itself doesn't have a device
(it's kind of part of the owner's device), and there's no driver in
i2c-atr.c

I don't like "client" here, as it reminds me of i2c_client (especially
as we're in i2c context).

What about i2c_atr_set_user_data()? Or "owner_data"?

Tomi

2023-01-19 11:12:25

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On 18/01/2023 16:23, Andy Shevchenko wrote:
> On Wed, Jan 18, 2023 at 02:40:25PM +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
>
> is is ?

Is is a typo, I'll fix it.

>> 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.
>
> ...
>
>> +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
>
> I'm not sure I got the real / virtual status of the adapters. Are the B and C
> virtual ones, while A is the real?
>
> ...
>
>> +#define ATR_MAX_ADAPTERS 99 /* Just a sanity limit */
>
> Hmm... It's not clear why this is not 100, for example, and how 99 below is
> related to that, assuming channel numbering is started from 0.
>
>> +#define ATR_MAX_SYMLINK_LEN 16 /* Longest name is 10 chars: "channel-99" */

Right, it should be 100.

I think I set the ATR_MAX_SYMLINK_LEN to 16 just for alignment, but that
probably doesn't make sense and I should just set ATR_MAX_SYMLINK_LEN to
11 (10 + zero byte).

> ...
>
>> + /* Ensure we have enough room to save the original addresses */
>> + if (unlikely(chan->orig_addrs_size < num)) {
>> + u16 *new_buf;
>> +
>> + new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);
>
> I remember that I asked why we don't use krealloc_array() here... Perhaps
> that we don't need to copy the old mapping table? Can we put a short comment
> to clarify this in the code?

Yes, we don't care about the old data, we just require the buffer to be
large enough.

I'm not sure what kind of comment you want here. Isn't this a common
idiom, where you have a buffer for temporary data, but you might need to
resize at some point if you need a larger one?

>> + if (!new_buf)
>> + return -ENOMEM;
>> +
>> + kfree(chan->orig_addrs);
>> + chan->orig_addrs = new_buf;
>> + chan->orig_addrs_size = num;
>> + }
>
> ...
>
>> +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;
>> + int ret;
>> +
>> + if (max_adapters > ATR_MAX_ADAPTERS)
>> + return ERR_PTR(-EINVAL);
>> +
>> + if (!ops || !ops->attach_client || !ops->detach_client)
>> + return ERR_PTR(-EINVAL);
>
>> + atr = devm_kzalloc(dev, struct_size(atr, adapter, max_adapters),
>> + GFP_KERNEL);
>
> How do you know (or why do we limit) that the scope of this function will be
> only in ->probe()? Even though, I would replace devm_ by non-devm_ since there
> is the tear-down function has to be called by the user anyway.

That's a very good point. I don't know why devm_kzalloc is used here.

>> + 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;
>> +
>> + atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call;
>> + ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb);
>> + if (ret) {
>> + mutex_destroy(&atr->lock);
>> + return ERR_PTR(ret);
>> + }
>> +
>> + return atr;
>> +}
>
> ...
>
>> +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id)
>> +{
>> + char symlink_name[ATR_MAX_SYMLINK_LEN];
>
>> +
>
> Redundant blank line.

Right.

>> + struct i2c_adapter *adap = atr->adapter[chan_id];
>> + struct i2c_atr_chan *chan = adap->algo_data;
>> + struct fwnode_handle *fwnode = dev_fwnode(&adap->dev);
>> + struct device *dev = atr->dev;
>
>> + if (!adap)
>> + return;
>
> Redundant check (it will be optimized out by compiler) or wrong assignments
> above.

Indeed.

The doc for the function says "If no I2C bus has been added this
function is a no-op", so we need the check, and I need to fix the
assignments.

Although to be honest, I don't usually like this kind of "do nothing if
given wrong parameters".

Tomi

2023-01-19 12:19:52

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hi Tomi, Andy,

On Thu, 19 Jan 2023 12:09:57 +0200
Tomi Valkeinen <[email protected]> wrote:

> On 19/01/2023 10:21, Luca Ceresoli wrote:
>
> <snip>
>
> >>>>> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> >>>>> +{
> >>>>> + atr->priv = data;
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> >>>>> +
> >>>>> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> >>>>> +{
> >>>>> + return atr->priv;
> >>>>> +}
> >>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
> >>>>
> >>>> Just to be sure: Is it really _driver_ data and not _device instance_ data?
> >>>
> >>> It is device instance data indeed. I don't remember why this got
> >>> changed, but in v3 it was i2c_atr_set_clientdata().
> >>
> >> It's me who was and is against calling it clientdata due to possible
> >> confusion with i2c_set/get_clientdata() that is about *driver data*.
> >> I missed that time the fact that this is about device instance data.
> >> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?
> >
> > Not sure I'm following you here. The i2c_atr_set_clientdata() name was
> > given for similarity with i2c_set_clientdata(). The latter wraps
> > dev_set_drvdata(), which sets `struct device`->driver_data. There is
> > one driver_data per each `struct device` instance, not per each driver.
> > The same goes for i2c_atr_set_driver_data(): there is one priv pointer
> > per each `struct i2c_atr` instance.
>
> I'm a bit confused. What is "driver data" and what is "device instance
> data"?
>
> This deals with the driver's private data, where the "driver" is the
> owner/creator of the i2c-atr. The i2c-atr itself doesn't have a device
> (it's kind of part of the owner's device), and there's no driver in
> i2c-atr.c
>
> I don't like "client" here, as it reminds me of i2c_client (especially
> as we're in i2c context).
>
> What about i2c_atr_set_user_data()? Or "owner_data"?

Ah, only now I got the point Andy made initially about "client" not
being an appropriate word.

In i2c we have:

i2c_set_clientdata(struct i2c_client *client, void *data)
^^^^^^~~~~ ^^^^^^ ~~~~

so "client" clearly makes sense there, now here.

The same logic applied here would lead to:

i2c_atr_set_atrdata(struct i2c_atr *atr, void *data)
^^^~~~~ ^^^ ~~~~

which makes sense but it is a ugly IMO.

So I think i2c_atr_get_driver_data() in this v7 makes sense, it's to
set the data that the ATR driver instance needs.

This is coherent with logic in spi/spi.h:

spi_set_drvdata(struct spi_device *spi, void *data)

except for the abbreviation ("_drvdata" vs "_driver_data").

Andy, Tomi, would i2c_atr_set_drvdata() be OK for you, just like SPI
does?

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

2023-01-19 12:30:00

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On 19/01/2023 13:35, Luca Ceresoli wrote:
> Hi Tomi, Andy,
>
> On Thu, 19 Jan 2023 12:09:57 +0200
> Tomi Valkeinen <[email protected]> wrote:
>
>> On 19/01/2023 10:21, Luca Ceresoli wrote:
>>
>> <snip>
>>
>>>>>>> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
>>>>>>> +{
>>>>>>> + atr->priv = data;
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
>>>>>>> +
>>>>>>> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
>>>>>>> +{
>>>>>>> + return atr->priv;
>>>>>>> +}
>>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
>>>>>>
>>>>>> Just to be sure: Is it really _driver_ data and not _device instance_ data?
>>>>>
>>>>> It is device instance data indeed. I don't remember why this got
>>>>> changed, but in v3 it was i2c_atr_set_clientdata().
>>>>
>>>> It's me who was and is against calling it clientdata due to possible
>>>> confusion with i2c_set/get_clientdata() that is about *driver data*.
>>>> I missed that time the fact that this is about device instance data.
>>>> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?
>>>
>>> Not sure I'm following you here. The i2c_atr_set_clientdata() name was
>>> given for similarity with i2c_set_clientdata(). The latter wraps
>>> dev_set_drvdata(), which sets `struct device`->driver_data. There is
>>> one driver_data per each `struct device` instance, not per each driver.
>>> The same goes for i2c_atr_set_driver_data(): there is one priv pointer
>>> per each `struct i2c_atr` instance.
>>
>> I'm a bit confused. What is "driver data" and what is "device instance
>> data"?
>>
>> This deals with the driver's private data, where the "driver" is the
>> owner/creator of the i2c-atr. The i2c-atr itself doesn't have a device
>> (it's kind of part of the owner's device), and there's no driver in
>> i2c-atr.c
>>
>> I don't like "client" here, as it reminds me of i2c_client (especially
>> as we're in i2c context).
>>
>> What about i2c_atr_set_user_data()? Or "owner_data"?
>
> Ah, only now I got the point Andy made initially about "client" not
> being an appropriate word.
>
> In i2c we have:
>
> i2c_set_clientdata(struct i2c_client *client, void *data)
> ^^^^^^~~~~ ^^^^^^ ~~~~
>
> so "client" clearly makes sense there, now here.

Isn't that also used by the i2c_client? A driver which handles an i2c
device is the "i2c client", in a sense?

> The same logic applied here would lead to:
>
> i2c_atr_set_atrdata(struct i2c_atr *atr, void *data)
> ^^^~~~~ ^^^ ~~~~
>
> which makes sense but it is a ugly IMO.

Here, I think, there's a bit of a difference to the i2c_client case, as
we have a separate component for the i2c-atr. Although I guess one can
argue that the top level driver is the ATR driver, as it handles the HW,
and i2c-atr.c is just a set of helpers, so... I don't know =).

> So I think i2c_atr_get_driver_data() in this v7 makes sense, it's to
> set the data that the ATR driver instance needs.
>
> This is coherent with logic in spi/spi.h:
>
> spi_set_drvdata(struct spi_device *spi, void *data)
>
> except for the abbreviation ("_drvdata" vs "_driver_data").
>
> Andy, Tomi, would i2c_atr_set_drvdata() be OK for you, just like SPI
> does?

Well, I'm good with the current i2c_atr_set_driver_data(). If all agrees
that it's "driver data", I'd rather keep it like that. I find this
"drvdata" style very odd. Why no underscore between drv and data? Why
abbreviate drv, it doesn't really help anything here?

That said, I'm also fine with i2c_atr_set_drvdata if that's the popular
opinion (between the three of us, so far ;).

Tomi

2023-01-19 12:47:08

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

On 19/01/2023 10:43, Luca Ceresoli wrote:
> Hi Andy,
>
> On Wed, 18 Jan 2023 19:43:23 +0200
> Andy Shevchenko <[email protected]> wrote:
>
>> On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
>>> On 18/01/2023 18:01, Andy Shevchenko wrote:
>>>> On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
>>>>> Hi,
>>>>>
>>>>> You can find the v6 from:
>>>>>
>>>>> https://lore.kernel.org/all/[email protected]/
>>>>>
>>>>> Main changes:
>>>>>
>>>>> * i2c-atr: Use bus notifier. This allows us to drop the patch that adds
>>>>> the attach_client/detach_client callbacks. On the downside, it removes
>>>>> the option for error handling if the translation setup fails, and also
>>>>> doesn't provide us the pointer to the i2c_board_info. I belive both
>>>>> are acceptable downsides.
>>>>>
>>>>> * Use fwnode in the fpdlink drivers instead of OF
>>>>>
>>>>> * Addressed all the review comments (I hope)
>>>>>
>>>>> * Lots of cosmetic or minor fixes which I came up while doing the fwnode
>>>>> change
>>>>
>>>> I believe my comments to the first driver applies to the next two, so please
>>>> address them whenever you are agree / it's possible / it makes sense.
>>>>
>>>> About ATR implementation. We have the i2c bus (Linux representation of
>>>> the driver model) and i2c_adapter and i2c_client objects there. Can't we
>>>> have an i2c_client_aliased in similar way and be transparent with users?
>>
>>> Can you clarify what you mean here?
>>>
>>> The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
>>> The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
>>> the ATR support.
>>
>> Can't that hardware be represented as I2C adapter? In such case the ATR specifics
>> can be hidden from the client (drivers).
>>
>> I'm worrying about code duplication and other things that leak into drivers as
>> ATR callbacks.
>
> Which callbacks do you refer to? i2c_atr_ops? I don't think we can do
> without the attach/detach_client ones, it's where the driver-specific
> implementation is hooked for the generic ATR infra to call it.
>
> However now I noticed the select/deselect ops are still there. IIRC
> they are not used by any driver and in the past the plan was to just
> remove them. Tomi, do you think there is a good reason to keep them?

I thought you had a reason to add them, so I didn't remove them =). I
can drop them.

Tomi

2023-01-19 13:42:10

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

Hi Tomi,

On Thu, 19 Jan 2023 14:40:24 +0200
Tomi Valkeinen <[email protected]> wrote:

> On 19/01/2023 10:43, Luca Ceresoli wrote:
> > Hi Andy,
> >
> > On Wed, 18 Jan 2023 19:43:23 +0200
> > Andy Shevchenko <[email protected]> wrote:
> >
> >> On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
> >>> On 18/01/2023 18:01, Andy Shevchenko wrote:
> >>>> On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
> >>>>> Hi,
> >>>>>
> >>>>> You can find the v6 from:
> >>>>>
> >>>>> https://lore.kernel.org/all/[email protected]/
> >>>>>
> >>>>> Main changes:
> >>>>>
> >>>>> * i2c-atr: Use bus notifier. This allows us to drop the patch that adds
> >>>>> the attach_client/detach_client callbacks. On the downside, it removes
> >>>>> the option for error handling if the translation setup fails, and also
> >>>>> doesn't provide us the pointer to the i2c_board_info. I belive both
> >>>>> are acceptable downsides.
> >>>>>
> >>>>> * Use fwnode in the fpdlink drivers instead of OF
> >>>>>
> >>>>> * Addressed all the review comments (I hope)
> >>>>>
> >>>>> * Lots of cosmetic or minor fixes which I came up while doing the fwnode
> >>>>> change
> >>>>
> >>>> I believe my comments to the first driver applies to the next two, so please
> >>>> address them whenever you are agree / it's possible / it makes sense.
> >>>>
> >>>> About ATR implementation. We have the i2c bus (Linux representation of
> >>>> the driver model) and i2c_adapter and i2c_client objects there. Can't we
> >>>> have an i2c_client_aliased in similar way and be transparent with users?
> >>
> >>> Can you clarify what you mean here?
> >>>
> >>> The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
> >>> The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
> >>> the ATR support.
> >>
> >> Can't that hardware be represented as I2C adapter? In such case the ATR specifics
> >> can be hidden from the client (drivers).
> >>
> >> I'm worrying about code duplication and other things that leak into drivers as
> >> ATR callbacks.
> >
> > Which callbacks do you refer to? i2c_atr_ops? I don't think we can do
> > without the attach/detach_client ones, it's where the driver-specific
> > implementation is hooked for the generic ATR infra to call it.
> >
> > However now I noticed the select/deselect ops are still there. IIRC
> > they are not used by any driver and in the past the plan was to just
> > remove them. Tomi, do you think there is a good reason to keep them?
>
> I thought you had a reason to add them, so I didn't remove them =). I
> can drop them.

Yes, please drop them.

It's the usual "it looked like a good idea" situation... :)

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

2023-01-19 13:54:43

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hi Tomi, Andy,

On Thu, 19 Jan 2023 14:22:26 +0200
Tomi Valkeinen <[email protected]> wrote:

> On 19/01/2023 13:35, Luca Ceresoli wrote:
> > Hi Tomi, Andy,
> >
> > On Thu, 19 Jan 2023 12:09:57 +0200
> > Tomi Valkeinen <[email protected]> wrote:
> >
> >> On 19/01/2023 10:21, Luca Ceresoli wrote:
> >>
> >> <snip>
> >>
> >>>>>>> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> >>>>>>> +{
> >>>>>>> + atr->priv = data;
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> >>>>>>> +
> >>>>>>> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> >>>>>>> +{
> >>>>>>> + return atr->priv;
> >>>>>>> +}
> >>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
> >>>>>>
> >>>>>> Just to be sure: Is it really _driver_ data and not _device instance_ data?
> >>>>>
> >>>>> It is device instance data indeed. I don't remember why this got
> >>>>> changed, but in v3 it was i2c_atr_set_clientdata().
> >>>>
> >>>> It's me who was and is against calling it clientdata due to possible
> >>>> confusion with i2c_set/get_clientdata() that is about *driver data*.
> >>>> I missed that time the fact that this is about device instance data.
> >>>> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?
> >>>
> >>> Not sure I'm following you here. The i2c_atr_set_clientdata() name was
> >>> given for similarity with i2c_set_clientdata(). The latter wraps
> >>> dev_set_drvdata(), which sets `struct device`->driver_data. There is
> >>> one driver_data per each `struct device` instance, not per each driver.
> >>> The same goes for i2c_atr_set_driver_data(): there is one priv pointer
> >>> per each `struct i2c_atr` instance.
> >>
> >> I'm a bit confused. What is "driver data" and what is "device instance
> >> data"?
> >>
> >> This deals with the driver's private data, where the "driver" is the
> >> owner/creator of the i2c-atr. The i2c-atr itself doesn't have a device
> >> (it's kind of part of the owner's device), and there's no driver in
> >> i2c-atr.c
> >>
> >> I don't like "client" here, as it reminds me of i2c_client (especially
> >> as we're in i2c context).
> >>
> >> What about i2c_atr_set_user_data()? Or "owner_data"?
> >
> > Ah, only now I got the point Andy made initially about "client" not
> > being an appropriate word.
> >
> > In i2c we have:
> >
> > i2c_set_clientdata(struct i2c_client *client, void *data)
> > ^^^^^^~~~~ ^^^^^^ ~~~~
> >
> > so "client" clearly makes sense there, now here.
>
> Isn't that also used by the i2c_client? A driver which handles an i2c
> device is the "i2c client", in a sense?
>
> > The same logic applied here would lead to:
> >
> > i2c_atr_set_atrdata(struct i2c_atr *atr, void *data)
> > ^^^~~~~ ^^^ ~~~~
> >
> > which makes sense but it is a ugly IMO.
>
> Here, I think, there's a bit of a difference to the i2c_client case, as
> we have a separate component for the i2c-atr. Although I guess one can
> argue that the top level driver is the ATR driver, as it handles the HW,
> and i2c-atr.c is just a set of helpers, so... I don't know =).
>
> > So I think i2c_atr_get_driver_data() in this v7 makes sense, it's to
> > set the data that the ATR driver instance needs.
> >
> > This is coherent with logic in spi/spi.h:
> >
> > spi_set_drvdata(struct spi_device *spi, void *data)
> >
> > except for the abbreviation ("_drvdata" vs "_driver_data").
> >
> > Andy, Tomi, would i2c_atr_set_drvdata() be OK for you, just like SPI
> > does?
>
> Well, I'm good with the current i2c_atr_set_driver_data(). If all agrees
> that it's "driver data", I'd rather keep it like that. I find this
> "drvdata" style very odd. Why no underscore between drv and data? Why
> abbreviate drv, it doesn't really help anything here?

Agreed, I'm OK with either form of "driver data".


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

2023-01-19 13:55:28

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On 19/01/2023 10:21, Luca Ceresoli wrote:
> Hi Andy,
>
> On Wed, 18 Jan 2023 19:39:46 +0200
> Andy Shevchenko <[email protected]> wrote:
>
>> On Wed, Jan 18, 2023 at 06:17:53PM +0100, Luca Ceresoli wrote:
>>> On Wed, 18 Jan 2023 16:23:53 +0200
>>> Andy Shevchenko <[email protected]> wrote:
>>
>> ...
>>
>>>>> +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
>>>>
>>>> I'm not sure I got the real / virtual status of the adapters. Are the B and C
>>>> virtual ones, while A is the real?
>>>
>>> Let me reply, as I wrote these docs back at the times and thus I feel
>>> guilty in case that's unclear. :)
>>>
>>> I don't like the word "virtual" in this situation. A, B and C are all
>>> physical busses, made of copper and run by electrons on PCBs. B and C
>>> are the "remote" or "downstream" busses (w.r.t. the CPU), where the i2c
>>> devices are and where transactions happen using the address that the
>>> chip responds to. A is the "local" or "upstream" bus that is driven
>>> directly by the CPU (*) and where address aliases are used. Using
>>> aliases there is necessary because using address 0x10 would be
>>> ambiguous as there are two 0x10 chips out there.
>>>
>>> (*) There could be more layers of course, but still A is "closer to the
>>> CPU than B and C", for the sake of completeness.
>>
>> Can the diagram and/or text be updated to elaborate this?
>
> Let's see whether the text below is better. I haven't changed the
> image, I don't think we can do much more in ASCII, but maybe we can
> replace it with an SVG [0]?
>
> [0]
> https://github.com/lucaceresoli/docs/blob/master/video-serdes-linux/images/i2c-ti.svg
>
> A typical example follows.
>
> Topology::
>
> Slave X @ 0x10
> .-----. |
> .-----. | |---+---- B
> | CPU |--A--| ATR |
> `-----' | |---+---- C
> `-----' |
> Slave Y @ 0x10

Slightly beside the point of this discussion, but one thing (I think) I
tried to highlight in some older cover letter was that we don't really
have the above structure. We have something like this (a quick edit, sorry):

.------. Slave X @ 0x10
.------. | FPDS | |
.-----. | FPDD |-F1-`------'---+---- B
| CPU |--A--| ATR |
`-----' | |-F2-.------.---+---- C
`------' | FPDS | |
`------' Slave Y @ 0x10

Where FPDD = Deserializer, FPDS = Serializer, F1/F2 = FPD-Link bus 1/2.

So the ATR functionality is in the deserializer, but the actual remote
i2c bus is on the serializer.

The current code manages this so that the deserializer driver owns the
ATR and programs the HW (as the ATR is part of the deserializer), but
it's the serializer driver that adds the remote adapter to the ATR
(using i2c_atr pointer given by the deserializer driver).

Tomi

2023-01-19 14:27:44

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hi Tomi, Andy,

On Thu, 19 Jan 2023 14:39:09 +0200
Tomi Valkeinen <[email protected]> wrote:

> On 19/01/2023 10:21, Luca Ceresoli wrote:
> > Hi Andy,
> >
> > On Wed, 18 Jan 2023 19:39:46 +0200
> > Andy Shevchenko <[email protected]> wrote:
> >
> >> On Wed, Jan 18, 2023 at 06:17:53PM +0100, Luca Ceresoli wrote:
> >>> On Wed, 18 Jan 2023 16:23:53 +0200
> >>> Andy Shevchenko <[email protected]> wrote:
> >>
> >> ...
> >>
> >>>>> +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
> >>>>
> >>>> I'm not sure I got the real / virtual status of the adapters. Are the B and C
> >>>> virtual ones, while A is the real?
> >>>
> >>> Let me reply, as I wrote these docs back at the times and thus I feel
> >>> guilty in case that's unclear. :)
> >>>
> >>> I don't like the word "virtual" in this situation. A, B and C are all
> >>> physical busses, made of copper and run by electrons on PCBs. B and C
> >>> are the "remote" or "downstream" busses (w.r.t. the CPU), where the i2c
> >>> devices are and where transactions happen using the address that the
> >>> chip responds to. A is the "local" or "upstream" bus that is driven
> >>> directly by the CPU (*) and where address aliases are used. Using
> >>> aliases there is necessary because using address 0x10 would be
> >>> ambiguous as there are two 0x10 chips out there.
> >>>
> >>> (*) There could be more layers of course, but still A is "closer to the
> >>> CPU than B and C", for the sake of completeness.
> >>
> >> Can the diagram and/or text be updated to elaborate this?
> >
> > Let's see whether the text below is better. I haven't changed the
> > image, I don't think we can do much more in ASCII, but maybe we can
> > replace it with an SVG [0]?
> >
> > [0]
> > https://github.com/lucaceresoli/docs/blob/master/video-serdes-linux/images/i2c-ti.svg
> >
> > A typical example follows.
> >
> > Topology::
> >
> > Slave X @ 0x10
> > .-----. |
> > .-----. | |---+---- B
> > | CPU |--A--| ATR |
> > `-----' | |---+---- C
> > `-----' |
> > Slave Y @ 0x10
>
> Slightly beside the point of this discussion, but one thing (I think) I
> tried to highlight in some older cover letter was that we don't really
> have the above structure. We have something like this (a quick edit, sorry):
>
> .------. Slave X @ 0x10
> .------. | FPDS | |
> .-----. | FPDD |-F1-`------'---+---- B
> | CPU |--A--| ATR |
> `-----' | |-F2-.------.---+---- C
> `------' | FPDS | |
> `------' Slave Y @ 0x10
>
> Where FPDD = Deserializer, FPDS = Serializer, F1/F2 = FPD-Link bus 1/2.
>
> So the ATR functionality is in the deserializer, but the actual remote
> i2c bus is on the serializer.

I'd rather say that the ATF functionality is in the sum of ser+des as
they really cooperate. But this is kind of philosophical. :) What
matters is that it's worth mentioning that the "ATR" box is actually an
abstract visualization of a feature that is provided by two or more
chips (in the known universe, at least).

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

2023-01-19 17:10:57

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

Hi Andy,

Thanks for the thorough review!

On 18/01/2023 17:48, Andy Shevchenko wrote:
> On Wed, Jan 18, 2023 at 02:40:29PM +0200, Tomi Valkeinen wrote:
>> Add driver for TI DS90UB960 FPD-Link III Deserializer.
>
> ...
>
>> +#define UB960_MIN_AEQ_STROBE_POS -7
>
> I believe it might need parentheses due to theoretical possibilities to be used
> in the expression.

Hmm, for my education, in which kind of expressions this could cause an
issue?

> ...
>
>> +#define UB960_MIN_MANUAL_STROBE_POS -(7 + 6)
>
> Ditto.
>
> ...
>
>> + ret = regmap_read(priv->regmap, reg, &v);
>> + if (ret)
>> + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n",
>> + __func__, reg, ret);
>
> Not sure how this messages are useful and esp. parameters, since regmap has
> already trace events. Maybe it's possible to narrow regmap traces to the
> dedicated functions?

I have found the error prints useful on multiple occasions. If something
goes wrong, I'd rather see it immediately. And isn't tracing and error
printing quite different things? What is your concern here, or rather,
what would you suggest instead of the above?

>> + else
>> + *val = v;
>
> ...
>
>> + 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));
>
> Any reason why not BIT() here, while above and below it's being used?

No reason.

>> + 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_csiport_select(struct ub960_data *priv, u8 nport)
>> +{
>
> Same comment as above.
>
>> +}
>
> ...
>
>> +out:
>
> out_unlock: ?

I'll change it, but often if there's only one label, I think just
err/out is fine.

>> + mutex_unlock(&priv->reg_lock);
>> +
>> + return ret;
>
> Also in some cases you are using 'else' (as 'if (ret) ... else') in some goto
> approach. Can it be unified?

Sure.

> ...
>
>> + v &= ~mask;
>> + v |= val;
>
> Usual pattern we use is
>
> v = (v & ~mask) | (val & mask);
>
> But can you use regmap_update_bits()? And why not?

I don't see why not. No idea why I open-coded it.

> ...
>
>> + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
>> + if (ret <= 0) {
>> + dev_err(dev, "tx%u: failed to parse 'data-lanes': %d\n", nport,
>> + ret);
>
> Message is not consistent with the case ret == 0.

Yep.

>> + goto err_free_txport;
>> + }
>
> ...
>
>> + if (ret != 1) {
>> + dev_err(dev,
>> + "tx%u: 'link-frequencies' must contain a single frequency: %d\n",
>> + nport, ret);
>> + goto err_free_txport;
>> + }
>
> Shouldn't be here.
> As Rob Herring told at least once that driver must not replace DT validator.

Ok. Hmm, looks like link-frequencies is not even in the binding doc.
I'll add it.

> ...
>
>> + if (priv->tx_data_rate != 1600000000 &&
>> + priv->tx_data_rate != 1200000000 &&
>> + priv->tx_data_rate != 800000000 &&
>> + priv->tx_data_rate != 400000000) {
>> + dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport);
>> + return -EINVAL;
>> + }
>
> Ditto.

Is that an important thing to restrict in the DT? I'd rather handle
these in the driver. There may well be HW revisions/versions or tricks
not documented in the public docs to use other data rates. And, while
this is not quite clear to me, the input refclk might also affect the
exact data rate.

With a quick glance, I don't see any other binding defining the data
rates. I didn't see any of them even limiting the number of items.

> ...
>
>> + dev_dbg(dev, "tx%u: nominal data rate: %u", nport, priv->tx_data_rate);
>
> All these kind of debugging are needed for production?

Production meaning upstream kernel? Hard to say. I find all kinds of
debugging prints useful.

> ...
>
>> +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);
>> +
>
> Redundant blank line.

Yep.

>> + if (!ret) {
>
> What's wrong with the positive and traditional check, i.e.
>
> if (ret)
> return;
>
> ?

Nothing, just a different style. I can change it.

>> + 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
>> + */
>
> Multi-line comment is not in the style.

True. Interesting that checkpatch didn't complain.

> ...
>
>> + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
>
> Post-increment is good enough, no? Ditto for other places.

Sure. Pre-increment is good enough too, right? It's a simpler operation,
although obviously they both compile into the same code. I usually use
pre-increment when there's no particular reason to pick either one,
although it also depends on how it looks.

> Esp. taking into account that some of them are using actually
> post-inc. Why this difference?

Possibly a different person has written that particular piece of code,
or maybe a copy paste from somewhere.

I'm personally fine with seeing both post and pre increments in code.

>> + struct ub960_rxport *rxport = priv->rxports[nport];
>> +
>> + if (!rxport || !rxport->vpoc)
>> + continue;
>> +
>> + ret = regulator_enable(rxport->vpoc);
>> + if (ret)
>> + goto err_disable_vpocs;
>> + }
>
> ...
>
>> +err_disable_vpocs:
>> + for (; nport > 0; --nport) {
>
> while (nport--) {

Yes, that's better.

>> + struct ub960_rxport *rxport = priv->rxports[nport - 1];
>> +
>> + if (!rxport || !rxport->vpoc)
>> + continue;
>> +
>> + regulator_disable(rxport->vpoc);
>> + }
>
> ...
>
>> + if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
>> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS))
>> + return;
>
> Always be careful about WARN*() APIs because with a little trick they may
> become equivalent to BUG() which is a beast that nobody likes. I.o.w.
> you have to have justify why this is needed and can't be replaced with
> dev_*() or analogue.
>
> Same for the other places with WARN*().

Valid point. I think most of them here are in cases that really
shouldn't happen. But if they do happen, I'd like to see a big loud
shout about it. The above is not a best example of this, and I think I
can just drop the above warns, but, e.g. handling the default case for
"switch (rxport->rx_mode)" (which shouldn't happen), I'd prefer to have
a big yell in place rather than return silently or print a "normal"
error print.

Obviously WARN is not a good one if it can be toggled to become a BUG.

So... I think I'll just drop most of them and probably convert the rest
(two, actually) to dev_errs.

> ...
>
>> + 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) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
>> + else if (strobe_pos > 0)
>> + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
>
> I'm wondering if clamp_t()/clamp_val() can be utilised here... And maybe in other
> places in the driver.

Hmm, I'm not sure how.

> ...
>
>> + ub960_write(priv, UB960_XR_SFILTER_CFG,
>
>> + ((u8)strobe_min << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) |
>> + ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT));
>
> Why castings are needed?

I don't think they strictly are, but it highlights that the variables,
which are signed, are now >= 0, and are used as unsigned.

> ...
>
>> + *eq_level = (v & 0x7) + ((v >> 3) & 0x7);
>
> GENMASK()?

I'll add defines for these bit fields.

>
>> + 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;
>
> A lot of magic 7 in the code. Are they all of the same semantic? Are they can
> be converted to use a macro (including respective MIN/MAX macros)?

It's related to how the value has to be encoded into the register. We
keep the equalization level in a simple variable, but need to write it
like this into the register. I'm not sure what I would call the magic 7
here.

Then for the strobe position, we use a logical signed value between -7
and 7, so we have to +7 when writing that to a register. Except when
using a manual strobe position, where the range is -13 to 13 (7+6,
that's the 6 in ub960_rxport_set_strobe_pos()).

It's rather confusing, in my opinion, but I think defines may just make
this more confusing. The magic numbers used should always be very close
to the registers in question, so if you know how the HW works wrt.
strobe & eq, they should be "clear". I'll try to come up with defines
that make this clearer, but no promises.

> ...
>
>> + WARN_ON(eq_stage_1_select_value > 7);
>> + WARN_ON(eq_stage_2_select_value > 7);
>
> Why WARN()?

I'll drop. I think these were development time helpers long time back.

> ...
>
>> + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
>> +
>> + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
>> + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK);
>> + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT;
>> + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT;
>> + v |= UB960_RR_AEQ_BYPASS_ENABLE; /* Enable AEQ Bypass */
>> +
>> + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v);
>
> Can't you provide ub960_rxport_update_bits() ?

I could, but I think it's worse:

ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
UB960_RR_AEQ_BYPASS_ENABLE,
(eq_stage_1_select_value
<< UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
(eq_stage_2_select_value
<< UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
);

Indenting it differently, I think it's still worse:

ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
UB960_RR_AEQ_BYPASS_ENABLE,
(eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
(eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
);

> ...
>
>> + 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;
>
> Can this be read at once as BE16/LE16 value?
> Or if the stream of bytes, you can use le/be16_to_cpu().

I'm not sure, possibly. But is it worth it? I'd need to add new helper
functions to read such a value.

>> + parity_errors = (v1 << 8) | v2;
>
> ...
>
>> + errors = (rx_port_sts1 & 0x2c) || (rx_port_sts2 & 0x20) ||
>> + (bcc_sts & 0x3f) || (csi_rx_sts & 0xf) || csi_err_cnt ||
>
> BIT()? GENMASK()?

I'll add defines for the error masks (those are bit masks of error bits
in the registers).

> At bare minimum this needs a good comment to explain all these magics.
>
>> + parity_errors;
>
> ...
>
>> + *ok = !errors;
>
> How this is different to the something like returning 1 here (and 0 above)?
> You may save some code by dropping redundant parameter.

Return value 1 means there was an error when reading the register
values. 0 means we read the values, and "ok" contains a summary (ok or
not) of the link's status.

>> + return 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);
>> + }
>
> You can wrap the body into readx_poll_timeout() from iopoll.h.

Hmm... How would I do that? With some kind of helper structs to wrap the
input and output parameters? Sounds very messy, but maybe I'm missing
something.

> ...
>
>> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
>> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);
>
> Same Q, can these be unified to some kind of bulk read?

Perhaps, but again, I don't see the value for creating a bulk read
helper function for these few cases.

> ...
>
>> + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
>> + nport, strobe_pos, eq_level,
>> + v1 * 1000000 + v2 * 1000000 / 256);
>
> Even this will be simpler with above suggestion.

Hmm... How is that?

> ...
>
>> +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) {
>
> Post-inc?

I still like pre-inc =).

I see there's a mix os post and pre incs in the code. I'll align those
when I encounter them, but I don't think it's worth the effort to
methodically go through all of them to change them use the same style.

>> + 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) {
>
> while(nport--)
>
> (and drop those -1:s below)

Yes, that cleans this up nicely.

>> + 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;
>> +}
>
> ...
>
>> + if (priv->tx_data_rate == 1600000000)
>
> Easy to make a mistake, perhaps something from units.h / time.h can be used?

Hmm, time.h? What's in there.

I don't like the HZ_PER_MHZ too much. But I'll add this, which I
recently added to a Renesas driver:

#define MHZ(v) ((u32)((v) * 1000000U))

I like it, as e.g. MHZ(1.6) works and (I think) is quite clear. Here
it's MHZ(1600), of course.

>> + csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN;
>
> ...
>
>> + 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;
>> + }
>
> Ditto, but maybe defines for them?
>
> ...
>
>> + switch (rxport->rx_mode) {
>> + default:
>> + WARN_ON(true);
>> + fallthrough;
>
> Maybe you can drop default completely and rely on compiler to produce a
> warning?

I've dropped the default, but I had to add cases for the RAW12 modes
(which are not supported at the moment).

>> + 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;
>> + }
>
> ...
>
>> + u8 cur_vc[UB960_MAX_TX_NPORTS] = { 0 };
>
> 0 is not needed.

Yep.

> ...
>
>> + 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);
>
>> +
>
> Redundant blank line.

Yep.

>> + if (BIT(rx) & handled_mask)
>> + continue;
>> +
>> + tx = ub960_pad_to_port(priv, route->source_pad);
>> +
>> + vc[rx] = cur_vc[tx]++;
>> + handled_mask |= BIT(rx);
>> + }
>
> ...
>
>> + if (rx_data[nport].tx_port == 1)
>> + fwd_ctl |= BIT(nport); /* forward to TX1 */
>> + else
>> + fwd_ctl &= ~BIT(nport); /* forward to TX0 */
>
> This and many other similar places can be replaced by __assign_bit()
> if the lvalue is type of unsigned long or can be made that type.

It is u8, as it is written to a 8 bit register. It could be made
unsigned long, but... I'd rather use u8 when we are dealing with 8 bits
of data.

> ...
>
>> +static void ub960_update_streaming_status(struct ub960_data *priv)
>> +{
>> + unsigned int i;
>
>> + bool streaming = false;
>
> Redundant
> >> + for (i = 0; i < UB960_MAX_NPORTS; ++i) {
>> + if (priv->stream_enable_mask[i]) {
>> + streaming = true;
>> + break;
>> + }
>> + }
>
>> + priv->streaming = streaming;
>
> priv->streaming = i < UB960_MAX_NPORTS;

Yes, that's a bit simpler.

>> +}
>
> ...
>
>> + for (; nport > 0; --nport) {
>
> while (nport--)

Yep.

>> + 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]);
>> + }
>
> ...
>
>> + char id[7];
>
> u8?
>
>> + for (unsigned int i = 0; i < 6; ++i)
>> + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
>> + id[6] = 0;
>
> If it's only for printing, the 0 is not needed...
>
>> + dev_info(dev, "ID '%s'\n", id);
>
> ...as you may put it as
>
> dev_info(dev, "ID: '%.*s'\n", (int)sizeof(id), id);
>
> (I wrote from the top of my head, maybe not compilable as is).

And you think that is clearer? =) I have to disagree.

> ...
>
>> +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);
>
>> +
>
> Redundant blank line. I guess you may decrease your code by ~2.5% by removing
> such unneeded blank lines here and there.

I only found a few of these, which I have fixed.

>> + if (!ret && int_sts) {
>
> if (ret)
> return ...
>
> if (!int_sts)
> return IRQ_NONE; // No?

Yes, I think that's correct. I wonder what to return in the (ret) case.
Probably also IRQ_NONE, as we don't know if the interrupt was for us.

>> + 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 %#02x\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;
>> +}
>
> ...
>
>> + if (cdr_mode > RXPORT_CDR_LAST) {
>> + dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, cdr_mode);
>> + return -EINVAL;
>> + }
>
> No DT validation if it's not used in (memory) allocation.

I'm not quite fine with dropping all these DT checks. If the user
happens to provide a DT with illegal values, the end results can be odd
and the reason quite difficult to figure out. Isn't it much better to
have a few extra checks in the driver?

> ...
>
>> + ret = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos",
>> + &strobe_pos);
>> + if (ret) {
>> + if (ret != -EINVAL) {
>> + dev_err(dev,
>> + "rx%u: failed to read 'ti,strobe-pos': %d\n",
>> + nport, ret);
>> + return ret;
>> + }
>> + } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
>> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) {
>> + dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", nport,
>> + strobe_pos);
>> + } else {
>> + // NOTE: ignored unless global manual strobe pos is set
>
> Style?

Oops...

>> + rxport->eq.strobe_pos = strobe_pos;
>> + if (!priv->strobe.manual)
>> + dev_warn(dev,
>> + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
>> + nport);
>> + }
>
> This and below looks a bit different to the above in the same function. Perhaps
> these can be refactored to be less LoCs.

Hmm what did you have in mind?

>> + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
>> + if (ret) {
>> + if (ret != -EINVAL) {
>> + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
>> + nport, ret);
>> + return ret;
>> + }
>> + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
>> + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
>> + eq_level);
>> + } else {
>> + rxport->eq.manual_eq = true;
>> + rxport->eq.manual.eq_level = eq_level;
>> + }
>> +
>> + ret = fwnode_property_read_u32(link_fwnode, "i2c-alias",
>> + &ser_i2c_alias);
>> + if (ret) {
>> + dev_err(dev, "rx%u: failed to read 'i2c-alias': %d\n", nport,
>> + ret);
>> + return ret;
>> + }
>
> ...
>
>> +static struct fwnode_handle *
>> +ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode,
>> + unsigned int nport)
>> +{
>> + struct fwnode_handle *link_fwnode;
>> + int ret;
>> +
>> + fwnode_for_each_child_node(links_fwnode, link_fwnode) {
>> + u32 link_num;
>> +
>> + if (strncmp(fwnode_get_name(link_fwnode), "link@", 5) != 0)
>> + continue;
>
> str_has_prefix()

I knew there must be a function for this, but didn't find it =).

>> +
>> + ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num);
>> + if (ret)
>
> Refcount imbalance.

Yep.

>> + return NULL;
>> +
>> + if (nport == link_num) {
>> + fwnode_handle_put(link_fwnode);
>> + return link_fwnode;
>> + }
>> + }
>> +
>> + return NULL;
>> +}
>
> ...
>
>> + asd = v4l2_async_nf_add_fwnode(&priv->notifier,
>> + rxport->source_ep_fwnode,
>> + struct ub960_asd);
>> + if (IS_ERR(asd)) {
>> + dev_err(dev, "Failed to add subdev for source %u: %ld",
>
> %pe ?

Yep.

>> + i, PTR_ERR(asd));
>> + v4l2_async_nf_cleanup(&priv->notifier);
>> + return PTR_ERR(asd);
>> + }
>
> ...
>
>> +err_pd_gpio:
>> + if (priv->pd_gpio)
>
> Dup test.

What do you mean dup? You mean gpiod_set_value_cansleep can be called
with gpio = NULL? The docs don't say this, but I guess that is the case.

>> + gpiod_set_value_cansleep(priv->pd_gpio, 1);
>
> ...
>
>> + if (priv->pd_gpio)
>> + gpiod_set_value_cansleep(priv->pd_gpio, 1);
>
> Ditto.
>
> ...
>
>> + priv->hw_data = of_device_get_match_data(dev);
>
> Why of_ out of the blue?!

Hmm... How do I get the data in a generic way? I'll have to study this a
bit.

>> + if (!priv->hw_data)
>> + return -ENODEV;
>
> ...
>
>> + 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;
>
> GENMASK()

These are not masks, but invalid values. We set these to an invalid
value (0xff) so that when a reg access function next time checks if we
are already targeting, e.g. a particular rxport, it will always opt to
select the rxport by writing to the approriate registers.

> ...
>
>> + ub960_rxport_wait_locks(priv, 0xf, NULL);
>
> Ditto?

Yep.

> ...
>
>> +static const struct i2c_device_id ub960_id[] = {
>> + { "ds90ub960-q1", 0 },
>> + { "ds90ub9702-q1", 0 },
>
> Why driver data is different to OF case?

I have just never used these so I've missed it. As I said, I need to
look at this and figure out how to get the data correctly in all the cases.

>> + {}
>> +};
>> +MODULE_DEVICE_TABLE(i2c, ub960_id);
>> +
>> +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);
>
>> +static struct i2c_driver ds90ub960_driver = {
>> + .probe_new = ub960_probe,
>> + .remove = ub960_remove,
>> + .id_table = ub960_id,
>> + .driver = {
>> + .name = "ds90ub960",
>
>> + .owner = THIS_MODULE,
>
> Set by macro from the beginning of its, macro, existence.

Ok.

>> + .of_match_table = ub960_dt_ids,
>> + },
>> +};
>
> ...
>
>> +struct ds90ub9xx_platform_data {
>> + u32 port;
>> + struct i2c_atr *atr;
>> + unsigned long bc_rate;
>
> Not sure why we need this to be public except, probably, atr...

The port and atr are used by the serializers, for atr. The bc_rate is
used by the serializers to figure out the clocking (they may use the
FPD-Link's frequency internally).

Tomi

2023-01-19 23:46:25

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Thu, Jan 19, 2023 at 06:27:31PM +0200, Tomi Valkeinen wrote:
> Hi Andy,
>
> Thanks for the thorough review!
>
> On 18/01/2023 17:48, Andy Shevchenko wrote:
> > On Wed, Jan 18, 2023 at 02:40:29PM +0200, Tomi Valkeinen wrote:
> >> Add driver for TI DS90UB960 FPD-Link III Deserializer.
> >
> > ...
> >
> >> +#define UB960_MIN_AEQ_STROBE_POS -7
> >
> > I believe it might need parentheses due to theoretical possibilities to be used
> > in the expression.
>
> Hmm, for my education, in which kind of expressions this could cause an
> issue?
>
> > ...
> >
> >> +#define UB960_MIN_MANUAL_STROBE_POS -(7 + 6)
> >
> > Ditto.
> >
> > ...
> >
> >> + ret = regmap_read(priv->regmap, reg, &v);
> >> + if (ret)
> >> + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n",
> >> + __func__, reg, ret);
> >
> > Not sure how this messages are useful and esp. parameters, since regmap has
> > already trace events. Maybe it's possible to narrow regmap traces to the
> > dedicated functions?
>
> I have found the error prints useful on multiple occasions. If something
> goes wrong, I'd rather see it immediately. And isn't tracing and error
> printing quite different things? What is your concern here, or rather,
> what would you suggest instead of the above?

I agree, if I2C transfers fail, we want to see errors immediately. If
that can be handled by regmap, that's great, but if the user has to
enable tracing manually, that's not a good option.

> >> + else
> >> + *val = v;
> >
> > ...
> >
> >> + 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));
> >
> > Any reason why not BIT() here, while above and below it's being used?
>
> No reason.
>
> >> + 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_csiport_select(struct ub960_data *priv, u8 nport)
> >> +{
> >
> > Same comment as above.
> >
> >> +}
> >
> > ...
> >
> >> +out:
> >
> > out_unlock: ?
>
> I'll change it, but often if there's only one label, I think just
> err/out is fine.
>
> >> + mutex_unlock(&priv->reg_lock);
> >> +
> >> + return ret;
> >
> > Also in some cases you are using 'else' (as 'if (ret) ... else') in some goto
> > approach. Can it be unified?
>
> Sure.
>
> > ...
> >
> >> + v &= ~mask;
> >> + v |= val;
> >
> > Usual pattern we use is
> >
> > v = (v & ~mask) | (val & mask);
> >
> > But can you use regmap_update_bits()? And why not?
>
> I don't see why not. No idea why I open-coded it.
>
> > ...
> >
> >> + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
> >> + if (ret <= 0) {
> >> + dev_err(dev, "tx%u: failed to parse 'data-lanes': %d\n", nport,
> >> + ret);
> >
> > Message is not consistent with the case ret == 0.
>
> Yep.
>
> >> + goto err_free_txport;
> >> + }
> >
> > ...
> >
> >> + if (ret != 1) {
> >> + dev_err(dev,
> >> + "tx%u: 'link-frequencies' must contain a single frequency: %d\n",
> >> + nport, ret);
> >> + goto err_free_txport;
> >> + }
> >
> > Shouldn't be here.
> > As Rob Herring told at least once that driver must not replace DT validator.
>
> Ok. Hmm, looks like link-frequencies is not even in the binding doc.
> I'll add it.
>
> > ...
> >
> >> + if (priv->tx_data_rate != 1600000000 &&
> >> + priv->tx_data_rate != 1200000000 &&
> >> + priv->tx_data_rate != 800000000 &&
> >> + priv->tx_data_rate != 400000000) {
> >> + dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport);
> >> + return -EINVAL;
> >> + }
> >
> > Ditto.
>
> Is that an important thing to restrict in the DT? I'd rather handle
> these in the driver. There may well be HW revisions/versions or tricks
> not documented in the public docs to use other data rates. And, while
> this is not quite clear to me, the input refclk might also affect the
> exact data rate.
>
> With a quick glance, I don't see any other binding defining the data
> rates. I didn't see any of them even limiting the number of items.

I don't think link frequencies should be restricted in DT, no. The idea
is that a system integrator should pick link frequencies that match
their requirements, both in terms of bandwidth and in terms of EMC. As
most devices have PLLs, the link frequencies can be chosen more or less
freely within a range (or multiple ranges). Many drivers restrict that
to a set of fixed values, but that's a driver limitation, it doesn't
belong to DT bindings.

> > ...
> >
> >> + dev_dbg(dev, "tx%u: nominal data rate: %u", nport, priv->tx_data_rate);
> >
> > All these kind of debugging are needed for production?
>
> Production meaning upstream kernel? Hard to say. I find all kinds of
> debugging prints useful.
>
> > ...
> >
> >> +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);
> >> +
> >
> > Redundant blank line.
>
> Yep.
>
> >> + if (!ret) {
> >
> > What's wrong with the positive and traditional check, i.e.
> >
> > if (ret)
> > return;
> >
> > ?
>
> Nothing, just a different style. I can change it.
>
> >> + 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
> >> + */
> >
> > Multi-line comment is not in the style.
>
> True. Interesting that checkpatch didn't complain.

This kind of section header is widely used in V4L2, if checkpatch
doesn't complain, let's keep it.

> > ...
> >
> >> + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
> >
> > Post-increment is good enough, no? Ditto for other places.
>
> Sure. Pre-increment is good enough too, right? It's a simpler operation,
> although obviously they both compile into the same code. I usually use
> pre-increment when there's no particular reason to pick either one,
> although it also depends on how it looks.
>
> > Esp. taking into account that some of them are using actually
> > post-inc. Why this difference?
>
> Possibly a different person has written that particular piece of code,
> or maybe a copy paste from somewhere.
>
> I'm personally fine with seeing both post and pre increments in code.

They can make a difference in C++ (although compilers should in many
cases optimize that out), which I think is why pre-increment is often
used for loop iterators, and that habbit has just expanded to C code. I
don't mind either way in C.

> >> + struct ub960_rxport *rxport = priv->rxports[nport];
> >> +
> >> + if (!rxport || !rxport->vpoc)
> >> + continue;
> >> +
> >> + ret = regulator_enable(rxport->vpoc);
> >> + if (ret)
> >> + goto err_disable_vpocs;
> >> + }
> >
> > ...
> >
> >> +err_disable_vpocs:
> >> + for (; nport > 0; --nport) {
> >
> > while (nport--) {
>
> Yes, that's better.

It's not equivalent though :-) You'll need to replace nport - 1 with
nport on the next line.

> >> + struct ub960_rxport *rxport = priv->rxports[nport - 1];
> >> +
> >> + if (!rxport || !rxport->vpoc)
> >> + continue;
> >> +
> >> + regulator_disable(rxport->vpoc);
> >> + }
> >
> > ...
> >
> >> + if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
> >> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS))
> >> + return;
> >
> > Always be careful about WARN*() APIs because with a little trick they may
> > become equivalent to BUG() which is a beast that nobody likes. I.o.w.
> > you have to have justify why this is needed and can't be replaced with
> > dev_*() or analogue.
> >
> > Same for the other places with WARN*().
>
> Valid point. I think most of them here are in cases that really
> shouldn't happen. But if they do happen, I'd like to see a big loud
> shout about it. The above is not a best example of this, and I think I
> can just drop the above warns, but, e.g. handling the default case for
> "switch (rxport->rx_mode)" (which shouldn't happen), I'd prefer to have
> a big yell in place rather than return silently or print a "normal"
> error print.
>
> Obviously WARN is not a good one if it can be toggled to become a BUG.
>
> So... I think I'll just drop most of them and probably convert the rest
> (two, actually) to dev_errs.
>
> > ...
> >
> >> + 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) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
> >> + else if (strobe_pos > 0)
> >> + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
> >
> > I'm wondering if clamp_t()/clamp_val() can be utilised here... And maybe in other
> > places in the driver.
>
> Hmm, I'm not sure how.
>
> > ...
> >
> >> + ub960_write(priv, UB960_XR_SFILTER_CFG,
> >
> >> + ((u8)strobe_min << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) |
> >> + ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT));
> >
> > Why castings are needed?
>
> I don't think they strictly are, but it highlights that the variables,
> which are signed, are now >= 0, and are used as unsigned.
>
> > ...
> >
> >> + *eq_level = (v & 0x7) + ((v >> 3) & 0x7);
> >
> > GENMASK()?
>
> I'll add defines for these bit fields.
>
> >> + 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;
> >
> > A lot of magic 7 in the code. Are they all of the same semantic? Are they can
> > be converted to use a macro (including respective MIN/MAX macros)?
>
> It's related to how the value has to be encoded into the register. We
> keep the equalization level in a simple variable, but need to write it
> like this into the register. I'm not sure what I would call the magic 7
> here.
>
> Then for the strobe position, we use a logical signed value between -7
> and 7, so we have to +7 when writing that to a register. Except when
> using a manual strobe position, where the range is -13 to 13 (7+6,
> that's the 6 in ub960_rxport_set_strobe_pos()).
>
> It's rather confusing, in my opinion, but I think defines may just make
> this more confusing. The magic numbers used should always be very close
> to the registers in question, so if you know how the HW works wrt.
> strobe & eq, they should be "clear". I'll try to come up with defines
> that make this clearer, but no promises.
>
> > ...
> >
> >> + WARN_ON(eq_stage_1_select_value > 7);
> >> + WARN_ON(eq_stage_2_select_value > 7);
> >
> > Why WARN()?
>
> I'll drop. I think these were development time helpers long time back.
>
> > ...
> >
> >> + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
> >> +
> >> + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> >> + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK);
> >> + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT;
> >> + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT;
> >> + v |= UB960_RR_AEQ_BYPASS_ENABLE; /* Enable AEQ Bypass */
> >> +
> >> + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v);
> >
> > Can't you provide ub960_rxport_update_bits() ?
>
> I could, but I think it's worse:
>
> ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
> UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_ENABLE,
> (eq_stage_1_select_value
> << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
> (eq_stage_2_select_value
> << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
> UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
> );
>
> Indenting it differently, I think it's still worse:
>
> ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
> UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_ENABLE,
> (eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
> (eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
> UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
> );

/me fetches the sickness bag :-)

> > ...
> >
> >> + 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;
> >
> > Can this be read at once as BE16/LE16 value?
> > Or if the stream of bytes, you can use le/be16_to_cpu().
>
> I'm not sure, possibly. But is it worth it? I'd need to add new helper
> functions to read such a value.

It would be nice if regmap provided a way to encode the register size in
the address. Look at the drivers/media/i2c/st-vgxy61.c driver for
instance, which has

#define VGXY61_REG_8BIT(n) ((1 << 16) | (n))
#define VGXY61_REG_16BIT(n) ((2 << 16) | (n))
#define VGXY61_REG_32BIT(n) ((4 << 16) | (n))
#define VGXY61_REG_SIZE_SHIFT 16
#define VGXY61_REG_ADDR_MASK 0xffff

#define VGXY61_REG_MODEL_ID VGXY61_REG_16BIT(0x0000)
...

id = vgxy61_read_reg(sensor, VGXY61_REG_MODEL_ID);


The vgxy61_read_reg() and vgxy61_write_reg() functions then extract the
size from the register address argument, and issue multi-byte I2C
reads/writes as necessary. It makes it really nice for drivers, as you
don't have to deal with multi-byte access manually anymore. Other
drivers, like imx219.c for instance, pass the size as an explicit
argument to the read/write functions, but that's error-prone.

> >> + parity_errors = (v1 << 8) | v2;
> >
> > ...
> >
> >> + errors = (rx_port_sts1 & 0x2c) || (rx_port_sts2 & 0x20) ||
> >> + (bcc_sts & 0x3f) || (csi_rx_sts & 0xf) || csi_err_cnt ||
> >
> > BIT()? GENMASK()?
>
> I'll add defines for the error masks (those are bit masks of error bits
> in the registers).
>
> > At bare minimum this needs a good comment to explain all these magics.
> >
> >> + parity_errors;
> >
> > ...
> >
> >> + *ok = !errors;
> >
> > How this is different to the something like returning 1 here (and 0 above)?
> > You may save some code by dropping redundant parameter.
>
> Return value 1 means there was an error when reading the register
> values. 0 means we read the values, and "ok" contains a summary (ok or
> not) of the link's status.
>
> >> + return 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);
> >> + }
> >
> > You can wrap the body into readx_poll_timeout() from iopoll.h.
>
> Hmm... How would I do that? With some kind of helper structs to wrap the
> input and output parameters? Sounds very messy, but maybe I'm missing
> something.
>
> > ...
> >
> >> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
> >> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);
> >
> > Same Q, can these be unified to some kind of bulk read?
>
> Perhaps, but again, I don't see the value for creating a bulk read
> helper function for these few cases.
>
> > ...
> >
> >> + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
> >> + nport, strobe_pos, eq_level,
> >> + v1 * 1000000 + v2 * 1000000 / 256);
> >
> > Even this will be simpler with above suggestion.
>
> Hmm... How is that?
>
> > ...
> >
> >> +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) {
> >
> > Post-inc?
>
> I still like pre-inc =).
>
> I see there's a mix os post and pre incs in the code. I'll align those
> when I encounter them, but I don't think it's worth the effort to
> methodically go through all of them to change them use the same style.
>
> >> + 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) {
> >
> > while(nport--)
> >
> > (and drop those -1:s below)
>
> Yes, that cleans this up nicely.
>
> >> + 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;
> >> +}
> >
> > ...
> >
> >> + if (priv->tx_data_rate == 1600000000)
> >
> > Easy to make a mistake, perhaps something from units.h / time.h can be used?
>
> Hmm, time.h? What's in there.
>
> I don't like the HZ_PER_MHZ too much. But I'll add this, which I
> recently added to a Renesas driver:
>
> #define MHZ(v) ((u32)((v) * 1000000U))
>
> I like it, as e.g. MHZ(1.6) works and (I think) is quite clear. Here
> it's MHZ(1600), of course.

C++ user-defined literals anyone, to write 1600_mHz and get the compiler
to do the magic ? ;-)

> >> + csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN;
> >
> > ...
> >
> >> + 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;
> >> + }
> >
> > Ditto, but maybe defines for them?
> >
> > ...
> >
> >> + switch (rxport->rx_mode) {
> >> + default:
> >> + WARN_ON(true);
> >> + fallthrough;
> >
> > Maybe you can drop default completely and rely on compiler to produce a
> > warning?
>
> I've dropped the default, but I had to add cases for the RAW12 modes
> (which are not supported at the moment).
>
> >> + 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;
> >> + }
> >
> > ...
> >
> >> + u8 cur_vc[UB960_MAX_TX_NPORTS] = { 0 };
> >
> > 0 is not needed.
>
> Yep.
>
> > ...
> >
> >> + 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);
> >
> >> +
> >
> > Redundant blank line.
>
> Yep.
>
> >> + if (BIT(rx) & handled_mask)
> >> + continue;
> >> +
> >> + tx = ub960_pad_to_port(priv, route->source_pad);
> >> +
> >> + vc[rx] = cur_vc[tx]++;
> >> + handled_mask |= BIT(rx);
> >> + }
> >
> > ...
> >
> >> + if (rx_data[nport].tx_port == 1)
> >> + fwd_ctl |= BIT(nport); /* forward to TX1 */
> >> + else
> >> + fwd_ctl &= ~BIT(nport); /* forward to TX0 */
> >
> > This and many other similar places can be replaced by __assign_bit()
> > if the lvalue is type of unsigned long or can be made that type.
>
> It is u8, as it is written to a 8 bit register. It could be made
> unsigned long, but... I'd rather use u8 when we are dealing with 8 bits
> of data.
>
> > ...
> >
> >> +static void ub960_update_streaming_status(struct ub960_data *priv)
> >> +{
> >> + unsigned int i;
> >
> >> + bool streaming = false;
> >
> > Redundant
> > >> + for (i = 0; i < UB960_MAX_NPORTS; ++i) {
> >> + if (priv->stream_enable_mask[i]) {
> >> + streaming = true;
> >> + break;
> >> + }
> >> + }
> >
> >> + priv->streaming = streaming;
> >
> > priv->streaming = i < UB960_MAX_NPORTS;
>
> Yes, that's a bit simpler.
>
> >> +}
> >
> > ...
> >
> >> + for (; nport > 0; --nport) {
> >
> > while (nport--)
>
> Yep.
>
> >> + 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]);
> >> + }
> >
> > ...
> >
> >> + char id[7];
> >
> > u8?
> >
> >> + for (unsigned int i = 0; i < 6; ++i)
> >> + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
> >> + id[6] = 0;
> >
> > If it's only for printing, the 0 is not needed...
> >
> >> + dev_info(dev, "ID '%s'\n", id);
> >
> > ...as you may put it as
> >
> > dev_info(dev, "ID: '%.*s'\n", (int)sizeof(id), id);
> >
> > (I wrote from the top of my head, maybe not compilable as is).
>
> And you think that is clearer? =) I have to disagree.
>
> > ...
> >
> >> +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);
> >
> >> +
> >
> > Redundant blank line. I guess you may decrease your code by ~2.5% by removing
> > such unneeded blank lines here and there.
>
> I only found a few of these, which I have fixed.
>
> >> + if (!ret && int_sts) {
> >
> > if (ret)
> > return ...
> >
> > if (!int_sts)
> > return IRQ_NONE; // No?
>
> Yes, I think that's correct. I wonder what to return in the (ret) case.
> Probably also IRQ_NONE, as we don't know if the interrupt was for us.
>
> >> + 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 %#02x\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;
> >> +}
> >
> > ...
> >
> >> + if (cdr_mode > RXPORT_CDR_LAST) {
> >> + dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, cdr_mode);
> >> + return -EINVAL;
> >> + }
> >
> > No DT validation if it's not used in (memory) allocation.
>
> I'm not quite fine with dropping all these DT checks. If the user
> happens to provide a DT with illegal values, the end results can be odd
> and the reason quite difficult to figure out. Isn't it much better to
> have a few extra checks in the driver?
>
> > ...
> >
> >> + ret = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos",
> >> + &strobe_pos);
> >> + if (ret) {
> >> + if (ret != -EINVAL) {
> >> + dev_err(dev,
> >> + "rx%u: failed to read 'ti,strobe-pos': %d\n",
> >> + nport, ret);
> >> + return ret;
> >> + }
> >> + } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
> >> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) {
> >> + dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", nport,
> >> + strobe_pos);
> >> + } else {
> >> + // NOTE: ignored unless global manual strobe pos is set
> >
> > Style?
>
> Oops...
>
> >> + rxport->eq.strobe_pos = strobe_pos;
> >> + if (!priv->strobe.manual)
> >> + dev_warn(dev,
> >> + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
> >> + nport);
> >> + }
> >
> > This and below looks a bit different to the above in the same function. Perhaps
> > these can be refactored to be less LoCs.
>
> Hmm what did you have in mind?
>
> >> + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
> >> + if (ret) {
> >> + if (ret != -EINVAL) {
> >> + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
> >> + nport, ret);
> >> + return ret;
> >> + }
> >> + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
> >> + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
> >> + eq_level);
> >> + } else {
> >> + rxport->eq.manual_eq = true;
> >> + rxport->eq.manual.eq_level = eq_level;
> >> + }
> >> +
> >> + ret = fwnode_property_read_u32(link_fwnode, "i2c-alias",
> >> + &ser_i2c_alias);
> >> + if (ret) {
> >> + dev_err(dev, "rx%u: failed to read 'i2c-alias': %d\n", nport,
> >> + ret);
> >> + return ret;
> >> + }
> >
> > ...
> >
> >> +static struct fwnode_handle *
> >> +ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode,
> >> + unsigned int nport)
> >> +{
> >> + struct fwnode_handle *link_fwnode;
> >> + int ret;
> >> +
> >> + fwnode_for_each_child_node(links_fwnode, link_fwnode) {
> >> + u32 link_num;
> >> +
> >> + if (strncmp(fwnode_get_name(link_fwnode), "link@", 5) != 0)
> >> + continue;
> >
> > str_has_prefix()
>
> I knew there must be a function for this, but didn't find it =).
>
> >> +
> >> + ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num);
> >> + if (ret)
> >
> > Refcount imbalance.
>
> Yep.
>
> >> + return NULL;
> >> +
> >> + if (nport == link_num) {
> >> + fwnode_handle_put(link_fwnode);
> >> + return link_fwnode;
> >> + }
> >> + }
> >> +
> >> + return NULL;
> >> +}
> >
> > ...
> >
> >> + asd = v4l2_async_nf_add_fwnode(&priv->notifier,
> >> + rxport->source_ep_fwnode,
> >> + struct ub960_asd);
> >> + if (IS_ERR(asd)) {
> >> + dev_err(dev, "Failed to add subdev for source %u: %ld",
> >
> > %pe ?
>
> Yep.
>
> >> + i, PTR_ERR(asd));
> >> + v4l2_async_nf_cleanup(&priv->notifier);
> >> + return PTR_ERR(asd);
> >> + }
> >
> > ...
> >
> >> +err_pd_gpio:
> >> + if (priv->pd_gpio)
> >
> > Dup test.
>
> What do you mean dup? You mean gpiod_set_value_cansleep can be called
> with gpio = NULL? The docs don't say this, but I guess that is the case.
>
> >> + gpiod_set_value_cansleep(priv->pd_gpio, 1);
> >
> > ...
> >
> >> + if (priv->pd_gpio)
> >> + gpiod_set_value_cansleep(priv->pd_gpio, 1);
> >
> > Ditto.
> >
> > ...
> >
> >> + priv->hw_data = of_device_get_match_data(dev);
> >
> > Why of_ out of the blue?!
>
> Hmm... How do I get the data in a generic way? I'll have to study this a
> bit.

Possibly device_get_match_data(dev).

While I like genericity, I'd also question why we should introduce the
overhead that goes with it for drivers that will most likely be used on
DT-based systems only.

> >> + if (!priv->hw_data)
> >> + return -ENODEV;
> >
> > ...
> >
> >> + 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;
> >
> > GENMASK()
>
> These are not masks, but invalid values. We set these to an invalid
> value (0xff) so that when a reg access function next time checks if we
> are already targeting, e.g. a particular rxport, it will always opt to
> select the rxport by writing to the approriate registers.
>
> > ...
> >
> >> + ub960_rxport_wait_locks(priv, 0xf, NULL);
> >
> > Ditto?
>
> Yep.
>
> > ...
> >
> >> +static const struct i2c_device_id ub960_id[] = {
> >> + { "ds90ub960-q1", 0 },
> >> + { "ds90ub9702-q1", 0 },
> >
> > Why driver data is different to OF case?
>
> I have just never used these so I've missed it. As I said, I need to
> look at this and figure out how to get the data correctly in all the cases.

device_get_match_data() will call of_device_get_match_data() on DT-based
systems, so there's no need to specify anything in the i2c_device_id
entries.

> >> + {}
> >> +};
> >> +MODULE_DEVICE_TABLE(i2c, ub960_id);
> >> +
> >> +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);
> >
> >> +static struct i2c_driver ds90ub960_driver = {
> >> + .probe_new = ub960_probe,
> >> + .remove = ub960_remove,
> >> + .id_table = ub960_id,
> >> + .driver = {
> >> + .name = "ds90ub960",
> >
> >> + .owner = THIS_MODULE,
> >
> > Set by macro from the beginning of its, macro, existence.
>
> Ok.
>
> >> + .of_match_table = ub960_dt_ids,
> >> + },
> >> +};
> >
> > ...
> >
> >> +struct ds90ub9xx_platform_data {
> >> + u32 port;
> >> + struct i2c_atr *atr;
> >> + unsigned long bc_rate;
> >
> > Not sure why we need this to be public except, probably, atr...
>
> The port and atr are used by the serializers, for atr. The bc_rate is
> used by the serializers to figure out the clocking (they may use the
> FPD-Link's frequency internally).

--
Regards,

Laurent Pinchart

2023-01-20 00:49:25

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 7/7] media: i2c: add DS90UB953 driver

Hi Tomi,

Thank you for the patch.

On Wed, Jan 18, 2023 at 02:40:31PM +0200, Tomi Valkeinen wrote:
> Add driver for TI DS90UB953 FPD-Link III Serializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> drivers/media/i2c/Kconfig | 13 +
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/ds90ub953.c | 1576 +++++++++++++++++++++++++++++++++
> 3 files changed, 1590 insertions(+)
> create mode 100644 drivers/media/i2c/ds90ub953.c
>
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index e0a1c2a5f3bf..0590312ec751 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1614,6 +1614,19 @@ config VIDEO_DS90UB913
> Device driver for the Texas Instruments DS90UB913
> FPD-Link III Serializer.
>
> +config VIDEO_DS90UB953
> + tristate "TI FPD-Link III/IV CSI-2 Serializers"
> + depends on OF && I2C && VIDEO_DEV
> + select I2C_ATR
> + select MEDIA_CONTROLLER
> + select OF_GPIO
> + select REGMAP_I2C
> + select V4L2_FWNODE
> + select VIDEO_V4L2_SUBDEV_API
> + help
> + Device driver for the Texas Instruments DS90UB953
> + FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer.
> +
> config VIDEO_DS90UB960
> tristate "TI FPD-Link III/IV Deserializers"
> depends on OF && I2C && VIDEO_DEV
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index c4875ec8c3b9..efd5f717a5f7 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -143,4 +143,5 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
> obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
> obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
> obj-$(CONFIG_VIDEO_DS90UB913) += ds90ub913.o
> +obj-$(CONFIG_VIDEO_DS90UB953) += ds90ub953.o
> obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o
> diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c
> new file mode 100644
> index 000000000000..ec33e16da3d1
> --- /dev/null
> +++ b/drivers/media/i2c/ds90ub953.c
> @@ -0,0 +1,1576 @@
> +// 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) 2023 Tomi Valkeinen <[email protected]>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/fwnode.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_device.h>
> +#include <linux/property.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_GENERAL_CFG_CONT_CLK BIT(6)
> +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT 4
> +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_MASK GENMASK(5, 4)
> +#define UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE BIT(1)
> +#define UB953_REG_GENERAL_CFG_I2C_STRAP_MODE BIT(0)
> +
> +#define UB953_REG_MODE_SEL 0x03
> +#define UB953_REG_MODE_SEL_MODE_DONE BIT(3)
> +#define UB953_REG_MODE_SEL_MODE_OVERRIDE BIT(4)
> +#define UB953_REG_MODE_SEL_MODE_MASK GENMASK(2, 0)
> +
> +#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

You have a mix of uppercase and lowercase hex constants, please
standardize on one of them. In the media subsystem we usually go for
lowercase.

> +
> +#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 III CSI-2 synchronous mode */
> + UB953_MODE_SYNC,
> + /* FPD-Link III CSI-2 non-synchronous mode, external ref clock */
> + UB953_MODE_NONSYNC_EXT,
> + /* FPD-Link III CSI-2 non-synchronous mode, internal ref clock */
> + UB953_MODE_NONSYNC_INT,
> + /* FPD-Link III DVP mode */
> + UB953_MODE_DVP,
> +};
> +
> +struct ub953_hw_data {
> + const char *model;
> + bool is_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;
> +
> + struct v4l2_subdev sd;
> + struct media_pad pads[2];
> +
> + struct v4l2_async_notifier notifier;
> +
> + struct v4l2_subdev *source_sd;
> + u16 source_sd_pad;
> +
> + struct v4l2_ctrl_handler ctrl_handler;
> +
> + u64 enabled_source_streams;
> +
> + /* 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;

const.

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

Is the leading _ needed ?

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

I'd still prefer dropping this function, but I won't insist.

> +{
> + 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) {
> + dev_err(&priv->client->dev,
> + "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
> + block, reg, ret);
> + goto out;
> + }

Would it make sense to cache the address as you do with
current_indirect_block, and skip this write if the address is correct
already ? If the device implements auto-increment of the address (I
haven't checked), this could save quite a few I2C writes.

> +
> + ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v);
> + if (ret) {
> + dev_err(&priv->client->dev,
> + "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
> + block, reg, 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) {
> + dev_err(&priv->client->dev,
> + "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n",
> + block, reg, ret);
> + goto out;
> + }
> +
> + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val);
> + if (ret) {
> + dev_err(&priv->client->dev,
> + "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n",
> + block, reg, ret);
> + }
> +
> +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;

Does the device auto-increment the address ? If so you could skip this.

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

Weird indentation.

> + 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);
> +
> + gc->label = dev_name(dev);
> + gc->parent = dev;
> + gc->owner = THIS_MODULE;
> + gc->base = -1;
> + gc->can_sleep = true;
> + 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_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)
> +{
> + static const struct v4l2_mbus_framefmt format = {
> + .width = 640,
> + .height = 480,
> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
> + .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_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;
> +
> + if (pad != UB953_PAD_SOURCE)
> + return -EINVAL;
> +
> + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
> + priv->source_sd_pad, &source_fd);
> + if (ret)
> + return ret;
> +
> + memset(fd, 0, sizeof(*fd));
> +
> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
> +
> + state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> + routing = &state->routing;
> +
> + for_each_active_route(routing, route) {

I'd write

for_each_active_route(&state->routing, route) {

and drop the routing local variable.

> + struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
> + unsigned int i;
> +
> + if (route->source_pad != pad)
> + continue;
> +
> + for (i = 0; i < source_fd.num_entries; ++i) {
> + if (source_fd.entry[i].stream == route->sink_stream) {
> + source_entry = &source_fd.entry[i];
> + 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 = source_entry->flags;
> + 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 == UB953_PAD_SOURCE)
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + /* Set sink format */
> + fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
> + format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + /* Propagate to source format */
> + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> + format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + return 0;
> +}
> +
> +static int ub953_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = UB953_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = UB953_PAD_SOURCE,
> + .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 %#02x\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 %#02x\n", v);
> +
> + ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v);
> + dev_info(dev, "CSI_ERR_DLANE01 %#02x\n", v);
> +
> + ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v);
> + dev_info(dev, "CSI_ERR_DLANE23 %#02x\n", v);
> +
> + ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v);
> + dev_info(dev, "CSI_ERR_CLK_LANE %#02x\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 %#02x\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);
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
> + UB953_PAD_SINK,
> + &streams_mask);
> +
> + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> + 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);
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
> + UB953_PAD_SINK,
> + &streams_mask);
> +
> + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> + 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 int 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);
> +
> + if (state->routing.num_routes != 1)
> + return -EINVAL;
> +
> + fmt = v4l2_subdev_state_get_stream_format(state, UB953_PAD_SOURCE, 0);
> + if (!fmt)
> + return -EINVAL;
> +
> + if (fmt->code != MEDIA_BUS_FMT_UYVY8_1X16)
> + return -EINVAL;
> +
> + 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);
> +
> + return 0;
> +}
> +
> +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);
> + int ret = 0;
> +
> + switch (ctrl->id) {
> + case V4L2_CID_TEST_PATTERN:
> + if (ctrl->val == 0)
> + ub953_disable_tpg(priv);
> + else
> + ret = ub953_enable_tpg(priv, ctrl->val);
> + break;
> + }
> +
> + return ret;
> +}
> +
> +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;
> + int ret;
> +
> + 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;
> + priv->source_sd_pad = ret;
> +
> + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_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, priv->source_sd_pad,
> + priv->sd.name);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations ub953_notify_ops = {
> + .bound = ub953_notify_bound,
> +};
> +
> +static int ub953_v4l2_notifier_register(struct ub953_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + struct v4l2_async_subdev *asd;
> + struct fwnode_handle *ep_fwnode;
> + int ret;
> +
> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> + UB953_PAD_SINK, 0, 0);
> + if (!ep_fwnode) {
> + dev_err(dev, "No graph endpoint\n");
> + return -ENODEV;
> + }
> +
> + v4l2_async_nf_init(&priv->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(ep_fwnode);
> +
> + 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)
> +{
> + 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 u64 ub953_get_fc_rate(struct ub953_data *priv)
> +{
> + if (priv->mode != UB953_MODE_SYNC) {
> + /* Not supported */
> + return 0;
> + }
> +
> + if (priv->hw_data->is_ub971)
> + return priv->plat_data->bc_rate * 160ull;
> + else
> + return priv->plat_data->bc_rate / 2 * 160ull;
> +}
> +
> +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->is_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->is_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->is_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;
> +
> + if (!init.name)
> + return -ENOMEM;
> +
> + /* Initialize clkout to 25MHz by default */
> + if (priv->hw_data->is_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;
> +
> + 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 int ub953_parse_dt(struct ub953_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + struct fwnode_handle *ep_fwnode;
> + int ret;
> +
> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> + UB953_PAD_SINK, 0, 0);
> + if (!ep_fwnode) {
> + dev_err(dev, "OF: no endpoint\n");

dev_err_probe() ? Same below.

> + return -ENOENT;
> + }
> +
> + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
> +
> + fwnode_handle_put(ep_fwnode);
> +
> + if (ret <= 0) {
> + dev_err(dev, "failed to parse property 'data-lanes': %d\n",
> + ret);
> + return ret;
> + }
> +
> + if (ret != 1 && ret != 2 && ret != 4) {
> + dev_err(dev, "bad number of data-lanes: %d\n", ret);
> + return -EINVAL;
> + }
> +
> + priv->num_data_lanes = ret;
> +
> + return 0;
> +}
> +
> +static int ub953_hw_init(struct ub953_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + bool mode_override;
> + int ret;
> + u8 v;
> +
> + ret = ub953_read(priv, UB953_REG_MODE_SEL, &v);
> + if (ret)
> + return ret;
> +
> + if (!(v & UB953_REG_MODE_SEL_MODE_DONE))
> + return dev_err_probe(dev, -EIO, "Mode value not stabilized\n");
> +
> + mode_override = v & UB953_REG_MODE_SEL_MODE_OVERRIDE;
> +
> + switch (v & UB953_REG_MODE_SEL_MODE_MASK) {
> + 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:
> + return dev_err_probe(dev, -EIO,
> + "Illegal mode in mode register\n");

s/Illegal/Invalid/

Nobody will come to arrest you if this happens :-)

> + }
> +
> + dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap",
> + priv->mode);
> +
> + if (priv->mode != UB953_MODE_SYNC)
> + return dev_err_probe(dev, -ENODEV,
> + "Only synchronous mode supported\n");
> +
> + ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to read revision");
> +
> + 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)
> + return ret;
> +
> + dev_dbg(dev, "i2c strap setting %s V\n",
> + (v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "1.8" : "3.3");
> +
> + ret = ub953_i2c_master_init(priv);
> + if (ret)
> + return dev_err_probe(dev, ret, "i2c init failed\n");
> +
> + ub953_write(priv, UB953_REG_GENERAL_CFG,
> + UB953_REG_GENERAL_CFG_CONT_CLK |
> + ((priv->num_data_lanes - 1) << UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT) |
> + UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE);
> +
> + return 0;
> +}
> +
> +static int ub953_subdev_init(struct ub953_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + int ret;
> +
> + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops);
> +
> + v4l2_ctrl_handler_init(&priv->ctrl_handler,
> + ARRAY_SIZE(ub953_tpg_qmenu) - 1);

The last argument to v4l2_ctrl_handler_init() should be the number of
controls you will create, so 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)
> + return dev_err_probe(dev, priv->ctrl_handler.error,
> + "Failed to set up v4l2 controls\n");

You need to goto err_remove_ctrls.

> +
> + 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_probe(dev, ret, "Failed to init pads\n");
> + goto err_remove_ctrls;
> + }
> +
> + priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> + UB953_PAD_SOURCE, 0,
> + 0);
> + if (!priv->sd.fwnode) {
> + ret = -ENODEV;
> + dev_err_probe(dev, ret, "Missing TX endpoint\n");
> + goto err_entity_cleanup;
> + }
> +
> + priv->sd.state_lock = priv->sd.ctrl_handler->lock;
> +
> + ret = v4l2_subdev_init_finalize(&priv->sd);
> + if (ret)
> + goto err_entity_cleanup;

You're leaking prev->sd.fwnode. Same in ub953_subdev_uninit().

> +
> + ret = ub953_v4l2_notifier_register(priv);
> + if (ret) {
> + dev_err_probe(dev, ret,
> + "v4l2 subdev notifier register failed\n");
> + goto err_free_state;
> + }
> +
> + ret = v4l2_async_register_subdev(&priv->sd);
> + if (ret) {
> + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
> + goto err_unreg_notif;
> + }
> +
> + return 0;
> +
> +err_unreg_notif:
> + ub953_v4l2_notifier_unregister(priv);
> +err_free_state:
> + v4l2_subdev_cleanup(&priv->sd);
> +err_entity_cleanup:
> + media_entity_cleanup(&priv->sd.entity);
> +err_remove_ctrls:
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +
> + return ret;
> +}
> +
> +static void ub953_subdev_uninit(struct ub953_data *priv)
> +{
> + v4l2_async_unregister_subdev(&priv->sd);
> + ub953_v4l2_notifier_unregister(priv);
> + v4l2_subdev_cleanup(&priv->sd);
> + media_entity_cleanup(&priv->sd.entity);
> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
> +}
> +
> +static int ub953_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct ub953_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;

Can this happen ?

Reviewed-by: Laurent Pinchart <[email protected]>

> +
> + priv->plat_data = dev_get_platdata(&client->dev);
> + if (!priv->plat_data)
> + return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
> +
> + 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)) {
> + ret = PTR_ERR(priv->regmap);
> + dev_err_probe(dev, ret, "Failed to init regmap\n");
> + goto err_mutex_destroy;
> + }
> +
> + ret = ub953_parse_dt(priv);
> + if (ret)
> + goto err_mutex_destroy;
> +
> + ret = ub953_hw_init(priv);
> + if (ret)
> + goto err_mutex_destroy;
> +
> + ret = ub953_gpiochip_probe(priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to init gpiochip\n");
> + goto err_mutex_destroy;
> + }
> +
> + ret = ub953_register_clkout(priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to register clkout\n");
> + goto err_gpiochip_remove;
> + }
> +
> + ret = ub953_subdev_init(priv);
> + if (ret)
> + goto err_gpiochip_remove;
> +
> + ret = ub953_add_i2c_adapter(priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
> + goto err_subdev_uninit;
> + }
> +
> + return 0;
> +
> +err_subdev_uninit:
> + ub953_subdev_uninit(priv);
> +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);
> +
> + i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port);
> +
> + ub953_subdev_uninit(priv);
> +
> + 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",
> + .is_ub971 = true,
> +};
> +
> +static const struct i2c_device_id ub953_id[] = {
> + { "ds90ub953-q1", 0 },
> + { "ds90ub971-q1", 0 },
> + {}
> +};
> +MODULE_DEVICE_TABLE(i2c, ub953_id);
> +
> +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);
> +
> +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 = ub953_dt_ids,
> + },
> +};
> +module_i2c_driver(ds90ub953_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV CSI-2 Serializers Driver");
> +MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
> +MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
> +MODULE_IMPORT_NS(I2C_ATR);

--
Regards,

Laurent Pinchart

2023-01-20 01:43:00

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 6/7] media: i2c: add DS90UB913 driver

Hi Tomi,

Thank you for the patch.

On Wed, Jan 18, 2023 at 02:40:30PM +0200, Tomi Valkeinen wrote:
> Add driver for TI DS90UB913 FPD-Link III Serializer.
>
> Signed-off-by: Tomi Valkeinen <[email protected]>
> ---
> drivers/media/i2c/Kconfig | 13 +
> drivers/media/i2c/Makefile | 1 +
> drivers/media/i2c/ds90ub913.c | 848 ++++++++++++++++++++++++++++++++++
> 3 files changed, 862 insertions(+)
> create mode 100644 drivers/media/i2c/ds90ub913.c
>
> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
> index dc1c7c80dc1c..e0a1c2a5f3bf 100644
> --- a/drivers/media/i2c/Kconfig
> +++ b/drivers/media/i2c/Kconfig
> @@ -1601,6 +1601,19 @@ endmenu
>
> menu "Video serializers and deserializers"
>
> +config VIDEO_DS90UB913
> + tristate "TI DS90UB913 FPD-Link III Serializer"
> + depends on OF && I2C && VIDEO_DEV
> + select I2C_ATR
> + select MEDIA_CONTROLLER
> + select OF_GPIO
> + select REGMAP_I2C
> + select V4L2_FWNODE
> + select VIDEO_V4L2_SUBDEV_API
> + help
> + Device driver for the Texas Instruments DS90UB913
> + FPD-Link III Serializer.
> +
> config VIDEO_DS90UB960
> tristate "TI FPD-Link III/IV Deserializers"
> depends on OF && I2C && VIDEO_DEV
> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
> index 4cd680f3b953..c4875ec8c3b9 100644
> --- a/drivers/media/i2c/Makefile
> +++ b/drivers/media/i2c/Makefile
> @@ -142,4 +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_DS90UB913) += ds90ub913.o
> obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o
> diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
> new file mode 100644
> index 000000000000..befa78128a9a
> --- /dev/null
> +++ b/drivers/media/i2c/ds90ub913.c
> @@ -0,0 +1,848 @@
> +// 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) 2023 Tomi Valkeinen <[email protected]>
> + */
> +
> +#include <linux/clk-provider.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/fwnode.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/property.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_GENERAL_CFG_CRC_ERR_RESET BIT(5)
> +#define UB913_REG_GENERAL_CFG_PCLK_RISING BIT(0)
> +
> +#define UB913_REG_MODE_SEL 0x05
> +#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5)
> +#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4)
> +#define UB913_REG_MODE_SEL_MODE_MASK GENMASK(3, 0)
> +
> +#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;
> +
> + struct gpio_chip gpio_chip;
> +
> + struct v4l2_subdev sd;
> + struct media_pad pads[2];
> +
> + struct v4l2_async_notifier notifier;
> +
> + struct v4l2_subdev *source_sd;
> + u16 source_sd_pad;
> +
> + u64 enabled_source_streams;
> +
> + struct clk_hw *clkout_clk_hw;
> +
> + struct ds90ub9xx_platform_data *plat_data;
> +
> + u32 pclk_polarity;
> +};
> +
> +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 = offset / 2;
> + unsigned int field_idx = offset % 2;
> +
> + return 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));

I find the indentation weird, I would have written

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

Your call.

> +}
> +
> +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);
> +
> + gc->label = dev_name(dev);
> + gc->parent = dev;
> + gc->owner = THIS_MODULE;
> + gc->base = -1;
> + gc->can_sleep = true;
> + 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_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 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);
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
> + UB913_PAD_SINK,
> + &streams_mask);
> +
> + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
> + 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);
> + u64 sink_streams;
> + int ret;
> +
> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
> + UB913_PAD_SINK,
> + &streams_mask);
> +
> + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
> + 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)
> +{
> + static 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_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;
> +
> + if (pad != UB913_PAD_SOURCE)
> + return -EINVAL;
> +
> + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
> + priv->source_sd_pad, &source_fd);
> + if (ret)
> + return ret;
> +
> + memset(fd, 0, sizeof(*fd));
> +
> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
> +
> + state = v4l2_subdev_lock_and_get_active_state(sd);
> +
> + routing = &state->routing;
> +
> + for_each_active_route(routing, route) {
> + unsigned int i;
> +
> + if (route->source_pad != pad)
> + continue;
> +
> + for (i = 0; i < source_fd.num_entries; ++i) {
> + if (source_fd.entry[i].stream == route->sink_stream)
> + break;
> + }
> +
> + if (i == 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 = source_fd.entry[i].flags;
> + fd->entry[fd->num_entries].length = source_fd.entry[i].length;
> + fd->entry[fd->num_entries].pixelcode =
> + source_fd.entry[i].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 == UB913_PAD_SOURCE)
> + return v4l2_subdev_get_fmt(sd, state, format);
> +
> + /* Set sink format */
> + fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
> + format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + /* Propagate to source format */
> + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
> + format->stream);
> + if (!fmt)
> + return -EINVAL;
> +
> + *fmt = format->format;
> +
> + return 0;
> +}
> +
> +static int ub913_init_cfg(struct v4l2_subdev *sd,
> + struct v4l2_subdev_state *state)
> +{
> + struct v4l2_subdev_route routes[] = {
> + {
> + .sink_pad = UB913_PAD_SINK,
> + .sink_stream = 0,
> + .source_pad = UB913_PAD_SOURCE,
> + .source_stream = 0,
> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> + },
> + };
> +
> + struct v4l2_subdev_krouting routing = {
> + .num_routes = ARRAY_SIZE(routes),
> + .routes = routes,
> + };
> +
> + return _ub913_set_routing(sd, state, &routing);
> +}
> +
> +static int ub913_log_status(struct v4l2_subdev *sd)
> +{
> + struct ub913_data *priv = sd_to_ub913(sd);
> + struct device *dev = &priv->client->dev;
> + u8 v, v1, v2;
> +
> + ub913_read(priv, UB913_REG_MODE_SEL, &v);
> + dev_info(dev, "MODE_SEL %#02x\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));
> +
> + /* clear CRC errors */
> + ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
> + ub913_write(priv, UB913_REG_GENERAL_CFG,
> + v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET);
> + ub913_write(priv, UB913_REG_GENERAL_CFG, v);
> +
> + ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
> + dev_info(dev, "GENERAL_STATUS %#02x\n", v);
> +
> + ub913_read(priv, UB913_REG_PLL_OVR, &v);
> + dev_info(dev, "PLL_OVR %#02x\n", 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;
> + int ret;
> +
> + 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;
> + priv->source_sd_pad = ret;
> +
> + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
> + &priv->sd.entity, UB913_PAD_SINK,
> + MEDIA_LNK_FL_ENABLED |
> + MEDIA_LNK_FL_IMMUTABLE);

Weird indentation again. It was fine in the previous version :-)

> + if (ret) {
> + dev_err(dev, "Unable to link %s:%u -> %s:0\n",
> + source_subdev->name, priv->source_sd_pad,
> + priv->sd.name);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations ub913_notify_ops = {
> + .bound = ub913_notify_bound,
> +};
> +
> +static int ub913_v4l2_notifier_register(struct ub913_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + struct v4l2_async_subdev *asd;
> + struct fwnode_handle *ep_fwnode;
> + int ret;
> +
> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> + UB913_PAD_SINK, 0, 0);
> + if (!ep_fwnode) {
> + dev_err(dev, "No graph endpoint\n");
> + return -ENODEV;
> + }
> +
> + v4l2_async_nf_init(&priv->notifier);
> +
> + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
> + struct v4l2_async_subdev);
> +
> + fwnode_handle_put(ep_fwnode);
> +
> + 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)
> +{
> + 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));
> + if (!name)
> + return -ENOMEM;
> +
> + 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;
> +
> + return 0;
> +}
> +
> +static int ub913_parse_dt(struct ub913_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + struct fwnode_handle *ep_fwnode;
> + int ret;
> +
> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> + UB913_PAD_SINK, 0, 0);
> + if (!ep_fwnode) {
> + dev_err(dev, "No sink endpoint\n");
> + return -ENOENT;
> + }
> +
> + ret = fwnode_property_read_u32(ep_fwnode, "pclk-sample",
> + &priv->pclk_polarity);
> +
> + fwnode_handle_put(ep_fwnode);
> +
> + if (ret) {
> + dev_err(dev, "OF: failed to parse pclk-sample: %d\n", ret);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ub913_hw_init(struct ub913_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + bool mode_override;
> + u8 mode;
> + int ret;
> + u8 v;
> +
> + ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
> + if (ret)
> + return ret;
> +
> + if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE))
> + return dev_err_probe(dev, -ENODEV,
> + "Mode value not stabilized\n");
> +
> + mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE;
> + mode = v & UB913_REG_MODE_SEL_MODE_MASK;
> +
> + dev_dbg(dev, "mode from %s: %#x\n",
> + mode_override ? "reg" : "deserializer", mode);
> +
> + ret = ub913_i2c_master_init(priv);
> + if (ret)
> + return dev_err_probe(dev, ret, "i2c master init failed\n");
> +
> + ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
> + v &= ~UB913_REG_GENERAL_CFG_PCLK_RISING;
> + v |= priv->pclk_polarity ? UB913_REG_GENERAL_CFG_PCLK_RISING : 0;
> + ub913_write(priv, UB913_REG_GENERAL_CFG, v);
> +
> + return 0;
> +}
> +
> +static int ub913_subdev_init(struct ub913_data *priv)
> +{
> + struct device *dev = &priv->client->dev;
> + int ret;
> +
> + 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)
> + return dev_err_probe(dev, ret, "Failed to init pads\n");
> +
> + priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
> + UB913_PAD_SOURCE, 0,
> + 0);
> +
> + if (!priv->sd.fwnode) {
> + ret = -ENODEV;
> + dev_err_probe(dev, ret, "Missing TX endpoint\n");
> + goto err_entity_cleanup;
> + }
> +
> + ret = v4l2_subdev_init_finalize(&priv->sd);
> + if (ret)
> + goto err_entity_cleanup;

You're leaking priv->sd.fwnode here, you need to call
fwnode_handle_put() in the error path.

> +
> + ret = ub913_v4l2_notifier_register(priv);
> + if (ret) {
> + dev_err_probe(dev, ret,
> + "v4l2 subdev notifier register failed\n");
> + goto err_subdev_cleanup;
> + }
> +
> + ret = v4l2_async_register_subdev(&priv->sd);
> + if (ret) {
> + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
> + goto err_unreg_notif;
> + }
> +
> + return 0;
> +
> +err_unreg_notif:
> + ub913_v4l2_nf_unregister(priv);
> +err_subdev_cleanup:
> + v4l2_subdev_cleanup(&priv->sd);
> +err_entity_cleanup:
> + media_entity_cleanup(&priv->sd.entity);
> +
> + return ret;
> +}
> +
> +static void ub913_subdev_uninit(struct ub913_data *priv)
> +{
> + v4l2_async_unregister_subdev(&priv->sd);
> + ub913_v4l2_nf_unregister(priv);
> + v4l2_subdev_cleanup(&priv->sd);
> + media_entity_cleanup(&priv->sd.entity);

Missing fwnode_handle_put() here too.

> +}
> +
> +static int ub913_probe(struct i2c_client *client)
> +{
> + struct device *dev = &client->dev;
> + struct ub913_data *priv;
> + int ret;
> +
> + 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)
> + return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
> +
> + priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
> + if (IS_ERR(priv->regmap))
> + return dev_err_probe(dev, PTR_ERR(priv->regmap),
> + "Failed to init regmap\n");
> +
> + /*
> + * ub913 can also work without ext clock, but that is not supported by
> + * the driver yet

s/yet/yet./

Reviewed-by: Laurent Pinchart <[email protected]>

> + */
> + priv->clkin = devm_clk_get(dev, "clkin");
> + if (IS_ERR(priv->clkin))
> + return dev_err_probe(dev, PTR_ERR(priv->clkin),
> + "Cannot get CLKIN\n");
> +
> + ret = ub913_parse_dt(priv);
> + if (ret)
> + return ret;
> +
> + ret = ub913_hw_init(priv);
> + if (ret)
> + return ret;
> +
> + ret = ub913_gpiochip_probe(priv);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to init gpiochip\n");
> +
> + ret = ub913_register_clkout(priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "Failed to register clkout\n");
> + goto err_gpiochip_remove;
> + }
> +
> + ret = ub913_subdev_init(priv);
> + if (ret)
> + goto err_gpiochip_remove;
> +
> + ret = ub913_add_i2c_adapter(priv);
> + if (ret) {
> + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n");
> + goto err_subdev_uninit;
> + }
> +
> + return 0;
> +
> +err_subdev_uninit:
> + ub913_subdev_uninit(priv);
> +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);
> +
> + i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port);
> +
> + ub913_subdev_uninit(priv);
> +
> + ub913_gpiochip_remove(priv);
> +}
> +
> +static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} };
> +MODULE_DEVICE_TABLE(i2c, ub913_id);
> +
> +static const struct of_device_id ub913_dt_ids[] = {
> + { .compatible = "ti,ds90ub913a-q1" },
> + {}
> +};
> +MODULE_DEVICE_TABLE(of, ub913_dt_ids);
> +
> +static struct i2c_driver ds90ub913_driver = {
> + .probe_new = ub913_probe,
> + .remove = ub913_remove,
> + .id_table = ub913_id,
> + .driver = {
> + .name = "ds90ub913a",
> + .of_match_table = ub913_dt_ids,
> + },
> +};
> +module_i2c_driver(ds90ub913_driver);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("Texas Instruments DS90UB913 FPD-Link III Serializer Driver");
> +MODULE_AUTHOR("Luca Ceresoli <[email protected]>");
> +MODULE_AUTHOR("Tomi Valkeinen <[email protected]>");
> +MODULE_IMPORT_NS(I2C_ATR);

--
Regards,

Laurent Pinchart

2023-01-20 07:19:42

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 6/7] media: i2c: add DS90UB913 driver

On 20/01/2023 02:03, Laurent Pinchart wrote:
> Hi Tomi,
>
> Thank you for the patch.
>
> On Wed, Jan 18, 2023 at 02:40:30PM +0200, Tomi Valkeinen wrote:
>> Add driver for TI DS90UB913 FPD-Link III Serializer.
>>
>> Signed-off-by: Tomi Valkeinen <[email protected]>
>> ---
>> drivers/media/i2c/Kconfig | 13 +
>> drivers/media/i2c/Makefile | 1 +
>> drivers/media/i2c/ds90ub913.c | 848 ++++++++++++++++++++++++++++++++++
>> 3 files changed, 862 insertions(+)
>> create mode 100644 drivers/media/i2c/ds90ub913.c
>>
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index dc1c7c80dc1c..e0a1c2a5f3bf 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -1601,6 +1601,19 @@ endmenu
>>
>> menu "Video serializers and deserializers"
>>
>> +config VIDEO_DS90UB913
>> + tristate "TI DS90UB913 FPD-Link III Serializer"
>> + depends on OF && I2C && VIDEO_DEV
>> + select I2C_ATR
>> + select MEDIA_CONTROLLER
>> + select OF_GPIO
>> + select REGMAP_I2C
>> + select V4L2_FWNODE
>> + select VIDEO_V4L2_SUBDEV_API
>> + help
>> + Device driver for the Texas Instruments DS90UB913
>> + FPD-Link III Serializer.
>> +
>> config VIDEO_DS90UB960
>> tristate "TI FPD-Link III/IV Deserializers"
>> depends on OF && I2C && VIDEO_DEV
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index 4cd680f3b953..c4875ec8c3b9 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -142,4 +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_DS90UB913) += ds90ub913.o
>> obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o
>> diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c
>> new file mode 100644
>> index 000000000000..befa78128a9a
>> --- /dev/null
>> +++ b/drivers/media/i2c/ds90ub913.c
>> @@ -0,0 +1,848 @@
>> +// 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) 2023 Tomi Valkeinen <[email protected]>
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/fwnode.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/property.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_GENERAL_CFG_CRC_ERR_RESET BIT(5)
>> +#define UB913_REG_GENERAL_CFG_PCLK_RISING BIT(0)
>> +
>> +#define UB913_REG_MODE_SEL 0x05
>> +#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5)
>> +#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4)
>> +#define UB913_REG_MODE_SEL_MODE_MASK GENMASK(3, 0)
>> +
>> +#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;
>> +
>> + struct gpio_chip gpio_chip;
>> +
>> + struct v4l2_subdev sd;
>> + struct media_pad pads[2];
>> +
>> + struct v4l2_async_notifier notifier;
>> +
>> + struct v4l2_subdev *source_sd;
>> + u16 source_sd_pad;
>> +
>> + u64 enabled_source_streams;
>> +
>> + struct clk_hw *clkout_clk_hw;
>> +
>> + struct ds90ub9xx_platform_data *plat_data;
>> +
>> + u32 pclk_polarity;
>> +};
>> +
>> +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 = offset / 2;
>> + unsigned int field_idx = offset % 2;
>> +
>> + return 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));
>
> I find the indentation weird, I would have written
>
> return 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));
>
> Your call.

It's clang-format. I still haven't found out how to make it indent as
I'd like. Actually, with kernel's clang-format settings this ends up as:

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

But checkpatch complains about line ending with (. Adjusting the
settings a bit I got it to indent as it is currently.

The indent after the line ending with | make sense, I think, but I don't
mind your formatting either.

>> +}
>> +
>> +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);
>> +
>> + gc->label = dev_name(dev);
>> + gc->parent = dev;
>> + gc->owner = THIS_MODULE;
>> + gc->base = -1;
>> + gc->can_sleep = true;
>> + 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_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 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);
>> + u64 sink_streams;
>> + int ret;
>> +
>> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
>> + UB913_PAD_SINK,
>> + &streams_mask);
>> +
>> + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
>> + 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);
>> + u64 sink_streams;
>> + int ret;
>> +
>> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE,
>> + UB913_PAD_SINK,
>> + &streams_mask);
>> +
>> + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
>> + 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)
>> +{
>> + static 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_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;
>> +
>> + if (pad != UB913_PAD_SOURCE)
>> + return -EINVAL;
>> +
>> + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
>> + priv->source_sd_pad, &source_fd);
>> + if (ret)
>> + return ret;
>> +
>> + memset(fd, 0, sizeof(*fd));
>> +
>> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL;
>> +
>> + state = v4l2_subdev_lock_and_get_active_state(sd);
>> +
>> + routing = &state->routing;
>> +
>> + for_each_active_route(routing, route) {
>> + unsigned int i;
>> +
>> + if (route->source_pad != pad)
>> + continue;
>> +
>> + for (i = 0; i < source_fd.num_entries; ++i) {
>> + if (source_fd.entry[i].stream == route->sink_stream)
>> + break;
>> + }
>> +
>> + if (i == 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 = source_fd.entry[i].flags;
>> + fd->entry[fd->num_entries].length = source_fd.entry[i].length;
>> + fd->entry[fd->num_entries].pixelcode =
>> + source_fd.entry[i].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 == UB913_PAD_SOURCE)
>> + return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> + /* Set sink format */
>> + fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
>> + format->stream);
>> + if (!fmt)
>> + return -EINVAL;
>> +
>> + *fmt = format->format;
>> +
>> + /* Propagate to source format */
>> + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> + format->stream);
>> + if (!fmt)
>> + return -EINVAL;
>> +
>> + *fmt = format->format;
>> +
>> + return 0;
>> +}
>> +
>> +static int ub913_init_cfg(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *state)
>> +{
>> + struct v4l2_subdev_route routes[] = {
>> + {
>> + .sink_pad = UB913_PAD_SINK,
>> + .sink_stream = 0,
>> + .source_pad = UB913_PAD_SOURCE,
>> + .source_stream = 0,
>> + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
>> + },
>> + };
>> +
>> + struct v4l2_subdev_krouting routing = {
>> + .num_routes = ARRAY_SIZE(routes),
>> + .routes = routes,
>> + };
>> +
>> + return _ub913_set_routing(sd, state, &routing);
>> +}
>> +
>> +static int ub913_log_status(struct v4l2_subdev *sd)
>> +{
>> + struct ub913_data *priv = sd_to_ub913(sd);
>> + struct device *dev = &priv->client->dev;
>> + u8 v, v1, v2;
>> +
>> + ub913_read(priv, UB913_REG_MODE_SEL, &v);
>> + dev_info(dev, "MODE_SEL %#02x\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));
>> +
>> + /* clear CRC errors */
>> + ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
>> + ub913_write(priv, UB913_REG_GENERAL_CFG,
>> + v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET);
>> + ub913_write(priv, UB913_REG_GENERAL_CFG, v);
>> +
>> + ub913_read(priv, UB913_REG_GENERAL_STATUS, &v);
>> + dev_info(dev, "GENERAL_STATUS %#02x\n", v);
>> +
>> + ub913_read(priv, UB913_REG_PLL_OVR, &v);
>> + dev_info(dev, "PLL_OVR %#02x\n", 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;
>> + int ret;
>> +
>> + 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;
>> + priv->source_sd_pad = ret;
>> +
>> + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad,
>> + &priv->sd.entity, UB913_PAD_SINK,
>> + MEDIA_LNK_FL_ENABLED |
>> + MEDIA_LNK_FL_IMMUTABLE);
>
> Weird indentation again. It was fine in the previous version :-)
>
>> + if (ret) {
>> + dev_err(dev, "Unable to link %s:%u -> %s:0\n",
>> + source_subdev->name, priv->source_sd_pad,
>> + priv->sd.name);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations ub913_notify_ops = {
>> + .bound = ub913_notify_bound,
>> +};
>> +
>> +static int ub913_v4l2_notifier_register(struct ub913_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + struct v4l2_async_subdev *asd;
>> + struct fwnode_handle *ep_fwnode;
>> + int ret;
>> +
>> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> + UB913_PAD_SINK, 0, 0);
>> + if (!ep_fwnode) {
>> + dev_err(dev, "No graph endpoint\n");
>> + return -ENODEV;
>> + }
>> +
>> + v4l2_async_nf_init(&priv->notifier);
>> +
>> + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
>> + struct v4l2_async_subdev);
>> +
>> + fwnode_handle_put(ep_fwnode);
>> +
>> + 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)
>> +{
>> + 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));
>> + if (!name)
>> + return -ENOMEM;
>> +
>> + 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;
>> +
>> + return 0;
>> +}
>> +
>> +static int ub913_parse_dt(struct ub913_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + struct fwnode_handle *ep_fwnode;
>> + int ret;
>> +
>> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> + UB913_PAD_SINK, 0, 0);
>> + if (!ep_fwnode) {
>> + dev_err(dev, "No sink endpoint\n");
>> + return -ENOENT;
>> + }
>> +
>> + ret = fwnode_property_read_u32(ep_fwnode, "pclk-sample",
>> + &priv->pclk_polarity);
>> +
>> + fwnode_handle_put(ep_fwnode);
>> +
>> + if (ret) {
>> + dev_err(dev, "OF: failed to parse pclk-sample: %d\n", ret);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int ub913_hw_init(struct ub913_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + bool mode_override;
>> + u8 mode;
>> + int ret;
>> + u8 v;
>> +
>> + ret = ub913_read(priv, UB913_REG_MODE_SEL, &v);
>> + if (ret)
>> + return ret;
>> +
>> + if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE))
>> + return dev_err_probe(dev, -ENODEV,
>> + "Mode value not stabilized\n");
>> +
>> + mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE;
>> + mode = v & UB913_REG_MODE_SEL_MODE_MASK;
>> +
>> + dev_dbg(dev, "mode from %s: %#x\n",
>> + mode_override ? "reg" : "deserializer", mode);
>> +
>> + ret = ub913_i2c_master_init(priv);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "i2c master init failed\n");
>> +
>> + ub913_read(priv, UB913_REG_GENERAL_CFG, &v);
>> + v &= ~UB913_REG_GENERAL_CFG_PCLK_RISING;
>> + v |= priv->pclk_polarity ? UB913_REG_GENERAL_CFG_PCLK_RISING : 0;
>> + ub913_write(priv, UB913_REG_GENERAL_CFG, v);
>> +
>> + return 0;
>> +}
>> +
>> +static int ub913_subdev_init(struct ub913_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + int ret;
>> +
>> + 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)
>> + return dev_err_probe(dev, ret, "Failed to init pads\n");
>> +
>> + priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> + UB913_PAD_SOURCE, 0,
>> + 0);
>> +
>> + if (!priv->sd.fwnode) {
>> + ret = -ENODEV;
>> + dev_err_probe(dev, ret, "Missing TX endpoint\n");
>> + goto err_entity_cleanup;
>> + }
>> +
>> + ret = v4l2_subdev_init_finalize(&priv->sd);
>> + if (ret)
>> + goto err_entity_cleanup;
>
> You're leaking priv->sd.fwnode here, you need to call
> fwnode_handle_put() in the error path.

Ok.

>> +
>> + ret = ub913_v4l2_notifier_register(priv);
>> + if (ret) {
>> + dev_err_probe(dev, ret,
>> + "v4l2 subdev notifier register failed\n");
>> + goto err_subdev_cleanup;
>> + }
>> +
>> + ret = v4l2_async_register_subdev(&priv->sd);
>> + if (ret) {
>> + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
>> + goto err_unreg_notif;
>> + }
>> +
>> + return 0;
>> +
>> +err_unreg_notif:
>> + ub913_v4l2_nf_unregister(priv);
>> +err_subdev_cleanup:
>> + v4l2_subdev_cleanup(&priv->sd);
>> +err_entity_cleanup:
>> + media_entity_cleanup(&priv->sd.entity);
>> +
>> + return ret;
>> +}
>> +
>> +static void ub913_subdev_uninit(struct ub913_data *priv)
>> +{
>> + v4l2_async_unregister_subdev(&priv->sd);
>> + ub913_v4l2_nf_unregister(priv);
>> + v4l2_subdev_cleanup(&priv->sd);
>> + media_entity_cleanup(&priv->sd.entity);
>
> Missing fwnode_handle_put() here too.

Yep.

>> +}
>> +
>> +static int ub913_probe(struct i2c_client *client)
>> +{
>> + struct device *dev = &client->dev;
>> + struct ub913_data *priv;
>> + int ret;
>> +
>> + 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)
>> + return dev_err_probe(dev, -ENODEV, "Platform data missing\n");
>> +
>> + priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config);
>> + if (IS_ERR(priv->regmap))
>> + return dev_err_probe(dev, PTR_ERR(priv->regmap),
>> + "Failed to init regmap\n");
>> +
>> + /*
>> + * ub913 can also work without ext clock, but that is not supported by
>> + * the driver yet
>
> s/yet/yet./
>
> Reviewed-by: Laurent Pinchart <[email protected]>

Thanks!

Tomi

2023-01-20 08:21:36

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 7/7] media: i2c: add DS90UB953 driver

On 20/01/2023 02:34, Laurent Pinchart wrote:
> Hi Tomi,
>
> Thank you for the patch.
>
> On Wed, Jan 18, 2023 at 02:40:31PM +0200, Tomi Valkeinen wrote:
>> Add driver for TI DS90UB953 FPD-Link III Serializer.
>>
>> Signed-off-by: Tomi Valkeinen <[email protected]>
>> ---
>> drivers/media/i2c/Kconfig | 13 +
>> drivers/media/i2c/Makefile | 1 +
>> drivers/media/i2c/ds90ub953.c | 1576 +++++++++++++++++++++++++++++++++
>> 3 files changed, 1590 insertions(+)
>> create mode 100644 drivers/media/i2c/ds90ub953.c
>>
>> diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
>> index e0a1c2a5f3bf..0590312ec751 100644
>> --- a/drivers/media/i2c/Kconfig
>> +++ b/drivers/media/i2c/Kconfig
>> @@ -1614,6 +1614,19 @@ config VIDEO_DS90UB913
>> Device driver for the Texas Instruments DS90UB913
>> FPD-Link III Serializer.
>>
>> +config VIDEO_DS90UB953
>> + tristate "TI FPD-Link III/IV CSI-2 Serializers"
>> + depends on OF && I2C && VIDEO_DEV
>> + select I2C_ATR
>> + select MEDIA_CONTROLLER
>> + select OF_GPIO
>> + select REGMAP_I2C
>> + select V4L2_FWNODE
>> + select VIDEO_V4L2_SUBDEV_API
>> + help
>> + Device driver for the Texas Instruments DS90UB953
>> + FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer.
>> +
>> config VIDEO_DS90UB960
>> tristate "TI FPD-Link III/IV Deserializers"
>> depends on OF && I2C && VIDEO_DEV
>> diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
>> index c4875ec8c3b9..efd5f717a5f7 100644
>> --- a/drivers/media/i2c/Makefile
>> +++ b/drivers/media/i2c/Makefile
>> @@ -143,4 +143,5 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o
>> obj-$(CONFIG_VIDEO_WM8739) += wm8739.o
>> obj-$(CONFIG_VIDEO_WM8775) += wm8775.o
>> obj-$(CONFIG_VIDEO_DS90UB913) += ds90ub913.o
>> +obj-$(CONFIG_VIDEO_DS90UB953) += ds90ub953.o
>> obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o
>> diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c
>> new file mode 100644
>> index 000000000000..ec33e16da3d1
>> --- /dev/null
>> +++ b/drivers/media/i2c/ds90ub953.c
>> @@ -0,0 +1,1576 @@
>> +// 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) 2023 Tomi Valkeinen <[email protected]>
>> + */
>> +
>> +#include <linux/clk-provider.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/fwnode.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_device.h>
>> +#include <linux/property.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_GENERAL_CFG_CONT_CLK BIT(6)
>> +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT 4
>> +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_MASK GENMASK(5, 4)
>> +#define UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE BIT(1)
>> +#define UB953_REG_GENERAL_CFG_I2C_STRAP_MODE BIT(0)
>> +
>> +#define UB953_REG_MODE_SEL 0x03
>> +#define UB953_REG_MODE_SEL_MODE_DONE BIT(3)
>> +#define UB953_REG_MODE_SEL_MODE_OVERRIDE BIT(4)
>> +#define UB953_REG_MODE_SEL_MODE_MASK GENMASK(2, 0)
>> +
>> +#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
>
> You have a mix of uppercase and lowercase hex constants, please
> standardize on one of them. In the media subsystem we usually go for
> lowercase.

Yep.

>> +
>> +#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 III CSI-2 synchronous mode */
>> + UB953_MODE_SYNC,
>> + /* FPD-Link III CSI-2 non-synchronous mode, external ref clock */
>> + UB953_MODE_NONSYNC_EXT,
>> + /* FPD-Link III CSI-2 non-synchronous mode, internal ref clock */
>> + UB953_MODE_NONSYNC_INT,
>> + /* FPD-Link III DVP mode */
>> + UB953_MODE_DVP,
>> +};
>> +
>> +struct ub953_hw_data {
>> + const char *model;
>> + bool is_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;
>> +
>> + struct v4l2_subdev sd;
>> + struct media_pad pads[2];
>> +
>> + struct v4l2_async_notifier notifier;
>> +
>> + struct v4l2_subdev *source_sd;
>> + u16 source_sd_pad;
>> +
>> + struct v4l2_ctrl_handler ctrl_handler;
>> +
>> + u64 enabled_source_streams;
>> +
>> + /* 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;
>
> const.

Ok.

>> +};
>> +
>> +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)
>
> Is the leading _ needed ?

No. I'm not sure why I added that...

>> +{
>> + 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)
>
> I'd still prefer dropping this function, but I won't insist.
>
>> +{
>> + 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) {
>> + dev_err(&priv->client->dev,
>> + "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
>> + block, reg, ret);
>> + goto out;
>> + }
>
> Would it make sense to cache the address as you do with
> current_indirect_block, and skip this write if the address is correct
> already ? If the device implements auto-increment of the address (I
> haven't checked), this could save quite a few I2C writes.

Yes, there's auto increment for these indirect register accesses
(IA_AUTO_INC). There's also IA_READ bit, but I don't understand what it
does:

Indirect Access Read:
Setting this allows generation of a read strobe to the selected register
block upon setting of the IND_ACC_ADDR register. In auto-increment mode,
read strobes are also asserted following a read of the IND_ACC_DATA
register. This function is only required for blocks that need to
pre-fetch register data.

In any case, the indirect accesses are only used when configuring the
TPG. I'm sure this can be optimized, but I question the need to do this
optimization.

The UB960 driver uses indirect accesses more often. There it might make
a bit more sense, although, again, I don't really see why it matters in
practice.

It doesn't sound like a complex optimization, so maybe it wouldn't
increase the chances of bugs much, but... still. Maybe something for later?

>> +
>> + ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v);
>> + if (ret) {
>> + dev_err(&priv->client->dev,
>> + "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
>> + block, reg, 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) {
>> + dev_err(&priv->client->dev,
>> + "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n",
>> + block, reg, ret);
>> + goto out;
>> + }
>> +
>> + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val);
>> + if (ret) {
>> + dev_err(&priv->client->dev,
>> + "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n",
>> + block, reg, ret);
>> + }
>> +
>> +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;
>
> Does the device auto-increment the address ? If so you could skip this.

Yes. As mentioned above, I can look at this if you think it's relevant,
but maybe this can be done after the series has been merged?

>> +
>> + 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),
>
> Weird indentation.

It's clang-format, and I think it makes sense to indent a line after |,
to separate the parameters to the function.

>> + 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);
>> +
>> + gc->label = dev_name(dev);
>> + gc->parent = dev;
>> + gc->owner = THIS_MODULE;
>> + gc->base = -1;
>> + gc->can_sleep = true;
>> + 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_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)
>> +{
>> + static const struct v4l2_mbus_framefmt format = {
>> + .width = 640,
>> + .height = 480,
>> + .code = MEDIA_BUS_FMT_UYVY8_1X16,
>> + .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_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;
>> +
>> + if (pad != UB953_PAD_SOURCE)
>> + return -EINVAL;
>> +
>> + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc,
>> + priv->source_sd_pad, &source_fd);
>> + if (ret)
>> + return ret;
>> +
>> + memset(fd, 0, sizeof(*fd));
>> +
>> + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2;
>> +
>> + state = v4l2_subdev_lock_and_get_active_state(sd);
>> +
>> + routing = &state->routing;
>> +
>> + for_each_active_route(routing, route) {
>
> I'd write
>
> for_each_active_route(&state->routing, route) {
>
> and drop the routing local variable.

Ok.

>> + struct v4l2_mbus_frame_desc_entry *source_entry = NULL;
>> + unsigned int i;
>> +
>> + if (route->source_pad != pad)
>> + continue;
>> +
>> + for (i = 0; i < source_fd.num_entries; ++i) {
>> + if (source_fd.entry[i].stream == route->sink_stream) {
>> + source_entry = &source_fd.entry[i];
>> + 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 = source_entry->flags;
>> + 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 == UB953_PAD_SOURCE)
>> + return v4l2_subdev_get_fmt(sd, state, format);
>> +
>> + /* Set sink format */
>> + fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
>> + format->stream);
>> + if (!fmt)
>> + return -EINVAL;
>> +
>> + *fmt = format->format;
>> +
>> + /* Propagate to source format */
>> + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
>> + format->stream);
>> + if (!fmt)
>> + return -EINVAL;
>> +
>> + *fmt = format->format;
>> +
>> + return 0;
>> +}
>> +
>> +static int ub953_init_cfg(struct v4l2_subdev *sd,
>> + struct v4l2_subdev_state *state)
>> +{
>> + struct v4l2_subdev_route routes[] = {
>> + {
>> + .sink_pad = UB953_PAD_SINK,
>> + .sink_stream = 0,
>> + .source_pad = UB953_PAD_SOURCE,
>> + .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 %#02x\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 %#02x\n", v);
>> +
>> + ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v);
>> + dev_info(dev, "CSI_ERR_DLANE01 %#02x\n", v);
>> +
>> + ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v);
>> + dev_info(dev, "CSI_ERR_DLANE23 %#02x\n", v);
>> +
>> + ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v);
>> + dev_info(dev, "CSI_ERR_CLK_LANE %#02x\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 %#02x\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);
>> + u64 sink_streams;
>> + int ret;
>> +
>> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
>> + UB953_PAD_SINK,
>> + &streams_mask);
>> +
>> + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad,
>> + 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);
>> + u64 sink_streams;
>> + int ret;
>> +
>> + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE,
>> + UB953_PAD_SINK,
>> + &streams_mask);
>> +
>> + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad,
>> + 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 int 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);
>> +
>> + if (state->routing.num_routes != 1)
>> + return -EINVAL;
>> +
>> + fmt = v4l2_subdev_state_get_stream_format(state, UB953_PAD_SOURCE, 0);
>> + if (!fmt)
>> + return -EINVAL;
>> +
>> + if (fmt->code != MEDIA_BUS_FMT_UYVY8_1X16)
>> + return -EINVAL;
>> +
>> + 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);
>> +
>> + return 0;
>> +}
>> +
>> +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);
>> + int ret = 0;
>> +
>> + switch (ctrl->id) {
>> + case V4L2_CID_TEST_PATTERN:
>> + if (ctrl->val == 0)
>> + ub953_disable_tpg(priv);
>> + else
>> + ret = ub953_enable_tpg(priv, ctrl->val);
>> + break;
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +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;
>> + int ret;
>> +
>> + 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;
>> + priv->source_sd_pad = ret;
>> +
>> + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_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, priv->source_sd_pad,
>> + priv->sd.name);
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static const struct v4l2_async_notifier_operations ub953_notify_ops = {
>> + .bound = ub953_notify_bound,
>> +};
>> +
>> +static int ub953_v4l2_notifier_register(struct ub953_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + struct v4l2_async_subdev *asd;
>> + struct fwnode_handle *ep_fwnode;
>> + int ret;
>> +
>> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> + UB953_PAD_SINK, 0, 0);
>> + if (!ep_fwnode) {
>> + dev_err(dev, "No graph endpoint\n");
>> + return -ENODEV;
>> + }
>> +
>> + v4l2_async_nf_init(&priv->notifier);
>> +
>> + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode,
>> + struct v4l2_async_subdev);
>> +
>> + fwnode_handle_put(ep_fwnode);
>> +
>> + 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)
>> +{
>> + 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 u64 ub953_get_fc_rate(struct ub953_data *priv)
>> +{
>> + if (priv->mode != UB953_MODE_SYNC) {
>> + /* Not supported */
>> + return 0;
>> + }
>> +
>> + if (priv->hw_data->is_ub971)
>> + return priv->plat_data->bc_rate * 160ull;
>> + else
>> + return priv->plat_data->bc_rate / 2 * 160ull;
>> +}
>> +
>> +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->is_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->is_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->is_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;
>> +
>> + if (!init.name)
>> + return -ENOMEM;
>> +
>> + /* Initialize clkout to 25MHz by default */
>> + if (priv->hw_data->is_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;
>> +
>> + 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 int ub953_parse_dt(struct ub953_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + struct fwnode_handle *ep_fwnode;
>> + int ret;
>> +
>> + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> + UB953_PAD_SINK, 0, 0);
>> + if (!ep_fwnode) {
>> + dev_err(dev, "OF: no endpoint\n");
>
> dev_err_probe() ? Same below.

Ok.

>> + return -ENOENT;
>> + }
>> +
>> + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes");
>> +
>> + fwnode_handle_put(ep_fwnode);
>> +
>> + if (ret <= 0) {
>> + dev_err(dev, "failed to parse property 'data-lanes': %d\n",
>> + ret);
>> + return ret;
>> + }
>> +
>> + if (ret != 1 && ret != 2 && ret != 4) {
>> + dev_err(dev, "bad number of data-lanes: %d\n", ret);
>> + return -EINVAL;
>> + }
>> +
>> + priv->num_data_lanes = ret;
>> +
>> + return 0;
>> +}
>> +
>> +static int ub953_hw_init(struct ub953_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + bool mode_override;
>> + int ret;
>> + u8 v;
>> +
>> + ret = ub953_read(priv, UB953_REG_MODE_SEL, &v);
>> + if (ret)
>> + return ret;
>> +
>> + if (!(v & UB953_REG_MODE_SEL_MODE_DONE))
>> + return dev_err_probe(dev, -EIO, "Mode value not stabilized\n");
>> +
>> + mode_override = v & UB953_REG_MODE_SEL_MODE_OVERRIDE;
>> +
>> + switch (v & UB953_REG_MODE_SEL_MODE_MASK) {
>> + 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:
>> + return dev_err_probe(dev, -EIO,
>> + "Illegal mode in mode register\n");
>
> s/Illegal/Invalid/
>
> Nobody will come to arrest you if this happens :-)

You've commented on this earlier too. I think "illegal" is commonly
used, like "illegal instruction" etc. But I can change it to make you
happy =).

>> + }
>> +
>> + dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap",
>> + priv->mode);
>> +
>> + if (priv->mode != UB953_MODE_SYNC)
>> + return dev_err_probe(dev, -ENODEV,
>> + "Only synchronous mode supported\n");
>> +
>> + ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "Failed to read revision");
>> +
>> + 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)
>> + return ret;
>> +
>> + dev_dbg(dev, "i2c strap setting %s V\n",
>> + (v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "1.8" : "3.3");
>> +
>> + ret = ub953_i2c_master_init(priv);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "i2c init failed\n");
>> +
>> + ub953_write(priv, UB953_REG_GENERAL_CFG,
>> + UB953_REG_GENERAL_CFG_CONT_CLK |
>> + ((priv->num_data_lanes - 1) << UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT) |
>> + UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE);
>> +
>> + return 0;
>> +}
>> +
>> +static int ub953_subdev_init(struct ub953_data *priv)
>> +{
>> + struct device *dev = &priv->client->dev;
>> + int ret;
>> +
>> + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops);
>> +
>> + v4l2_ctrl_handler_init(&priv->ctrl_handler,
>> + ARRAY_SIZE(ub953_tpg_qmenu) - 1);
>
> The last argument to v4l2_ctrl_handler_init() should be the number of
> controls you will create, so 1.

Hmm, right... I have the same in ub960.

>> + 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)
>> + return dev_err_probe(dev, priv->ctrl_handler.error,
>> + "Failed to set up v4l2 controls\n");
>
> You need to goto err_remove_ctrls.

Indeed.

>> +
>> + 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_probe(dev, ret, "Failed to init pads\n");
>> + goto err_remove_ctrls;
>> + }
>> +
>> + priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev),
>> + UB953_PAD_SOURCE, 0,
>> + 0);
>> + if (!priv->sd.fwnode) {
>> + ret = -ENODEV;
>> + dev_err_probe(dev, ret, "Missing TX endpoint\n");
>> + goto err_entity_cleanup;
>> + }
>> +
>> + priv->sd.state_lock = priv->sd.ctrl_handler->lock;
>> +
>> + ret = v4l2_subdev_init_finalize(&priv->sd);
>> + if (ret)
>> + goto err_entity_cleanup;
>
> You're leaking prev->sd.fwnode. Same in ub953_subdev_uninit().

Yep.

>> +
>> + ret = ub953_v4l2_notifier_register(priv);
>> + if (ret) {
>> + dev_err_probe(dev, ret,
>> + "v4l2 subdev notifier register failed\n");
>> + goto err_free_state;
>> + }
>> +
>> + ret = v4l2_async_register_subdev(&priv->sd);
>> + if (ret) {
>> + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n");
>> + goto err_unreg_notif;
>> + }
>> +
>> + return 0;
>> +
>> +err_unreg_notif:
>> + ub953_v4l2_notifier_unregister(priv);
>> +err_free_state:
>> + v4l2_subdev_cleanup(&priv->sd);
>> +err_entity_cleanup:
>> + media_entity_cleanup(&priv->sd.entity);
>> +err_remove_ctrls:
>> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
>> +
>> + return ret;
>> +}
>> +
>> +static void ub953_subdev_uninit(struct ub953_data *priv)
>> +{
>> + v4l2_async_unregister_subdev(&priv->sd);
>> + ub953_v4l2_notifier_unregister(priv);
>> + v4l2_subdev_cleanup(&priv->sd);
>> + media_entity_cleanup(&priv->sd.entity);
>> + v4l2_ctrl_handler_free(&priv->ctrl_handler);
>> +}
>> +
>> +static int ub953_probe(struct i2c_client *client)
>> +{
>> + struct device *dev = &client->dev;
>> + struct ub953_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;
>
> Can this happen ?

I don't know. Probably not.

> Reviewed-by: Laurent Pinchart <[email protected]>

Thanks!

2023-01-20 10:12:39

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 7/7] media: i2c: add DS90UB953 driver

Hi Tomi,

On Fri, Jan 20, 2023 at 10:13:25AM +0200, Tomi Valkeinen wrote:
> On 20/01/2023 02:34, Laurent Pinchart wrote:
> > On Wed, Jan 18, 2023 at 02:40:31PM +0200, Tomi Valkeinen wrote:
> >> Add driver for TI DS90UB953 FPD-Link III Serializer.
> >>
> >> Signed-off-by: Tomi Valkeinen <[email protected]>
> >> ---
> >> drivers/media/i2c/Kconfig | 13 +
> >> drivers/media/i2c/Makefile | 1 +
> >> drivers/media/i2c/ds90ub953.c | 1576 +++++++++++++++++++++++++++++++++
> >> 3 files changed, 1590 insertions(+)
> >> create mode 100644 drivers/media/i2c/ds90ub953.c

[snip]

> >> diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c
> >> new file mode 100644
> >> index 000000000000..ec33e16da3d1
> >> --- /dev/null
> >> +++ b/drivers/media/i2c/ds90ub953.c
> >> @@ -0,0 +1,1576 @@

[snip]

> >> +__maybe_unused static int ub953_read_ind(struct ub953_data *priv, u8 block,
> >> + u8 reg, u8 *val)
> >
> > I'd still prefer dropping this function, but I won't insist.
> >
> >> +{
> >> + 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) {
> >> + dev_err(&priv->client->dev,
> >> + "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n",
> >> + block, reg, ret);
> >> + goto out;
> >> + }
> >
> > Would it make sense to cache the address as you do with
> > current_indirect_block, and skip this write if the address is correct
> > already ? If the device implements auto-increment of the address (I
> > haven't checked), this could save quite a few I2C writes.
>
> Yes, there's auto increment for these indirect register accesses
> (IA_AUTO_INC). There's also IA_READ bit, but I don't understand what it
> does:
>
> Indirect Access Read:
> Setting this allows generation of a read strobe to the selected register
> block upon setting of the IND_ACC_ADDR register. In auto-increment mode,
> read strobes are also asserted following a read of the IND_ACC_DATA
> register. This function is only required for blocks that need to
> pre-fetch register data.
>
> In any case, the indirect accesses are only used when configuring the
> TPG. I'm sure this can be optimized, but I question the need to do this
> optimization.
>
> The UB960 driver uses indirect accesses more often. There it might make
> a bit more sense, although, again, I don't really see why it matters in
> practice.
>
> It doesn't sound like a complex optimization, so maybe it wouldn't
> increase the chances of bugs much, but... still. Maybe something for later?

I'm fine doing this on top. As you said, it shouldn't be difficult, but
it's also not a must-have right away.

> >> +
> >> + ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v);
> >> + if (ret) {
> >> + dev_err(&priv->client->dev,
> >> + "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n",
> >> + block, reg, ret);
> >> + goto out;
> >> + }
> >> +
> >> + *val = v;
> >> +
> >> +out:
> >> + mutex_unlock(&priv->reg_lock);
> >> +
> >> + return ret;
> >> +}

[snip]

--
Regards,

Laurent Pinchart

2023-01-20 10:12:58

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 6/7] media: i2c: add DS90UB913 driver

Hi Tomi,

On Fri, Jan 20, 2023 at 09:04:39AM +0200, Tomi Valkeinen wrote:
> On 20/01/2023 02:03, Laurent Pinchart wrote:
> > On Wed, Jan 18, 2023 at 02:40:30PM +0200, Tomi Valkeinen wrote:
> >> Add driver for TI DS90UB913 FPD-Link III Serializer.
> >>
> >> Signed-off-by: Tomi Valkeinen <[email protected]>
> >> ---
> >> drivers/media/i2c/Kconfig | 13 +
> >> drivers/media/i2c/Makefile | 1 +
> >> drivers/media/i2c/ds90ub913.c | 848 ++++++++++++++++++++++++++++++++++
> >> 3 files changed, 862 insertions(+)
> >> create mode 100644 drivers/media/i2c/ds90ub913.c

[snip]

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

[snip]

> >> +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 = offset / 2;
> >> + unsigned int field_idx = offset % 2;
> >> +
> >> + return 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));
> >
> > I find the indentation weird, I would have written
> >
> > return 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));
> >
> > Your call.
>
> It's clang-format. I still haven't found out how to make it indent as
> I'd like. Actually, with kernel's clang-format settings this ends up as:
>
> return 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));

That's horrible indeed.

> But checkpatch complains about line ending with (. Adjusting the
> settings a bit I got it to indent as it is currently.

It's lovely when tools work together as expected, right ? :-)

> The indent after the line ending with | make sense, I think, but I don't
> mind your formatting either.

I don't think I've seen that style before. If you prefer it, your code,
your style :-) Just be consistent through the driver in that case.

> >> +}

[snip]

--
Regards,

Laurent Pinchart

2023-01-20 10:15:21

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hello,

On Thu, Jan 19, 2023 at 02:00:56PM +0100, Luca Ceresoli wrote:
> On Thu, 19 Jan 2023 14:22:26 +0200 Tomi Valkeinen wrote:
> > On 19/01/2023 13:35, Luca Ceresoli wrote:
> > > On Thu, 19 Jan 2023 12:09:57 +0200 Tomi Valkeinen wrote:
> > >> On 19/01/2023 10:21, Luca Ceresoli wrote:
> > >>
> > >> <snip>
> > >>
> > >>>>>>> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> > >>>>>>> +{
> > >>>>>>> + atr->priv = data;
> > >>>>>>> +}
> > >>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> > >>>>>>> +
> > >>>>>>> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> > >>>>>>> +{
> > >>>>>>> + return atr->priv;
> > >>>>>>> +}
> > >>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
> > >>>>>>
> > >>>>>> Just to be sure: Is it really _driver_ data and not _device instance_ data?
> > >>>>>
> > >>>>> It is device instance data indeed. I don't remember why this got
> > >>>>> changed, but in v3 it was i2c_atr_set_clientdata().
> > >>>>
> > >>>> It's me who was and is against calling it clientdata due to possible
> > >>>> confusion with i2c_set/get_clientdata() that is about *driver data*.
> > >>>> I missed that time the fact that this is about device instance data.
> > >>>> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?
> > >>>
> > >>> Not sure I'm following you here. The i2c_atr_set_clientdata() name was
> > >>> given for similarity with i2c_set_clientdata(). The latter wraps
> > >>> dev_set_drvdata(), which sets `struct device`->driver_data. There is
> > >>> one driver_data per each `struct device` instance, not per each driver.
> > >>> The same goes for i2c_atr_set_driver_data(): there is one priv pointer
> > >>> per each `struct i2c_atr` instance.
> > >>
> > >> I'm a bit confused. What is "driver data" and what is "device instance
> > >> data"?
> > >>
> > >> This deals with the driver's private data, where the "driver" is the
> > >> owner/creator of the i2c-atr. The i2c-atr itself doesn't have a device
> > >> (it's kind of part of the owner's device), and there's no driver in
> > >> i2c-atr.c
> > >>
> > >> I don't like "client" here, as it reminds me of i2c_client (especially
> > >> as we're in i2c context).
> > >>
> > >> What about i2c_atr_set_user_data()? Or "owner_data"?
> > >
> > > Ah, only now I got the point Andy made initially about "client" not
> > > being an appropriate word.
> > >
> > > In i2c we have:
> > >
> > > i2c_set_clientdata(struct i2c_client *client, void *data)
> > > ^^^^^^~~~~ ^^^^^^ ~~~~
> > >
> > > so "client" clearly makes sense there, now here.
> >
> > Isn't that also used by the i2c_client? A driver which handles an i2c
> > device is the "i2c client", in a sense?
> >
> > > The same logic applied here would lead to:
> > >
> > > i2c_atr_set_atrdata(struct i2c_atr *atr, void *data)
> > > ^^^~~~~ ^^^ ~~~~
> > >
> > > which makes sense but it is a ugly IMO.
> >
> > Here, I think, there's a bit of a difference to the i2c_client case, as
> > we have a separate component for the i2c-atr. Although I guess one can
> > argue that the top level driver is the ATR driver, as it handles the HW,
> > and i2c-atr.c is just a set of helpers, so... I don't know =).
> >
> > > So I think i2c_atr_get_driver_data() in this v7 makes sense, it's to
> > > set the data that the ATR driver instance needs.
> > >
> > > This is coherent with logic in spi/spi.h:
> > >
> > > spi_set_drvdata(struct spi_device *spi, void *data)
> > >
> > > except for the abbreviation ("_drvdata" vs "_driver_data").
> > >
> > > Andy, Tomi, would i2c_atr_set_drvdata() be OK for you, just like SPI
> > > does?
> >
> > Well, I'm good with the current i2c_atr_set_driver_data(). If all agrees
> > that it's "driver data", I'd rather keep it like that. I find this
> > "drvdata" style very odd. Why no underscore between drv and data? Why
> > abbreviate drv, it doesn't really help anything here?
>
> Agreed, I'm OK with either form of "driver data".

Have you considered allowing drivers to embed i2c_atr in a larger
structure, instead of forcing allocation through i2c_atr_new() ? Drivers
could then use container_of() instead of the get/set driver/device data
accessors.

--
Regards,

Laurent Pinchart

2023-01-20 14:21:11

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

Hi Laurent,

On Fri, 20 Jan 2023 11:55:21 +0200
Laurent Pinchart <[email protected]> wrote:

> Hello,
>
> On Thu, Jan 19, 2023 at 02:00:56PM +0100, Luca Ceresoli wrote:
> > On Thu, 19 Jan 2023 14:22:26 +0200 Tomi Valkeinen wrote:
> > > On 19/01/2023 13:35, Luca Ceresoli wrote:
> > > > On Thu, 19 Jan 2023 12:09:57 +0200 Tomi Valkeinen wrote:
> > > >> On 19/01/2023 10:21, Luca Ceresoli wrote:
> > > >>
> > > >> <snip>
> > > >>
> > > >>>>>>> +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data)
> > > >>>>>>> +{
> > > >>>>>>> + atr->priv = data;
> > > >>>>>>> +}
> > > >>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR);
> > > >>>>>>> +
> > > >>>>>>> +void *i2c_atr_get_driver_data(struct i2c_atr *atr)
> > > >>>>>>> +{
> > > >>>>>>> + return atr->priv;
> > > >>>>>>> +}
> > > >>>>>>> +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR);
> > > >>>>>>
> > > >>>>>> Just to be sure: Is it really _driver_ data and not _device instance_ data?
> > > >>>>>
> > > >>>>> It is device instance data indeed. I don't remember why this got
> > > >>>>> changed, but in v3 it was i2c_atr_set_clientdata().
> > > >>>>
> > > >>>> It's me who was and is against calling it clientdata due to possible
> > > >>>> confusion with i2c_set/get_clientdata() that is about *driver data*.
> > > >>>> I missed that time the fact that this is about device instance data.
> > > >>>> I dunno which name would be better in this case, i2c_atr_set/get_client_priv() ?
> > > >>>
> > > >>> Not sure I'm following you here. The i2c_atr_set_clientdata() name was
> > > >>> given for similarity with i2c_set_clientdata(). The latter wraps
> > > >>> dev_set_drvdata(), which sets `struct device`->driver_data. There is
> > > >>> one driver_data per each `struct device` instance, not per each driver.
> > > >>> The same goes for i2c_atr_set_driver_data(): there is one priv pointer
> > > >>> per each `struct i2c_atr` instance.
> > > >>
> > > >> I'm a bit confused. What is "driver data" and what is "device instance
> > > >> data"?
> > > >>
> > > >> This deals with the driver's private data, where the "driver" is the
> > > >> owner/creator of the i2c-atr. The i2c-atr itself doesn't have a device
> > > >> (it's kind of part of the owner's device), and there's no driver in
> > > >> i2c-atr.c
> > > >>
> > > >> I don't like "client" here, as it reminds me of i2c_client (especially
> > > >> as we're in i2c context).
> > > >>
> > > >> What about i2c_atr_set_user_data()? Or "owner_data"?
> > > >
> > > > Ah, only now I got the point Andy made initially about "client" not
> > > > being an appropriate word.
> > > >
> > > > In i2c we have:
> > > >
> > > > i2c_set_clientdata(struct i2c_client *client, void *data)
> > > > ^^^^^^~~~~ ^^^^^^ ~~~~
> > > >
> > > > so "client" clearly makes sense there, now here.
> > >
> > > Isn't that also used by the i2c_client? A driver which handles an i2c
> > > device is the "i2c client", in a sense?
> > >
> > > > The same logic applied here would lead to:
> > > >
> > > > i2c_atr_set_atrdata(struct i2c_atr *atr, void *data)
> > > > ^^^~~~~ ^^^ ~~~~
> > > >
> > > > which makes sense but it is a ugly IMO.
> > >
> > > Here, I think, there's a bit of a difference to the i2c_client case, as
> > > we have a separate component for the i2c-atr. Although I guess one can
> > > argue that the top level driver is the ATR driver, as it handles the HW,
> > > and i2c-atr.c is just a set of helpers, so... I don't know =).
> > >
> > > > So I think i2c_atr_get_driver_data() in this v7 makes sense, it's to
> > > > set the data that the ATR driver instance needs.
> > > >
> > > > This is coherent with logic in spi/spi.h:
> > > >
> > > > spi_set_drvdata(struct spi_device *spi, void *data)
> > > >
> > > > except for the abbreviation ("_drvdata" vs "_driver_data").
> > > >
> > > > Andy, Tomi, would i2c_atr_set_drvdata() be OK for you, just like SPI
> > > > does?
> > >
> > > Well, I'm good with the current i2c_atr_set_driver_data(). If all agrees
> > > that it's "driver data", I'd rather keep it like that. I find this
> > > "drvdata" style very odd. Why no underscore between drv and data? Why
> > > abbreviate drv, it doesn't really help anything here?
> >
> > Agreed, I'm OK with either form of "driver data".
>
> Have you considered allowing drivers to embed i2c_atr in a larger
> structure, instead of forcing allocation through i2c_atr_new() ? Drivers
> could then use container_of() instead of the get/set driver/device data
> accessors.

Off the top of my head I don't see a good reason to not do it, and it
would be nice to have indeed.

For the sake of historical discussion, I guess I didn't do initially
just because my starting point was i2c-mux where allocation is dynamic.
But i2c_mux_alloc() also takes a 'int sizeof_priv' parameter to
allocate some extra space for private driver data. I don't love that
approach but it probably makes sense for mux devices which tend to be
very simple, not for the ATR where chips are definitely complex. Indeed
embedded i2c_atr in the larger driver-specific struct seems the best
option.

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

2023-01-20 16:10:21

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On 20/01/2023 17:58, Andy Shevchenko wrote:
> On Thu, Jan 19, 2023 at 12:01:33PM +0200, Tomi Valkeinen wrote:
>> On 18/01/2023 16:23, Andy Shevchenko wrote:
>>> On Wed, Jan 18, 2023 at 02:40:25PM +0200, Tomi Valkeinen wrote:
>
> ...
>
>>>> + /* Ensure we have enough room to save the original addresses */
>>>> + if (unlikely(chan->orig_addrs_size < num)) {
>>>> + u16 *new_buf;
>>>> +
>>>> + new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);
>>>
>>> I remember that I asked why we don't use krealloc_array() here... Perhaps
>>> that we don't need to copy the old mapping table? Can we put a short comment
>>> to clarify this in the code?
>>
>> Yes, we don't care about the old data, we just require the buffer to be
>> large enough.
>>
>> I'm not sure what kind of comment you want here. Isn't this a common idiom,
>> where you have a buffer for temporary data, but you might need to resize at
>> some point if you need a larger one?
>
> Then why not krealloc_array()? That's the question I want to see the answer for
> in the comments:
>
> /* We don't care about old data, hence no realloc() */

Ok, I'll add that.

Tomi

2023-01-20 16:11:26

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 1/7] i2c: add I2C Address Translator (ATR) support

On Thu, Jan 19, 2023 at 12:01:33PM +0200, Tomi Valkeinen wrote:
> On 18/01/2023 16:23, Andy Shevchenko wrote:
> > On Wed, Jan 18, 2023 at 02:40:25PM +0200, Tomi Valkeinen wrote:

...

> > > + /* Ensure we have enough room to save the original addresses */
> > > + if (unlikely(chan->orig_addrs_size < num)) {
> > > + u16 *new_buf;
> > > +
> > > + new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL);
> >
> > I remember that I asked why we don't use krealloc_array() here... Perhaps
> > that we don't need to copy the old mapping table? Can we put a short comment
> > to clarify this in the code?
>
> Yes, we don't care about the old data, we just require the buffer to be
> large enough.
>
> I'm not sure what kind of comment you want here. Isn't this a common idiom,
> where you have a buffer for temporary data, but you might need to resize at
> some point if you need a larger one?

Then why not krealloc_array()? That's the question I want to see the answer for
in the comments:

/* We don't care about old data, hence no realloc() */

> > > + if (!new_buf)
> > > + return -ENOMEM;
> > > +
> > > + kfree(chan->orig_addrs);
> > > + chan->orig_addrs = new_buf;
> > > + chan->orig_addrs_size = num;
> > > + }

--
With Best Regards,
Andy Shevchenko


2023-01-20 16:30:27

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

On Thu, Jan 19, 2023 at 09:43:58AM +0100, Luca Ceresoli wrote:
> On Wed, 18 Jan 2023 19:43:23 +0200
> Andy Shevchenko <[email protected]> wrote:
> > On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
> > > On 18/01/2023 18:01, Andy Shevchenko wrote:
> > > > On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:

...

> > > Can you clarify what you mean here?
> > >
> > > The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
> > > The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
> > > the ATR support.
> >
> > Can't that hardware be represented as I2C adapter? In such case the ATR specifics
> > can be hidden from the client (drivers).
> >
> > I'm worrying about code duplication and other things that leak into drivers as
> > ATR callbacks.
>
> Which callbacks do you refer to? i2c_atr_ops? I don't think we can do
> without the attach/detach_client ones, it's where the driver-specific
> implementation is hooked for the generic ATR infra to call it.
>
> However now I noticed the select/deselect ops are still there. IIRC
> they are not used by any driver and in the past the plan was to just
> remove them. Tomi, do you think there is a good reason to keep them?
>
> > It might be that I didn't get how hw exactly functioning on this
> > level and why we need those callbacks.
>
> As far as "how hw exactly works", in case you haven't seen that, the
> best explanation I was able to give is in my ELCE 2019 talk, at minute
> ~22. It's a 2-3 minute watch. The slides have pointers to other talks
> and discussion.

Probably I have missed the URL in the discussion, care to resend?

--
With Best Regards,
Andy Shevchenko


2023-01-20 16:55:19

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

On 20/01/2023 18:17, Luca Ceresoli wrote:
> Hi Andy,
>
> On Fri, 20 Jan 2023 18:00:02 +0200
> Andy Shevchenko <[email protected]> wrote:
>
>> On Thu, Jan 19, 2023 at 09:43:58AM +0100, Luca Ceresoli wrote:
>>> On Wed, 18 Jan 2023 19:43:23 +0200
>>> Andy Shevchenko <[email protected]> wrote:
>>>> On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
>>>>> On 18/01/2023 18:01, Andy Shevchenko wrote:
>>>>>> On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
>>
>> ...
>>
>>>>> Can you clarify what you mean here?
>>>>>
>>>>> The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
>>>>> The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
>>>>> the ATR support.
>>>>
>>>> Can't that hardware be represented as I2C adapter? In such case the ATR specifics
>>>> can be hidden from the client (drivers).
>>>>
>>>> I'm worrying about code duplication and other things that leak into drivers as
>>>> ATR callbacks.
>>>
>>> Which callbacks do you refer to? i2c_atr_ops? I don't think we can do
>>> without the attach/detach_client ones, it's where the driver-specific
>>> implementation is hooked for the generic ATR infra to call it.
>>>
>>> However now I noticed the select/deselect ops are still there. IIRC
>>> they are not used by any driver and in the past the plan was to just
>>> remove them. Tomi, do you think there is a good reason to keep them?
>>>
>>>> It might be that I didn't get how hw exactly functioning on this
>>>> level and why we need those callbacks.
>>>
>>> As far as "how hw exactly works", in case you haven't seen that, the
>>> best explanation I was able to give is in my ELCE 2019 talk, at minute
>>> ~22. It's a 2-3 minute watch. The slides have pointers to other talks
>>> and discussion.
>>
>> Probably I have missed the URL in the discussion, care to resend?
>
> I hadn't sent any URL :)
>
> Here's the shortcut to go straight to the ATR description:
> https://youtu.be/7hLv6fYAW-E?t=1350
>
> Slides:
> https://elinux.org/images/f/fc/Ceresoli-elce2019-video-serdes-linux.pdf

A small note: the current implementation doesn't match the slides, as
the adapter is now (at least kind of) in the serializer (the "ideal
solution" in the slides.

Tomi

2023-01-20 16:57:46

by Luca Ceresoli

[permalink] [raw]
Subject: Re: [PATCH v7 0/7] i2c-atr and FPDLink

Hi Andy,

On Fri, 20 Jan 2023 18:00:02 +0200
Andy Shevchenko <[email protected]> wrote:

> On Thu, Jan 19, 2023 at 09:43:58AM +0100, Luca Ceresoli wrote:
> > On Wed, 18 Jan 2023 19:43:23 +0200
> > Andy Shevchenko <[email protected]> wrote:
> > > On Wed, Jan 18, 2023 at 07:28:20PM +0200, Tomi Valkeinen wrote:
> > > > On 18/01/2023 18:01, Andy Shevchenko wrote:
> > > > > On Wed, Jan 18, 2023 at 02:40:24PM +0200, Tomi Valkeinen wrote:
>
> ...
>
> > > > Can you clarify what you mean here?
> > > >
> > > > The i2c_clients are not aware of the i2c-atr. They are normal i2c clients.
> > > > The FPD-Link drivers are aware of the ATR, as the FPD-Link hardware contains
> > > > the ATR support.
> > >
> > > Can't that hardware be represented as I2C adapter? In such case the ATR specifics
> > > can be hidden from the client (drivers).
> > >
> > > I'm worrying about code duplication and other things that leak into drivers as
> > > ATR callbacks.
> >
> > Which callbacks do you refer to? i2c_atr_ops? I don't think we can do
> > without the attach/detach_client ones, it's where the driver-specific
> > implementation is hooked for the generic ATR infra to call it.
> >
> > However now I noticed the select/deselect ops are still there. IIRC
> > they are not used by any driver and in the past the plan was to just
> > remove them. Tomi, do you think there is a good reason to keep them?
> >
> > > It might be that I didn't get how hw exactly functioning on this
> > > level and why we need those callbacks.
> >
> > As far as "how hw exactly works", in case you haven't seen that, the
> > best explanation I was able to give is in my ELCE 2019 talk, at minute
> > ~22. It's a 2-3 minute watch. The slides have pointers to other talks
> > and discussion.
>
> Probably I have missed the URL in the discussion, care to resend?

I hadn't sent any URL :)

Here's the shortcut to go straight to the ATR description:
https://youtu.be/7hLv6fYAW-E?t=1350

Slides:
https://elinux.org/images/f/fc/Ceresoli-elce2019-video-serdes-linux.pdf

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

2023-01-20 17:10:26

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Thu, Jan 19, 2023 at 06:27:31PM +0200, Tomi Valkeinen wrote:
> On 18/01/2023 17:48, Andy Shevchenko wrote:
> > On Wed, Jan 18, 2023 at 02:40:29PM +0200, Tomi Valkeinen wrote:
> > > Add driver for TI DS90UB960 FPD-Link III Deserializer.

...

> > > +#define UB960_MIN_AEQ_STROBE_POS -7
> >
> > I believe it might need parentheses due to theoretical possibilities to be used
> > in the expression.
>
> Hmm, for my education, in which kind of expressions this could cause an
> issue?

To mine as well, surprised by 'foo = foo - - - -7;' being compilable.
But according to the Operator Precedence it's fine.

...

> Is that an important thing to restrict in the DT?

I believe so. It's a part of the idea behind YAML schema to make it possible
to validate for all possible scenarios. AFAIU that's how they can actually
see if firmware doesn't have any bugs in the description (read: DT).

> I'd rather handle these in
> the driver. There may well be HW revisions/versions or tricks not documented
> in the public docs to use other data rates. And, while this is not quite
> clear to me, the input refclk might also affect the exact data rate.
>
> With a quick glance, I don't see any other binding defining the data rates.
> I didn't see any of them even limiting the number of items.

Then I suggest to have Rob's Ack on this. If he is fine, I'll keep that
in mind.

...

> > > + if (!ret) {
> >
> > What's wrong with the positive and traditional check, i.e.
> >
> > if (ret)
> > return;
> >
> > ?
>
> Nothing, just a different style. I can change it.

The benefit is a decreased level of the indentation.

> > > + 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
> > > + */
> >
> > Multi-line comment is not in the style.
>
> True. Interesting that checkpatch didn't complain.

Because this format is the official for net subsystem.

...

> > > + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
> >
> > Post-increment is good enough, no? Ditto for other places.
>
> Sure. Pre-increment is good enough too, right? It's a simpler operation,
> although obviously they both compile into the same code. I usually use
> pre-increment when there's no particular reason to pick either one, although
> it also depends on how it looks.

Then be consistent, use it everywhere.

> > Esp. taking into account that some of them are using actually
> > post-inc. Why this difference?
>
> Possibly a different person has written that particular piece of code, or
> maybe a copy paste from somewhere.
>
> I'm personally fine with seeing both post and pre increments in code.

I'm not :-), if it's not required by the code. Pre-increment always puzzles
me: Is here anything I have to pay an additional attention to?

> > > + struct ub960_rxport *rxport = priv->rxports[nport];
> > > +
> > > + if (!rxport || !rxport->vpoc)
> > > + continue;
> > > +
> > > + ret = regulator_enable(rxport->vpoc);
> > > + if (ret)
> > > + goto err_disable_vpocs;
> > > + }

...

> > > + if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
> > > + strobe_pos > UB960_MAX_MANUAL_STROBE_POS))
> > > + return;
> >
> > Always be careful about WARN*() APIs because with a little trick they may
> > become equivalent to BUG() which is a beast that nobody likes. I.o.w.
> > you have to have justify why this is needed and can't be replaced with
> > dev_*() or analogue.
> >
> > Same for the other places with WARN*().
>
> Valid point. I think most of them here are in cases that really shouldn't
> happen. But if they do happen, I'd like to see a big loud shout about it.

...if you have time to catch it. Read about "panic_on_warn".

> The above is not a best example of this, and I think I can just drop the
> above warns, but, e.g. handling the default case for "switch
> (rxport->rx_mode)" (which shouldn't happen), I'd prefer to have a big yell
> in place rather than return silently or print a "normal" error print.
>
> Obviously WARN is not a good one if it can be toggled to become a BUG.
>
> So... I think I'll just drop most of them and probably convert the rest
> (two, actually) to dev_errs.

...

> > > + 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) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
> > > + else if (strobe_pos > 0)
> > > + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
> >
> > I'm wondering if clamp_t()/clamp_val() can be utilised here... And maybe in other
> > places in the driver.
>
> Hmm, I'm not sure how.

I can't suggest you, because it's magic to me what is going on, e.g. for
the strobe_pos < -7 case, and why abs(strobe_pos) - 6 wouldn't overflow
the bit field or whatever that uses it.

...

> > > + 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;
> >
> > A lot of magic 7 in the code. Are they all of the same semantic? Are they can
> > be converted to use a macro (including respective MIN/MAX macros)?
>
> It's related to how the value has to be encoded into the register. We keep
> the equalization level in a simple variable, but need to write it like this
> into the register. I'm not sure what I would call the magic 7 here.
>
> Then for the strobe position, we use a logical signed value between -7 and
> 7, so we have to +7 when writing that to a register. Except when using a
> manual strobe position, where the range is -13 to 13 (7+6, that's the 6 in
> ub960_rxport_set_strobe_pos()).
>
> It's rather confusing, in my opinion, but I think defines may just make this
> more confusing. The magic numbers used should always be very close to the
> registers in question, so if you know how the HW works wrt. strobe & eq,
> they should be "clear". I'll try to come up with defines that make this
> clearer, but no promises.

Obviously I disagree on the fact that it's more confusing. Consider that 7
and 7. How do I know that their semantics is the same or different? With
the name assigned it's differentiated by the name used.

...

> > > + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
> > > +
> > > + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> > > + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK);
> > > + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT;
> > > + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT;
> > > + v |= UB960_RR_AEQ_BYPASS_ENABLE; /* Enable AEQ Bypass */
> > > +
> > > + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v);
> >
> > Can't you provide ub960_rxport_update_bits() ?
>
> I could, but I think it's worse:
>
> ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
> UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_ENABLE,
> (eq_stage_1_select_value
> << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
> (eq_stage_2_select_value
> << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
> UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
> );
>
> Indenting it differently, I think it's still worse:
>
> ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
> UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
> UB960_RR_AEQ_BYPASS_ENABLE,
> (eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
> (eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
> UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
> );

You can always use temporary variables to make code better to read.
But it's up to you. Usually the R-M-W <--> vs. U is about locking or
serialisation and handling it in a separate code is better.

...

> > > + 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;
> >
> > Can this be read at once as BE16/LE16 value?
> > Or if the stream of bytes, you can use le/be16_to_cpu().
>
> I'm not sure, possibly. But is it worth it? I'd need to add new helper
> functions to read such a value.

I think it worth it to show exactly what is provided by the hardware
and how we handle it (endianess wise).

> > > + parity_errors = (v1 << 8) | v2;

...

> > > + *ok = !errors;
> >
> > How this is different to the something like returning 1 here (and 0 above)?
> > You may save some code by dropping redundant parameter.
>
> Return value 1 means there was an error when reading the register values. 0
> means we read the values, and "ok" contains a summary (ok or not) of the
> link's status.

I was expecting that error is negative, no?

> > > + return 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);
> > > + }
> >
> > You can wrap the body into readx_poll_timeout() from iopoll.h.
>
> Hmm... How would I do that? With some kind of helper structs to wrap the
> input and output parameters? Sounds very messy, but maybe I'm missing
> something.

It's me who added extra 'x', what I meant is read_poll_timeout(). It
accepts variadic arguments, i.o.w. any function with any arguments can
be provided.

...

> > > + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
> > > + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);
> >
> > Same Q, can these be unified to some kind of bulk read?
>
> Perhaps, but again, I don't see the value for creating a bulk read helper
> function for these few cases.

OK.

...

> > > + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
> > > + nport, strobe_pos, eq_level,
> > > + v1 * 1000000 + v2 * 1000000 / 256);
> >
> > Even this will be simpler with above suggestion.
>
> Hmm... How is that?

dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
nport, strobe_pos, eq_level, v * 1000000 / 256);

See?

...

> > > + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
> >
> > Post-inc?
>
> I still like pre-inc =).
>
> I see there's a mix os post and pre incs in the code. I'll align those when
> I encounter them, but I don't think it's worth the effort to methodically go
> through all of them to change them use the same style.

Kernel uses post-inc is an idiom for loops:

$ git grep -n -w '[_a-z0-9]\+++' | wc -l
148693

$ git grep -n -w ' ++[a-z0-9_]\+' | wc -l
8701

So, non-standard pattern needs to be explained.

> > > + }

...

> > > + for (unsigned int i = 0; i < 6; ++i)
> > > + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
> > > + id[6] = 0;
> >
> > If it's only for printing, the 0 is not needed...
> >
> > > + dev_info(dev, "ID '%s'\n", id);
> >
> > ...as you may put it as
> >
> > dev_info(dev, "ID: '%.*s'\n", (int)sizeof(id), id);
> >
> > (I wrote from the top of my head, maybe not compilable as is).
>
> And you think that is clearer? =)

To me, yes. Maybe because I'm familiar with that.

> I have to disagree.

Your right :-)

...

> I'm not quite fine with dropping all these DT checks. If the user happens to
> provide a DT with illegal values, the end results can be odd and the reason
> quite difficult to figure out. Isn't it much better to have a few extra
> checks in the driver?

As I said above, ask Rob, if he is fine with that I will have no objections.

...

> > > + rxport->eq.strobe_pos = strobe_pos;
> > > + if (!priv->strobe.manual)
> > > + dev_warn(dev,
> > > + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
> > > + nport);
> > > + }
> >
> > This and below looks a bit different to the above in the same function. Perhaps
> > these can be refactored to be less LoCs.
>
> Hmm what did you have in mind?
>
> > > + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
> > > + if (ret) {
> > > + if (ret != -EINVAL) {
> > > + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
> > > + nport, ret);
> > > + return ret;
> > > + }

This seems like trying to handle special cases, if you want it to be optional,
why not ignoring all errors?

> > > + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
> > > + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
> > > + eq_level);

This part is a validation of DT again, but we discussed above this.

> > > + } else {
> > > + rxport->eq.manual_eq = true;
> > > + rxport->eq.manual.eq_level = eq_level;
> > > + }

...

> > > +err_pd_gpio:
> > > + if (priv->pd_gpio)
> >
> > Dup test.
>
> What do you mean dup? You mean gpiod_set_value_cansleep can be called with
> gpio = NULL? The docs don't say this, but I guess that is the case.

Yes. This is the idea of having _optional() GPIO APIs.

> > > + gpiod_set_value_cansleep(priv->pd_gpio, 1);

...

> > > + if (priv->pd_gpio)
> > > + gpiod_set_value_cansleep(priv->pd_gpio, 1);

Ditto.

...

> > > + priv->hw_data = of_device_get_match_data(dev);
> >
> > Why of_ out of the blue?!
>
> Hmm... How do I get the data in a generic way? I'll have to study this a
> bit.

Just drop of_ :-)

priv->hw_data = device_get_match_data(dev);

> > > + if (!priv->hw_data)
> > > + return -ENODEV;

...

> > > + 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;
> >
> > GENMASK()
>
> These are not masks, but invalid values. We set these to an invalid value
> (0xff) so that when a reg access function next time checks if we are already
> targeting, e.g. a particular rxport, it will always opt to select the rxport
> by writing to the approriate registers.

Then define with respective name?

...

> > > +struct ds90ub9xx_platform_data {
> > > + u32 port;
> > > + struct i2c_atr *atr;
> > > + unsigned long bc_rate;
> >
> > Not sure why we need this to be public except, probably, atr...
>
> The port and atr are used by the serializers, for atr. The bc_rate is used
> by the serializers to figure out the clocking (they may use the FPD-Link's
> frequency internally).

The plain numbers can be passed as device properties. That's why the question
about platform data. Platform data in general is discouraged to be used in a
new code.


--
With Best Regards,
Andy Shevchenko


2023-01-25 11:16:54

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

Hi Andy,

On 20/01/2023 18:47, Andy Shevchenko wrote:

>>> Esp. taking into account that some of them are using actually
>>> post-inc. Why this difference?
>>
>> Possibly a different person has written that particular piece of code, or
>> maybe a copy paste from somewhere.
>>
>> I'm personally fine with seeing both post and pre increments in code.
>
> I'm not :-), if it's not required by the code. Pre-increment always puzzles
> me: Is here anything I have to pay an additional attention to?

That is interesting, as to me pre-increment is the simpler, more obvious
case. It's just:

v = v + 1
v

Whereas post-increment is:

temp = v
v = v + 1
temp

In any case, we're side-tracking here, I think =).

>>>> + struct ub960_rxport *rxport = priv->rxports[nport];
>>>> +
>>>> + if (!rxport || !rxport->vpoc)
>>>> + continue;
>>>> +
>>>> + ret = regulator_enable(rxport->vpoc);
>>>> + if (ret)
>>>> + goto err_disable_vpocs;
>>>> + }
>
> ...
>
>>>> + if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS ||
>>>> + strobe_pos > UB960_MAX_MANUAL_STROBE_POS))
>>>> + return;
>>>
>>> Always be careful about WARN*() APIs because with a little trick they may
>>> become equivalent to BUG() which is a beast that nobody likes. I.o.w.
>>> you have to have justify why this is needed and can't be replaced with
>>> dev_*() or analogue.
>>>
>>> Same for the other places with WARN*().
>>
>> Valid point. I think most of them here are in cases that really shouldn't
>> happen. But if they do happen, I'd like to see a big loud shout about it.
>
> ...if you have time to catch it. Read about "panic_on_warn".

Reading about WARNs on coding-style.rst, it very much sounds like the
WARNs in the driver were fine: they were in places that are never
supposed to happen. However, I have already dropped them, and I'm fine
keeping it that way.

>> The above is not a best example of this, and I think I can just drop the
>> above warns, but, e.g. handling the default case for "switch
>> (rxport->rx_mode)" (which shouldn't happen), I'd prefer to have a big yell
>> in place rather than return silently or print a "normal" error print.
>>
>> Obviously WARN is not a good one if it can be toggled to become a BUG.
>>
>> So... I think I'll just drop most of them and probably convert the rest
>> (two, actually) to dev_errs.
>
> ...
>
>>>> + 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) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY;
>>>> + else if (strobe_pos > 0)
>>>> + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY;
>>>
>>> I'm wondering if clamp_t()/clamp_val() can be utilised here... And maybe in other
>>> places in the driver.
>>
>> Hmm, I'm not sure how.
>
> I can't suggest you, because it's magic to me what is going on, e.g. for
> the strobe_pos < -7 case, and why abs(strobe_pos) - 6 wouldn't overflow
> the bit field or whatever that uses it.

Ah, I see, you were thinking of ensuring the input parameters are in
range. I thought you meant to somehow optimize the "algorithm" above
with clamps.

I don't think clamps are needed, the input parameters should always be
in range, as they are validated when reading the DT.

> ...
>
>>>> + 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;
>>>
>>> A lot of magic 7 in the code. Are they all of the same semantic? Are they can
>>> be converted to use a macro (including respective MIN/MAX macros)?
>>
>> It's related to how the value has to be encoded into the register. We keep
>> the equalization level in a simple variable, but need to write it like this
>> into the register. I'm not sure what I would call the magic 7 here.
>>
>> Then for the strobe position, we use a logical signed value between -7 and
>> 7, so we have to +7 when writing that to a register. Except when using a
>> manual strobe position, where the range is -13 to 13 (7+6, that's the 6 in
>> ub960_rxport_set_strobe_pos()).
>>
>> It's rather confusing, in my opinion, but I think defines may just make this
>> more confusing. The magic numbers used should always be very close to the
>> registers in question, so if you know how the HW works wrt. strobe & eq,
>> they should be "clear". I'll try to come up with defines that make this
>> clearer, but no promises.
>
> Obviously I disagree on the fact that it's more confusing. Consider that 7
> and 7. How do I know that their semantics is the same or different? With
> the name assigned it's differentiated by the name used.

I have added defines for these now.

> ...
>
>>>> + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v);
>>>> +
>>>> + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
>>>> + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK);
>>>> + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT;
>>>> + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT;
>>>> + v |= UB960_RR_AEQ_BYPASS_ENABLE; /* Enable AEQ Bypass */
>>>> +
>>>> + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v);
>>>
>>> Can't you provide ub960_rxport_update_bits() ?
>>
>> I could, but I think it's worse:
>>
>> ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
>> UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
>> UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
>> UB960_RR_AEQ_BYPASS_ENABLE,
>> (eq_stage_1_select_value
>> << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
>> (eq_stage_2_select_value
>> << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
>> UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
>> );
>>
>> Indenting it differently, I think it's still worse:
>>
>> ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS,
>> UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK |
>> UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK |
>> UB960_RR_AEQ_BYPASS_ENABLE,
>> (eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT) |
>> (eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT) |
>> UB960_RR_AEQ_BYPASS_ENABLE /* Enable AEQ Bypass */
>> );
>
> You can always use temporary variables to make code better to read.
> But it's up to you. Usually the R-M-W <--> vs. U is about locking or
> serialisation and handling it in a separate code is better.
>
> ...
>
>>>> + 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;
>>>
>>> Can this be read at once as BE16/LE16 value?
>>> Or if the stream of bytes, you can use le/be16_to_cpu().
>>
>> I'm not sure, possibly. But is it worth it? I'd need to add new helper
>> functions to read such a value.
>
> I think it worth it to show exactly what is provided by the hardware
> and how we handle it (endianess wise).
>
>>>> + parity_errors = (v1 << 8) | v2;
>
> ...
>
>>>> + *ok = !errors;
>>>
>>> How this is different to the something like returning 1 here (and 0 above)?
>>> You may save some code by dropping redundant parameter.
>>
>> Return value 1 means there was an error when reading the register values. 0
>> means we read the values, and "ok" contains a summary (ok or not) of the
>> link's status.
>
> I was expecting that error is negative, no?

Ah, sorry. I meant the return value is negative if there was an error.

Yes, the "ok" value could be returned as 0 or 1. But I don't usually
like combining the error code and the return value, it just makes
messier code.

>>>> + return 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);
>>>> + }
>>>
>>> You can wrap the body into readx_poll_timeout() from iopoll.h.
>>
>> Hmm... How would I do that? With some kind of helper structs to wrap the
>> input and output parameters? Sounds very messy, but maybe I'm missing
>> something.
>
> It's me who added extra 'x', what I meant is read_poll_timeout(). It
> accepts variadic arguments, i.o.w. any function with any arguments can
> be provided.

I see. Yes, I see how it would be used. read_poll_timeout() uses
sleep_range, though, and we're sleeping more than the recommended limit
of 20ms. It's also slightly messy, as we need to keep some state
(link_ok_mask, loops is optional).

> ...
>
>>>> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1);
>>>> + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2);
>>>
>>> Same Q, can these be unified to some kind of bulk read?
>>
>> Perhaps, but again, I don't see the value for creating a bulk read helper
>> function for these few cases.
>
> OK.
>
> ...
>
>>>> + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
>>>> + nport, strobe_pos, eq_level,
>>>> + v1 * 1000000 + v2 * 1000000 / 256);
>>>
>>> Even this will be simpler with above suggestion.
>>
>> Hmm... How is that?
>
> dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n",
> nport, strobe_pos, eq_level, v * 1000000 / 256);
>
> See?

Ah, of course, 256 is 1 << 8. The HW documentation just said that the
high byte is the MHz part and the low byte is the fractional part in
1/256, and I went with that without thinking about it.

This has the small complication that it overflows 32 bit variables, so I
need to use 64 bit.

I have added 16 bit register access functions.

> ...
>
>>>> + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
>>>
>>> Post-inc?
>>
>> I still like pre-inc =).
>>
>> I see there's a mix os post and pre incs in the code. I'll align those when
>> I encounter them, but I don't think it's worth the effort to methodically go
>> through all of them to change them use the same style.
>
> Kernel uses post-inc is an idiom for loops:
>
> $ git grep -n -w '[_a-z0-9]\+++' | wc -l
> 148693
>
> $ git grep -n -w ' ++[a-z0-9_]\+' | wc -l
> 8701
>
> So, non-standard pattern needs to be explained.
>
>>>> + }
>
> ...
>
>>>> + for (unsigned int i = 0; i < 6; ++i)
>>>> + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]);
>>>> + id[6] = 0;
>>>
>>> If it's only for printing, the 0 is not needed...
>>>
>>>> + dev_info(dev, "ID '%s'\n", id);
>>>
>>> ...as you may put it as
>>>
>>> dev_info(dev, "ID: '%.*s'\n", (int)sizeof(id), id);
>>>
>>> (I wrote from the top of my head, maybe not compilable as is).
>>
>> And you think that is clearer? =)
>
> To me, yes. Maybe because I'm familiar with that.
>
>> I have to disagree.
>
> Your right :-)
>
> ...
>
>> I'm not quite fine with dropping all these DT checks. If the user happens to
>> provide a DT with illegal values, the end results can be odd and the reason
>> quite difficult to figure out. Isn't it much better to have a few extra
>> checks in the driver?
>
> As I said above, ask Rob, if he is fine with that I will have no objections.
>
> ...
>
>>>> + rxport->eq.strobe_pos = strobe_pos;
>>>> + if (!priv->strobe.manual)
>>>> + dev_warn(dev,
>>>> + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n",
>>>> + nport);
>>>> + }
>>>
>>> This and below looks a bit different to the above in the same function. Perhaps
>>> these can be refactored to be less LoCs.
>>
>> Hmm what did you have in mind?
>>
>>>> + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
>>>> + if (ret) {
>>>> + if (ret != -EINVAL) {
>>>> + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
>>>> + nport, ret);
>>>> + return ret;
>>>> + }
>
> This seems like trying to handle special cases, if you want it to be optional,
> why not ignoring all errors?

I don't follow. Why would we ignore all errors even if the property is
optional? If there's a failure in reading the property, or checking if
it exists or not, surely that's an actual error to be handled, not to be
ignored?

>>>> + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
>>>> + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
>>>> + eq_level);
>
> This part is a validation of DT again, but we discussed above this.
>
>>>> + } else {
>>>> + rxport->eq.manual_eq = true;
>>>> + rxport->eq.manual.eq_level = eq_level;
>>>> + }
>
> ...
>
>>>> +err_pd_gpio:
>>>> + if (priv->pd_gpio)
>>>
>>> Dup test.
>>
>> What do you mean dup? You mean gpiod_set_value_cansleep can be called with
>> gpio = NULL? The docs don't say this, but I guess that is the case.
>
> Yes. This is the idea of having _optional() GPIO APIs.
>
>>>> + gpiod_set_value_cansleep(priv->pd_gpio, 1);
>
> ...
>
>>>> + if (priv->pd_gpio)
>>>> + gpiod_set_value_cansleep(priv->pd_gpio, 1);
>
> Ditto.
>
> ...
>
>>>> + priv->hw_data = of_device_get_match_data(dev);
>>>
>>> Why of_ out of the blue?!
>>
>> Hmm... How do I get the data in a generic way? I'll have to study this a
>> bit.
>
> Just drop of_ :-)
>
> priv->hw_data = device_get_match_data(dev);
>
>>>> + if (!priv->hw_data)
>>>> + return -ENODEV;
>
> ...
>
>>>> + 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;
>>>
>>> GENMASK()
>>
>> These are not masks, but invalid values. We set these to an invalid value
>> (0xff) so that when a reg access function next time checks if we are already
>> targeting, e.g. a particular rxport, it will always opt to select the rxport
>> by writing to the approriate registers.
>
> Then define with respective name?

I think the comment just above these should be enough:

* Initialize these to invalid values so that the first reg writes will
* configure the target.

We're just initializing the fields to an unused value and the value has
no other meaning, and is not used anywhere else. We could as well
initialize to 0, and use +1 in the relevant code to avoid 0 being a
valid value.

> ...
>
>>>> +struct ds90ub9xx_platform_data {
>>>> + u32 port;
>>>> + struct i2c_atr *atr;
>>>> + unsigned long bc_rate;
>>>
>>> Not sure why we need this to be public except, probably, atr...
>>
>> The port and atr are used by the serializers, for atr. The bc_rate is used
>> by the serializers to figure out the clocking (they may use the FPD-Link's
>> frequency internally).
>
> The plain numbers can be passed as device properties. That's why the question
> about platform data. Platform data in general is discouraged to be used in a
> new code.

Device properties, as in, coming from DT? The port could be in the DT,
but the others are not hardware properties.

Yes, I don't like using platform data. We need some way to pass
information between the drivers. Maybe a custom FPD-Link bus could do
that, but that's then going into totally new directions.

Tomi


2023-01-25 12:11:15

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Wed, Jan 25, 2023 at 01:15:34PM +0200, Tomi Valkeinen wrote:
> On 20/01/2023 18:47, Andy Shevchenko wrote:

...

> > > > Esp. taking into account that some of them are using actually
> > > > post-inc. Why this difference?
> > >
> > > Possibly a different person has written that particular piece of code, or
> > > maybe a copy paste from somewhere.
> > >
> > > I'm personally fine with seeing both post and pre increments in code.
> >
> > I'm not :-), if it's not required by the code. Pre-increment always puzzles
> > me: Is here anything I have to pay an additional attention to?
>
> That is interesting, as to me pre-increment is the simpler, more obvious
> case. It's just:
>
> v = v + 1
> v
>
> Whereas post-increment is:
>
> temp = v
> v = v + 1
> temp
>
> In any case, we're side-tracking here, I think =).

Yes, just see the statistics of use below.

...

> > > > > + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
> > > >
> > > > Post-inc?
> > >
> > > I still like pre-inc =).
> > >
> > > I see there's a mix os post and pre incs in the code. I'll align those when
> > > I encounter them, but I don't think it's worth the effort to methodically go
> > > through all of them to change them use the same style.
> >
> > Kernel uses post-inc is an idiom for loops:
> >
> > $ git grep -n -w '[_a-z0-9]\+++' | wc -l
> > 148693
> >
> > $ git grep -n -w ' ++[a-z0-9_]\+' | wc -l
> > 8701
> >
> > So, non-standard pattern needs to be explained.

> > > > > + }

...

> > > > > + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
> > > > > + if (ret) {
> > > > > + if (ret != -EINVAL) {
> > > > > + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
> > > > > + nport, ret);
> > > > > + return ret;
> > > > > + }
> >
> > This seems like trying to handle special cases, if you want it to be optional,
> > why not ignoring all errors?
>
> I don't follow. Why would we ignore all errors even if the property is
> optional? If there's a failure in reading the property, or checking if it
> exists or not, surely that's an actual error to be handled, not to be
> ignored?

What the problem to ignore them?

But if you are really pedantic about it, perhaps the proper way is to add

fwnode_property_*_optional()

APIs to the set where you take default and return 0 in case default had been
used for the absent property.

> > > > > + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
> > > > > + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
> > > > > + eq_level);
> >
> > This part is a validation of DT again, but we discussed above this.
> >
> > > > > + } else {
> > > > > + rxport->eq.manual_eq = true;
> > > > > + rxport->eq.manual.eq_level = eq_level;
> > > > > + }

...

> > > > > +struct ds90ub9xx_platform_data {
> > > > > + u32 port;
> > > > > + struct i2c_atr *atr;
> > > > > + unsigned long bc_rate;
> > > >
> > > > Not sure why we need this to be public except, probably, atr...
> > >
> > > The port and atr are used by the serializers, for atr. The bc_rate is used
> > > by the serializers to figure out the clocking (they may use the FPD-Link's
> > > frequency internally).
> >
> > The plain numbers can be passed as device properties. That's why the question
> > about platform data. Platform data in general is discouraged to be used in a
> > new code.
>
> Device properties, as in, coming from DT?

From anywhere.

> The port could be in the DT, but
> the others are not hardware properties.

Why do we need them? For example, bc_rate.

> Yes, I don't like using platform data. We need some way to pass information
> between the drivers.

Device properties allow that and targeting to remove the legacy platform data
in zillions of the drivers.

> Maybe a custom FPD-Link bus could do that, but that's
> then going into totally new directions.

--
With Best Regards,
Andy Shevchenko



2023-01-25 13:33:47

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On 25/01/2023 14:10, Andy Shevchenko wrote:
> On Wed, Jan 25, 2023 at 01:15:34PM +0200, Tomi Valkeinen wrote:
>> On 20/01/2023 18:47, Andy Shevchenko wrote:
>
> ...
>
>>>>> Esp. taking into account that some of them are using actually
>>>>> post-inc. Why this difference?
>>>>
>>>> Possibly a different person has written that particular piece of code, or
>>>> maybe a copy paste from somewhere.
>>>>
>>>> I'm personally fine with seeing both post and pre increments in code.
>>>
>>> I'm not :-), if it's not required by the code. Pre-increment always puzzles
>>> me: Is here anything I have to pay an additional attention to?
>>
>> That is interesting, as to me pre-increment is the simpler, more obvious
>> case. It's just:
>>
>> v = v + 1
>> v
>>
>> Whereas post-increment is:
>>
>> temp = v
>> v = v + 1
>> temp
>>
>> In any case, we're side-tracking here, I think =).
>
> Yes, just see the statistics of use below.
>
> ...
>
>>>>>> + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) {
>>>>>
>>>>> Post-inc?
>>>>
>>>> I still like pre-inc =).
>>>>
>>>> I see there's a mix os post and pre incs in the code. I'll align those when
>>>> I encounter them, but I don't think it's worth the effort to methodically go
>>>> through all of them to change them use the same style.
>>>
>>> Kernel uses post-inc is an idiom for loops:
>>>
>>> $ git grep -n -w '[_a-z0-9]\+++' | wc -l
>>> 148693
>>>
>>> $ git grep -n -w ' ++[a-z0-9_]\+' | wc -l
>>> 8701
>>>
>>> So, non-standard pattern needs to be explained.
>
>>>>>> + }
>
> ...
>
>>>>>> + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
>>>>>> + if (ret) {
>>>>>> + if (ret != -EINVAL) {
>>>>>> + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
>>>>>> + nport, ret);
>>>>>> + return ret;
>>>>>> + }
>>>
>>> This seems like trying to handle special cases, if you want it to be optional,
>>> why not ignoring all errors?
>>
>> I don't follow. Why would we ignore all errors even if the property is
>> optional? If there's a failure in reading the property, or checking if it
>> exists or not, surely that's an actual error to be handled, not to be
>> ignored?
>
> What the problem to ignore them?

Well, probably nothing will explode if we just ignore them. But... Why
would we ignore them?

> But if you are really pedantic about it, perhaps the proper way is to add
>
> fwnode_property_*_optional()
>
> APIs to the set where you take default and return 0 in case default had been
> used for the absent property.

Perhaps, but I don't have a default value here.

In any case, I'm not quite sure what you are arguing here. Is it just
that you don't think the error check is necessary and should be dropped?

>>>>>> + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
>>>>>> + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
>>>>>> + eq_level);
>>>
>>> This part is a validation of DT again, but we discussed above this.
>>>
>>>>>> + } else {
>>>>>> + rxport->eq.manual_eq = true;
>>>>>> + rxport->eq.manual.eq_level = eq_level;
>>>>>> + }
>
> ...
>
>>>>>> +struct ds90ub9xx_platform_data {
>>>>>> + u32 port;
>>>>>> + struct i2c_atr *atr;
>>>>>> + unsigned long bc_rate;
>>>>>
>>>>> Not sure why we need this to be public except, probably, atr...
>>>>
>>>> The port and atr are used by the serializers, for atr. The bc_rate is used
>>>> by the serializers to figure out the clocking (they may use the FPD-Link's
>>>> frequency internally).
>>>
>>> The plain numbers can be passed as device properties. That's why the question
>>> about platform data. Platform data in general is discouraged to be used in a
>>> new code.
>>
>> Device properties, as in, coming from DT?
>
> From anywhere.
>
>> The port could be in the DT, but
>> the others are not hardware properties.
>
> Why do we need them? For example, bc_rate.

The atr pointer is needed so that the serializers (ub913, ub953) can add
their i2c adapter to the deserializer's i2c-atr. The port is also needed
for that.

The bc rate (back-channel rate) is the FPD-Link back-channel rate which
the serializers use for various functionalities. At the moment only the
ub953 uses it for calculating an output clock rate.

The bc-rate could be implemented using the clock framework, even if it's
not quite a plain clock. I had that code at some point, but it felt a
bit off and as we needed the pdata for the ATR, I added the bc-rate there.

>> Yes, I don't like using platform data. We need some way to pass information
>> between the drivers.
>
> Device properties allow that and targeting to remove the legacy platform data
> in zillions of the drivers.

Do you have any pointers to guide me into the right direction? I
couldn't find anything with some grepping and googling.

If you mean "device properties" as in ACPI, and so similar to DT
properties, aren't those hardware properties? Only the port here is
about the hardware.

Tomi


2023-01-25 14:50:11

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Wed, Jan 25, 2023 at 03:33:35PM +0200, Tomi Valkeinen wrote:
> On 25/01/2023 14:10, Andy Shevchenko wrote:
> > On Wed, Jan 25, 2023 at 01:15:34PM +0200, Tomi Valkeinen wrote:
> > > On 20/01/2023 18:47, Andy Shevchenko wrote:

...

> > > > > > > + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
> > > > > > > + if (ret) {
> > > > > > > + if (ret != -EINVAL) {
> > > > > > > + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
> > > > > > > + nport, ret);
> > > > > > > + return ret;
> > > > > > > + }
> > > >
> > > > This seems like trying to handle special cases, if you want it to be optional,
> > > > why not ignoring all errors?
> > >
> > > I don't follow. Why would we ignore all errors even if the property is
> > > optional? If there's a failure in reading the property, or checking if it
> > > exists or not, surely that's an actual error to be handled, not to be
> > > ignored?
> >
> > What the problem to ignore them?
>
> Well, probably nothing will explode if we just ignore them. But... Why would
> we ignore them?
>
> > But if you are really pedantic about it, perhaps the proper way is to add
> >
> > fwnode_property_*_optional()
> >
> > APIs to the set where you take default and return 0 in case default had been
> > used for the absent property.
>
> Perhaps, but I don't have a default value here.

It's impossible. You have one. 0 is also can be default.

> In any case, I'm not quite sure what you are arguing here. Is it just that
> you don't think the error check is necessary and should be dropped?

Yes, I do not see the value of these complex error checking.
Dropping that makes it KISS. I.o.w. why do we care about errors
if the property is optional? Make it mandatory otherwise.

> > > > > > > + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
> > > > > > > + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
> > > > > > > + eq_level);
> > > >
> > > > This part is a validation of DT again, but we discussed above this.
> > > >
> > > > > > > + } else {
> > > > > > > + rxport->eq.manual_eq = true;
> > > > > > > + rxport->eq.manual.eq_level = eq_level;
> > > > > > > + }
> >
> > ...
> >
> > > > > > > +struct ds90ub9xx_platform_data {
> > > > > > > + u32 port;
> > > > > > > + struct i2c_atr *atr;
> > > > > > > + unsigned long bc_rate;
> > > > > >
> > > > > > Not sure why we need this to be public except, probably, atr...
> > > > >
> > > > > The port and atr are used by the serializers, for atr. The bc_rate is used
> > > > > by the serializers to figure out the clocking (they may use the FPD-Link's
> > > > > frequency internally).
> > > >
> > > > The plain numbers can be passed as device properties. That's why the question
> > > > about platform data. Platform data in general is discouraged to be used in a
> > > > new code.
> > >
> > > Device properties, as in, coming from DT?
> >
> > From anywhere.
> >
> > > The port could be in the DT, but
> > > the others are not hardware properties.
> >
> > Why do we need them? For example, bc_rate.
>
> The atr pointer is needed so that the serializers (ub913, ub953) can add
> their i2c adapter to the deserializer's i2c-atr. The port is also needed for
> that.
>
> The bc rate (back-channel rate) is the FPD-Link back-channel rate which the
> serializers use for various functionalities. At the moment only the ub953
> uses it for calculating an output clock rate.
>
> The bc-rate could be implemented using the clock framework, even if it's not
> quite a plain clock. I had that code at some point, but it felt a bit off
> and as we needed the pdata for the ATR, I added the bc-rate there.

And I don't see why it is not a property of the device.

> > > Yes, I don't like using platform data. We need some way to pass information
> > > between the drivers.
> >
> > Device properties allow that and targeting to remove the legacy platform data
> > in zillions of the drivers.
>
> Do you have any pointers to guide me into the right direction? I couldn't
> find anything with some grepping and googling.
>
> If you mean "device properties" as in ACPI, and so similar to DT properties,
> aren't those hardware properties? Only the port here is about the hardware.

About hardware, or PCB, or as quirks for missing DT/ACPI/any FW properties,
like clock rates.

The Linux kernel layer for that is called software nodes. The rough
approximation to see where and how it's being used can be achieved
by grepping for specific macros:

git grep -lw PROPERTY_ENTRY_.*

E.g. arch/arm/mach-tegra/board-paz00.c tegra_paz00_wifikill_init()
implementation.

--
With Best Regards,
Andy Shevchenko



2023-01-25 15:15:11

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On 25/01/2023 16:49, Andy Shevchenko wrote:
> On Wed, Jan 25, 2023 at 03:33:35PM +0200, Tomi Valkeinen wrote:
>> On 25/01/2023 14:10, Andy Shevchenko wrote:
>>> On Wed, Jan 25, 2023 at 01:15:34PM +0200, Tomi Valkeinen wrote:
>>>> On 20/01/2023 18:47, Andy Shevchenko wrote:
>
> ...
>
>>>>>>>> + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
>>>>>>>> + if (ret) {
>>>>>>>> + if (ret != -EINVAL) {
>>>>>>>> + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
>>>>>>>> + nport, ret);
>>>>>>>> + return ret;
>>>>>>>> + }
>>>>>
>>>>> This seems like trying to handle special cases, if you want it to be optional,
>>>>> why not ignoring all errors?
>>>>
>>>> I don't follow. Why would we ignore all errors even if the property is
>>>> optional? If there's a failure in reading the property, or checking if it
>>>> exists or not, surely that's an actual error to be handled, not to be
>>>> ignored?
>>>
>>> What the problem to ignore them?
>>
>> Well, probably nothing will explode if we just ignore them. But... Why would
>> we ignore them?
>>
>>> But if you are really pedantic about it, perhaps the proper way is to add
>>>
>>> fwnode_property_*_optional()
>>>
>>> APIs to the set where you take default and return 0 in case default had been
>>> used for the absent property.
>>
>> Perhaps, but I don't have a default value here.
>
> It's impossible. You have one. 0 is also can be default.

No, I either have the value ("eq-level"), or it's undefined, not used,
doesn't exist. There's no default eq-level.

>> In any case, I'm not quite sure what you are arguing here. Is it just that
>> you don't think the error check is necessary and should be dropped?
>
> Yes, I do not see the value of these complex error checking.
> Dropping that makes it KISS. I.o.w. why do we care about errors
> if the property is optional? Make it mandatory otherwise.

If the call fails, there's an error somewhere. Maybe the user tried to
define eq-level, but something is wrong. Isn't it better to catch that
error, rather than ignoring it, leaving the user wonder why things don't
work as he expects?

>>>>>>>> + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
>>>>>>>> + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
>>>>>>>> + eq_level);
>>>>>
>>>>> This part is a validation of DT again, but we discussed above this.
>>>>>
>>>>>>>> + } else {
>>>>>>>> + rxport->eq.manual_eq = true;
>>>>>>>> + rxport->eq.manual.eq_level = eq_level;
>>>>>>>> + }
>>>
>>> ...
>>>
>>>>>>>> +struct ds90ub9xx_platform_data {
>>>>>>>> + u32 port;
>>>>>>>> + struct i2c_atr *atr;
>>>>>>>> + unsigned long bc_rate;
>>>>>>>
>>>>>>> Not sure why we need this to be public except, probably, atr...
>>>>>>
>>>>>> The port and atr are used by the serializers, for atr. The bc_rate is used
>>>>>> by the serializers to figure out the clocking (they may use the FPD-Link's
>>>>>> frequency internally).
>>>>>
>>>>> The plain numbers can be passed as device properties. That's why the question
>>>>> about platform data. Platform data in general is discouraged to be used in a
>>>>> new code.
>>>>
>>>> Device properties, as in, coming from DT?
>>>
>>> From anywhere.
>>>
>>>> The port could be in the DT, but
>>>> the others are not hardware properties.
>>>
>>> Why do we need them? For example, bc_rate.
>>
>> The atr pointer is needed so that the serializers (ub913, ub953) can add
>> their i2c adapter to the deserializer's i2c-atr. The port is also needed for
>> that.
>>
>> The bc rate (back-channel rate) is the FPD-Link back-channel rate which the
>> serializers use for various functionalities. At the moment only the ub953
>> uses it for calculating an output clock rate.
>>
>> The bc-rate could be implemented using the clock framework, even if it's not
>> quite a plain clock. I had that code at some point, but it felt a bit off
>> and as we needed the pdata for the ATR, I added the bc-rate there.
>
> And I don't see why it is not a property of the device.

It with a "property of the device" you mean a hardware property, it's
not because we don't know it, it can be changed at runtime. It's not
supposed to change after probing the serializer, but up to that point it
can change.

>>>> Yes, I don't like using platform data. We need some way to pass information
>>>> between the drivers.
>>>
>>> Device properties allow that and targeting to remove the legacy platform data
>>> in zillions of the drivers.
>>
>> Do you have any pointers to guide me into the right direction? I couldn't
>> find anything with some grepping and googling.
>>
>> If you mean "device properties" as in ACPI, and so similar to DT properties,
>> aren't those hardware properties? Only the port here is about the hardware.
>
> About hardware, or PCB, or as quirks for missing DT/ACPI/any FW properties,
> like clock rates.
>
> The Linux kernel layer for that is called software nodes. The rough
> approximation to see where and how it's being used can be achieved
> by grepping for specific macros:
>
> git grep -lw PROPERTY_ENTRY_.*
>
> E.g. arch/arm/mach-tegra/board-paz00.c tegra_paz00_wifikill_init()
> implementation.

Thanks, I'll have a look. But I presume we can only pass "plain" values,
so it won't work for the ATR pointer anyway.

Tomi


2023-01-25 15:28:03

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Wed, Jan 25, 2023 at 05:14:59PM +0200, Tomi Valkeinen wrote:
> On 25/01/2023 16:49, Andy Shevchenko wrote:
> > On Wed, Jan 25, 2023 at 03:33:35PM +0200, Tomi Valkeinen wrote:
> > > On 25/01/2023 14:10, Andy Shevchenko wrote:
> > > > On Wed, Jan 25, 2023 at 01:15:34PM +0200, Tomi Valkeinen wrote:
> > > > > On 20/01/2023 18:47, Andy Shevchenko wrote:

...

> > > > > > > > > + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level);
> > > > > > > > > + if (ret) {
> > > > > > > > > + if (ret != -EINVAL) {
> > > > > > > > > + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n",
> > > > > > > > > + nport, ret);
> > > > > > > > > + return ret;
> > > > > > > > > + }
> > > > > >
> > > > > > This seems like trying to handle special cases, if you want it to be optional,
> > > > > > why not ignoring all errors?
> > > > >
> > > > > I don't follow. Why would we ignore all errors even if the property is
> > > > > optional? If there's a failure in reading the property, or checking if it
> > > > > exists or not, surely that's an actual error to be handled, not to be
> > > > > ignored?
> > > >
> > > > What the problem to ignore them?
> > >
> > > Well, probably nothing will explode if we just ignore them. But... Why would
> > > we ignore them?
> > >
> > > > But if you are really pedantic about it, perhaps the proper way is to add
> > > >
> > > > fwnode_property_*_optional()
> > > >
> > > > APIs to the set where you take default and return 0 in case default had been
> > > > used for the absent property.
> > >
> > > Perhaps, but I don't have a default value here.
> >
> > It's impossible. You have one. 0 is also can be default.
>
> No, I either have the value ("eq-level"), or it's undefined, not used,
> doesn't exist. There's no default eq-level.
>
> > > In any case, I'm not quite sure what you are arguing here. Is it just that
> > > you don't think the error check is necessary and should be dropped?
> >
> > Yes, I do not see the value of these complex error checking.
> > Dropping that makes it KISS. I.o.w. why do we care about errors
> > if the property is optional? Make it mandatory otherwise.
>
> If the call fails, there's an error somewhere. Maybe the user tried to
> define eq-level, but something is wrong. Isn't it better to catch that
> error, rather than ignoring it, leaving the user wonder why things don't
> work as he expects?

I expressed my opinion, if you and maintainers think otherwise, fine.
I don't won't spend more time on this :-)

> > > > > > > > > + } else if (eq_level > UB960_MAX_EQ_LEVEL) {
> > > > > > > > > + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport,
> > > > > > > > > + eq_level);
> > > > > >
> > > > > > This part is a validation of DT again, but we discussed above this.
> > > > > >
> > > > > > > > > + } else {
> > > > > > > > > + rxport->eq.manual_eq = true;
> > > > > > > > > + rxport->eq.manual.eq_level = eq_level;
> > > > > > > > > + }

...

> > > > > > > > > +struct ds90ub9xx_platform_data {
> > > > > > > > > + u32 port;
> > > > > > > > > + struct i2c_atr *atr;
> > > > > > > > > + unsigned long bc_rate;
> > > > > > > >
> > > > > > > > Not sure why we need this to be public except, probably, atr...
> > > > > > >
> > > > > > > The port and atr are used by the serializers, for atr. The bc_rate is used
> > > > > > > by the serializers to figure out the clocking (they may use the FPD-Link's
> > > > > > > frequency internally).
> > > > > >
> > > > > > The plain numbers can be passed as device properties. That's why the question
> > > > > > about platform data. Platform data in general is discouraged to be used in a
> > > > > > new code.
> > > > >
> > > > > Device properties, as in, coming from DT?
> > > >
> > > > From anywhere.
> > > >
> > > > > The port could be in the DT, but
> > > > > the others are not hardware properties.
> > > >
> > > > Why do we need them? For example, bc_rate.
> > >
> > > The atr pointer is needed so that the serializers (ub913, ub953) can add
> > > their i2c adapter to the deserializer's i2c-atr. The port is also needed for
> > > that.
> > >
> > > The bc rate (back-channel rate) is the FPD-Link back-channel rate which the
> > > serializers use for various functionalities. At the moment only the ub953
> > > uses it for calculating an output clock rate.
> > >
> > > The bc-rate could be implemented using the clock framework, even if it's not
> > > quite a plain clock. I had that code at some point, but it felt a bit off
> > > and as we needed the pdata for the ATR, I added the bc-rate there.
> >
> > And I don't see why it is not a property of the device.
>
> It with a "property of the device" you mean a hardware property, it's not
> because we don't know it, it can be changed at runtime. It's not supposed to
> change after probing the serializer, but up to that point it can change.

Yes, which is still property of the device, isn't it?

> > > > > Yes, I don't like using platform data. We need some way to pass information
> > > > > between the drivers.
> > > >
> > > > Device properties allow that and targeting to remove the legacy platform data
> > > > in zillions of the drivers.
> > >
> > > Do you have any pointers to guide me into the right direction? I couldn't
> > > find anything with some grepping and googling.
> > >
> > > If you mean "device properties" as in ACPI, and so similar to DT properties,
> > > aren't those hardware properties? Only the port here is about the hardware.
> >
> > About hardware, or PCB, or as quirks for missing DT/ACPI/any FW properties,
> > like clock rates.
> >
> > The Linux kernel layer for that is called software nodes. The rough
> > approximation to see where and how it's being used can be achieved
> > by grepping for specific macros:
> >
> > git grep -lw PROPERTY_ENTRY_.*
> >
> > E.g. arch/arm/mach-tegra/board-paz00.c tegra_paz00_wifikill_init()
> > implementation.
>
> Thanks, I'll have a look. But I presume we can only pass "plain" values, so
> it won't work for the ATR pointer anyway.

Yes, that's what I have told at the very beginning when answering to your
patch.

But I probably don't understand the ATR structure and what exactly we need to
pass to it, perhaps it also can be replaced with properties (note, that we have
some interesting ones that called references, which is an alternative to DT
phandle).

--
With Best Regards,
Andy Shevchenko



2023-01-26 08:42:00

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On 25/01/2023 17:27, Andy Shevchenko wrote:

>>>>>>>>>> +struct ds90ub9xx_platform_data {
>>>>>>>>>> + u32 port;
>>>>>>>>>> + struct i2c_atr *atr;
>>>>>>>>>> + unsigned long bc_rate;
>>>>>>>>>
>>>>>>>>> Not sure why we need this to be public except, probably, atr...
>>>>>>>>
>>>>>>>> The port and atr are used by the serializers, for atr. The bc_rate is used
>>>>>>>> by the serializers to figure out the clocking (they may use the FPD-Link's
>>>>>>>> frequency internally).
>>>>>>>
>>>>>>> The plain numbers can be passed as device properties. That's why the question
>>>>>>> about platform data. Platform data in general is discouraged to be used in a
>>>>>>> new code.
>>>>>>
>>>>>> Device properties, as in, coming from DT?
>>>>>
>>>>> From anywhere.
>>>>>
>>>>>> The port could be in the DT, but
>>>>>> the others are not hardware properties.
>>>>>
>>>>> Why do we need them? For example, bc_rate.
>>>>
>>>> The atr pointer is needed so that the serializers (ub913, ub953) can add
>>>> their i2c adapter to the deserializer's i2c-atr. The port is also needed for
>>>> that.
>>>>
>>>> The bc rate (back-channel rate) is the FPD-Link back-channel rate which the
>>>> serializers use for various functionalities. At the moment only the ub953
>>>> uses it for calculating an output clock rate.
>>>>
>>>> The bc-rate could be implemented using the clock framework, even if it's not
>>>> quite a plain clock. I had that code at some point, but it felt a bit off
>>>> and as we needed the pdata for the ATR, I added the bc-rate there.
>>>
>>> And I don't see why it is not a property of the device.
>>
>> It with a "property of the device" you mean a hardware property, it's not
>> because we don't know it, it can be changed at runtime. It's not supposed to
>> change after probing the serializer, but up to that point it can change.
>
> Yes, which is still property of the device, isn't it?

No, I don't see it as a property of the serializer device.

The deserializer sends messages to the serializer over the back-channel.
The rate of the back-channel is defined by the clock used for
deserializer's refclock, and internal deserializer configuration. The
serializer may use the back-channel rate for its own operations.

>>>>>> Yes, I don't like using platform data. We need some way to pass information
>>>>>> between the drivers.
>>>>>
>>>>> Device properties allow that and targeting to remove the legacy platform data
>>>>> in zillions of the drivers.
>>>>
>>>> Do you have any pointers to guide me into the right direction? I couldn't
>>>> find anything with some grepping and googling.
>>>>
>>>> If you mean "device properties" as in ACPI, and so similar to DT properties,
>>>> aren't those hardware properties? Only the port here is about the hardware.
>>>
>>> About hardware, or PCB, or as quirks for missing DT/ACPI/any FW properties,
>>> like clock rates.
>>>
>>> The Linux kernel layer for that is called software nodes. The rough
>>> approximation to see where and how it's being used can be achieved
>>> by grepping for specific macros:
>>>
>>> git grep -lw PROPERTY_ENTRY_.*
>>>
>>> E.g. arch/arm/mach-tegra/board-paz00.c tegra_paz00_wifikill_init()
>>> implementation.
>>
>> Thanks, I'll have a look. But I presume we can only pass "plain" values, so
>> it won't work for the ATR pointer anyway.
>
> Yes, that's what I have told at the very beginning when answering to your
> patch.
>
> But I probably don't understand the ATR structure and what exactly we need to
> pass to it, perhaps it also can be replaced with properties (note, that we have
> some interesting ones that called references, which is an alternative to DT
> phandle).

Well, maybe this needs a Linux bus implementation. I'm not that familiar
with implementing a bus, but I think that would make it easier to share
data between the deserializer and the serializer. A bus sounds a bit
like an overkill for a 1-to-1 connection, used by a few drivers, but
maybe it wouldn't be too much code.

Tomi


2023-01-26 10:22:00

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Thu, Jan 26, 2023 at 10:41:47AM +0200, Tomi Valkeinen wrote:
> On 25/01/2023 17:27, Andy Shevchenko wrote:

...

> > But I probably don't understand the ATR structure and what exactly we need to
> > pass to it, perhaps it also can be replaced with properties (note, that we have
> > some interesting ones that called references, which is an alternative to DT
> > phandle).
>
> Well, maybe this needs a Linux bus implementation. I'm not that familiar
> with implementing a bus, but I think that would make it easier to share data
> between the deserializer and the serializer. A bus sounds a bit like an
> overkill for a 1-to-1 connection, used by a few drivers, but maybe it
> wouldn't be too much code.

Have you looked at auxiliary bus (appeared a few releases ago in kernel)?

--
With Best Regards,
Andy Shevchenko



2023-01-26 10:51:57

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Thu, Jan 26, 2023 at 12:21:06PM +0200, Andy Shevchenko wrote:
> On Thu, Jan 26, 2023 at 10:41:47AM +0200, Tomi Valkeinen wrote:
> > On 25/01/2023 17:27, Andy Shevchenko wrote:
>
> ...
>
> > > But I probably don't understand the ATR structure and what exactly we need to
> > > pass to it, perhaps it also can be replaced with properties (note, that we have
> > > some interesting ones that called references, which is an alternative to DT
> > > phandle).
> >
> > Well, maybe this needs a Linux bus implementation. I'm not that familiar
> > with implementing a bus, but I think that would make it easier to share data
> > between the deserializer and the serializer. A bus sounds a bit like an
> > overkill for a 1-to-1 connection, used by a few drivers, but maybe it
> > wouldn't be too much code.
>
> Have you looked at auxiliary bus (appeared a few releases ago in kernel)?

As far as I understand, the auxiliary bus infrastructure is meant for
use cases where a single hardware device needs to be split into multiple
logical devices (as in struct device). Platform devices were
historically (ab)used for this, and the auxiliary bus is meant as a
cleaner solution. I'm not sure if it would be a good match here, or if
it would be considered an abuse of the auxiliary bus API.

--
Regards,

Laurent Pinchart

2023-01-27 08:24:16

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On 26/01/2023 12:51, Laurent Pinchart wrote:
> On Thu, Jan 26, 2023 at 12:21:06PM +0200, Andy Shevchenko wrote:
>> On Thu, Jan 26, 2023 at 10:41:47AM +0200, Tomi Valkeinen wrote:
>>> On 25/01/2023 17:27, Andy Shevchenko wrote:
>>
>> ...
>>
>>>> But I probably don't understand the ATR structure and what exactly we need to
>>>> pass to it, perhaps it also can be replaced with properties (note, that we have
>>>> some interesting ones that called references, which is an alternative to DT
>>>> phandle).
>>>
>>> Well, maybe this needs a Linux bus implementation. I'm not that familiar
>>> with implementing a bus, but I think that would make it easier to share data
>>> between the deserializer and the serializer. A bus sounds a bit like an
>>> overkill for a 1-to-1 connection, used by a few drivers, but maybe it
>>> wouldn't be too much code.
>>
>> Have you looked at auxiliary bus (appeared a few releases ago in kernel)?
>
> As far as I understand, the auxiliary bus infrastructure is meant for
> use cases where a single hardware device needs to be split into multiple
> logical devices (as in struct device). Platform devices were
> historically (ab)used for this, and the auxiliary bus is meant as a
> cleaner solution. I'm not sure if it would be a good match here, or if
> it would be considered an abuse of the auxiliary bus API.

The aux bus docs say "A key requirement for utilizing the auxiliary bus
is that there is no dependency on a physical bus, device, register
accesses or regmap support. These individual devices split from the core
cannot live on the platform bus as they are not physical devices that
are controlled by DT/ACPI.", which doesn't sound like a good fit.

The deserializer and serializers are currently independent devices and
drivers (the pdata is the only shared thing), but I think we may need
something better here. The devices are more tightly tied together than
"normal" video devices, in my opinion, as the serializer is fully
controlled by the deserializer (including power).

And if we ever want to implement something like power management, we
probably need something more than what we have now. Although I don't
know how that would be done, as all the peripherals behind the
serializer would also lose power...

Tomi


2023-01-27 09:15:15

by Andy Shevchenko

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On Fri, Jan 27, 2023 at 10:24:04AM +0200, Tomi Valkeinen wrote:
> On 26/01/2023 12:51, Laurent Pinchart wrote:
> > On Thu, Jan 26, 2023 at 12:21:06PM +0200, Andy Shevchenko wrote:
> > > On Thu, Jan 26, 2023 at 10:41:47AM +0200, Tomi Valkeinen wrote:
> > > > On 25/01/2023 17:27, Andy Shevchenko wrote:

...

> > > > > But I probably don't understand the ATR structure and what exactly we need to
> > > > > pass to it, perhaps it also can be replaced with properties (note, that we have
> > > > > some interesting ones that called references, which is an alternative to DT
> > > > > phandle).
> > > >
> > > > Well, maybe this needs a Linux bus implementation. I'm not that familiar
> > > > with implementing a bus, but I think that would make it easier to share data
> > > > between the deserializer and the serializer. A bus sounds a bit like an
> > > > overkill for a 1-to-1 connection, used by a few drivers, but maybe it
> > > > wouldn't be too much code.
> > >
> > > Have you looked at auxiliary bus (appeared a few releases ago in kernel)?
> >
> > As far as I understand, the auxiliary bus infrastructure is meant for
> > use cases where a single hardware device needs to be split into multiple
> > logical devices (as in struct device). Platform devices were
> > historically (ab)used for this, and the auxiliary bus is meant as a
> > cleaner solution. I'm not sure if it would be a good match here, or if
> > it would be considered an abuse of the auxiliary bus API.
>
> The aux bus docs say "A key requirement for utilizing the auxiliary bus is
> that there is no dependency on a physical bus, device, register accesses or
> regmap support. These individual devices split from the core cannot live on
> the platform bus as they are not physical devices that are controlled by
> DT/ACPI.", which doesn't sound like a good fit.

Thanks for checking!

> The deserializer and serializers are currently independent devices and
> drivers (the pdata is the only shared thing), but I think we may need
> something better here. The devices are more tightly tied together than
> "normal" video devices, in my opinion, as the serializer is fully controlled
> by the deserializer (including power).
>
> And if we ever want to implement something like power management, we
> probably need something more than what we have now. Although I don't know
> how that would be done, as all the peripherals behind the serializer would
> also lose power...

I believe you have to create a power domain for them and when such device
is added, the power domain of it should belong to the serialized.

--
With Best Regards,
Andy Shevchenko



2023-02-08 15:10:35

by Tomi Valkeinen

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

On 27/01/2023 11:15, Andy Shevchenko wrote:
> On Fri, Jan 27, 2023 at 10:24:04AM +0200, Tomi Valkeinen wrote:
>> On 26/01/2023 12:51, Laurent Pinchart wrote:
>>> On Thu, Jan 26, 2023 at 12:21:06PM +0200, Andy Shevchenko wrote:
>>>> On Thu, Jan 26, 2023 at 10:41:47AM +0200, Tomi Valkeinen wrote:
>>>>> On 25/01/2023 17:27, Andy Shevchenko wrote:
>
> ...
>
>>>>>> But I probably don't understand the ATR structure and what exactly we need to
>>>>>> pass to it, perhaps it also can be replaced with properties (note, that we have
>>>>>> some interesting ones that called references, which is an alternative to DT
>>>>>> phandle).
>>>>>
>>>>> Well, maybe this needs a Linux bus implementation. I'm not that familiar
>>>>> with implementing a bus, but I think that would make it easier to share data
>>>>> between the deserializer and the serializer. A bus sounds a bit like an
>>>>> overkill for a 1-to-1 connection, used by a few drivers, but maybe it
>>>>> wouldn't be too much code.
>>>>
>>>> Have you looked at auxiliary bus (appeared a few releases ago in kernel)?
>>>
>>> As far as I understand, the auxiliary bus infrastructure is meant for
>>> use cases where a single hardware device needs to be split into multiple
>>> logical devices (as in struct device). Platform devices were
>>> historically (ab)used for this, and the auxiliary bus is meant as a
>>> cleaner solution. I'm not sure if it would be a good match here, or if
>>> it would be considered an abuse of the auxiliary bus API.
>>
>> The aux bus docs say "A key requirement for utilizing the auxiliary bus is
>> that there is no dependency on a physical bus, device, register accesses or
>> regmap support. These individual devices split from the core cannot live on
>> the platform bus as they are not physical devices that are controlled by
>> DT/ACPI.", which doesn't sound like a good fit.
>
> Thanks for checking!
>
>> The deserializer and serializers are currently independent devices and
>> drivers (the pdata is the only shared thing), but I think we may need
>> something better here. The devices are more tightly tied together than
>> "normal" video devices, in my opinion, as the serializer is fully controlled
>> by the deserializer (including power).
>>
>> And if we ever want to implement something like power management, we
>> probably need something more than what we have now. Although I don't know
>> how that would be done, as all the peripherals behind the serializer would
>> also lose power...
>
> I believe you have to create a power domain for them and when such device
> is added, the power domain of it should belong to the serialized.

I was testing this, and got something working.

I have the deserializer introducing a separate power-domain for each RX
port, and the serializer and the sensor both refer to their port's
domain. I can see that the deserializer gets power on/off callbacks
correctly when either serializer or sensor resumes.

The problem I have now is that while the power comes from the
deserializer and is thus covered with the power domain, the sensor uses
services from the serializer (gpios, clocks, i2c bus), and the
serializer is not woken up when the sensor does runtime-pm resume (the
power domain is powered up correctly when the sensor resumes).

The serializer creates the i2c adapter to which the sensor is added, so,
afaics, there should be a child-parent relationship there. But maybe I
have something wrong there, or it just doesn't work as I imagine it
would work.

Tomi


2023-02-09 10:55:18

by Laurent Pinchart

[permalink] [raw]
Subject: Re: [PATCH v7 5/7] media: i2c: add DS90UB960 driver

Hi Tomi,

On Wed, Feb 08, 2023 at 05:10:05PM +0200, Tomi Valkeinen wrote:
> On 27/01/2023 11:15, Andy Shevchenko wrote:
> > On Fri, Jan 27, 2023 at 10:24:04AM +0200, Tomi Valkeinen wrote:
> >> On 26/01/2023 12:51, Laurent Pinchart wrote:
> >>> On Thu, Jan 26, 2023 at 12:21:06PM +0200, Andy Shevchenko wrote:
> >>>> On Thu, Jan 26, 2023 at 10:41:47AM +0200, Tomi Valkeinen wrote:
> >>>>> On 25/01/2023 17:27, Andy Shevchenko wrote:
> >
> > ...
> >
> >>>>>> But I probably don't understand the ATR structure and what exactly we need to
> >>>>>> pass to it, perhaps it also can be replaced with properties (note, that we have
> >>>>>> some interesting ones that called references, which is an alternative to DT
> >>>>>> phandle).
> >>>>>
> >>>>> Well, maybe this needs a Linux bus implementation. I'm not that familiar
> >>>>> with implementing a bus, but I think that would make it easier to share data
> >>>>> between the deserializer and the serializer. A bus sounds a bit like an
> >>>>> overkill for a 1-to-1 connection, used by a few drivers, but maybe it
> >>>>> wouldn't be too much code.
> >>>>
> >>>> Have you looked at auxiliary bus (appeared a few releases ago in kernel)?
> >>>
> >>> As far as I understand, the auxiliary bus infrastructure is meant for
> >>> use cases where a single hardware device needs to be split into multiple
> >>> logical devices (as in struct device). Platform devices were
> >>> historically (ab)used for this, and the auxiliary bus is meant as a
> >>> cleaner solution. I'm not sure if it would be a good match here, or if
> >>> it would be considered an abuse of the auxiliary bus API.
> >>
> >> The aux bus docs say "A key requirement for utilizing the auxiliary bus is
> >> that there is no dependency on a physical bus, device, register accesses or
> >> regmap support. These individual devices split from the core cannot live on
> >> the platform bus as they are not physical devices that are controlled by
> >> DT/ACPI.", which doesn't sound like a good fit.
> >
> > Thanks for checking!
> >
> >> The deserializer and serializers are currently independent devices and
> >> drivers (the pdata is the only shared thing), but I think we may need
> >> something better here. The devices are more tightly tied together than
> >> "normal" video devices, in my opinion, as the serializer is fully controlled
> >> by the deserializer (including power).
> >>
> >> And if we ever want to implement something like power management, we
> >> probably need something more than what we have now. Although I don't know
> >> how that would be done, as all the peripherals behind the serializer would
> >> also lose power...
> >
> > I believe you have to create a power domain for them and when such device
> > is added, the power domain of it should belong to the serialized.
>
> I was testing this, and got something working.

As discussed offline, I'm not sure power domains are the right tool for
this. I would model the power supplies as regulators, provided by the
deserializer, and acquired by the serializers. If the devices on the
remote side are all children of the serializer (which I think they
should be), then enabling the regulator in the PM resume handler of the
serializer should be all you need.

> I have the deserializer introducing a separate power-domain for each RX
> port, and the serializer and the sensor both refer to their port's
> domain. I can see that the deserializer gets power on/off callbacks
> correctly when either serializer or sensor resumes.
>
> The problem I have now is that while the power comes from the
> deserializer and is thus covered with the power domain, the sensor uses
> services from the serializer (gpios, clocks, i2c bus), and the
> serializer is not woken up when the sensor does runtime-pm resume (the
> power domain is powered up correctly when the sensor resumes).

Is the sensor not a child of the serializer ?

> The serializer creates the i2c adapter to which the sensor is added, so,
> afaics, there should be a child-parent relationship there. But maybe I
> have something wrong there, or it just doesn't work as I imagine it
> would work.

You can check the parent/child relationships fairly easily in sysfs.

--
Regards,

Laurent Pinchart