From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8A945FA3748 for ; Tue, 1 Nov 2022 13:21:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230271AbiKANVB (ORCPT ); Tue, 1 Nov 2022 09:21:01 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52000 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229880AbiKANU6 (ORCPT ); Tue, 1 Nov 2022 09:20:58 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C7711FD08; Tue, 1 Nov 2022 06:20:57 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DD77D158A; Tue, 1 Nov 2022 14:20:53 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308854; bh=qqlP9y4CxrbgrpwUUQtSV2m4Q9RgrkWSjW1olrXBJFA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FX+7y83Onmb73XKFQVu26+/w/AMpXDmSGEgqA/GFV5gACoI/bRG40e1LyZtPvQlTL fXTZlotT1Ejspi4Rs4fZhQeyVAN6Nip7mx8ui1nmAQ0ig3tRhidjTxNv7inrBpCndm +Nmkx4oJU6L+E7DSogpobL/nG4Iu7IDFV8CZamV0= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 1/8] i2c: core: let adapters be notified of client attach/detach Date: Tue, 1 Nov 2022 15:20:25 +0200 Message-Id: <20221101132032.1542416-2-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: Luca Ceresoli An adapter might need to know when a new device is about to be added. This will soon bee needed to implement an "I2C address translator" (ATR for short), a device that propagates I2C transactions with a different slave address (an "alias" address). An ATR driver needs to know when a slave is being added to find a suitable alias and program the device translation map. Add an attach/detach callback pair to allow adapter drivers to be notified of clients being added and removed. Signed-off-by: Luca Ceresoli Signed-off-by: Tomi Valkeinen --- drivers/i2c/i2c-core-base.c | 18 +++++++++++++++++- include/linux/i2c.h | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index b4edf10e8fd0..c8bc71b1db82 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -966,15 +966,23 @@ i2c_new_client_device(struct i2c_adapter *adap, struc= t i2c_board_info const *inf } } =20 + if (adap->attach_ops && + adap->attach_ops->attach_client && + adap->attach_ops->attach_client(adap, info, client) !=3D 0) + goto out_remove_swnode; + status =3D device_register(&client->dev); if (status) - goto out_remove_swnode; + goto out_detach_client; =20 dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); =20 return client; =20 +out_detach_client: + if (adap->attach_ops && adap->attach_ops->detach_client) + adap->attach_ops->detach_client(adap, client); out_remove_swnode: device_remove_software_node(&client->dev); out_err_put_of_node: @@ -996,9 +1004,17 @@ EXPORT_SYMBOL_GPL(i2c_new_client_device); */ void i2c_unregister_device(struct i2c_client *client) { + struct i2c_adapter *adap; + if (IS_ERR_OR_NULL(client)) return; =20 + adap =3D client->adapter; + + if (adap->attach_ops && + adap->attach_ops->detach_client) + adap->attach_ops->detach_client(adap, client); + if (client->dev.of_node) { of_node_clear_flag(client->dev.of_node, OF_POPULATED); of_node_put(client->dev.of_node); diff --git a/include/linux/i2c.h b/include/linux/i2c.h index f7c49bbdb8a1..9a385b6de388 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -584,6 +584,21 @@ struct i2c_lock_operations { void (*unlock_bus)(struct i2c_adapter *adapter, unsigned int flags); }; =20 +/** + * struct i2c_attach_operations - callbacks to notify client attach/detach + * @attach_client: Notify of new client being attached + * @detach_client: Notify of new client being detached + * + * Both ops are optional. + */ +struct i2c_attach_operations { + int (*attach_client)(struct i2c_adapter *adapter, + const struct i2c_board_info *info, + const struct i2c_client *client); + void (*detach_client)(struct i2c_adapter *adapter, + const struct i2c_client *client); +}; + /** * struct i2c_timings - I2C timing information * @bus_freq_hz: the bus frequency in Hz @@ -726,6 +741,7 @@ struct i2c_adapter { =20 /* 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; =20 --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5DA6DECAAA1 for ; Tue, 1 Nov 2022 13:21:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229528AbiKANVH (ORCPT ); Tue, 1 Nov 2022 09:21:07 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52048 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229880AbiKANVB (ORCPT ); Tue, 1 Nov 2022 09:21:01 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EB929F5BA; Tue, 1 Nov 2022 06:20:59 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 235F11643; Tue, 1 Nov 2022 14:20:55 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308856; bh=JBlah+yywOFdaVBeO1c5YpZ5zrmk8nZ9hOoraPtJRNg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uQRsz4s6h6v5KW2X+hwQVlbpAZ4g1pzET3bWDGCFcX0auYOFv87g0JkNyObxiu1il rPb4zJ9kX9xUSK/NUKNSXtqx+Pc+EunKjQWnkxj77XGJScF7Dml4WeNr/sd0yv9U1s vwb3Ke0TEw/zcWpe4WlQOWz/g7Hj+/qMJQVnsw5g= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 2/8] i2c: add I2C Address Translator (ATR) support Date: Tue, 1 Nov 2022 15:20:26 +0200 Message-Id: <20221101132032.1542416-3-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" From: Luca Ceresoli 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 Signed-off-by: Tomi Valkeinen --- Documentation/i2c/index.rst | 1 + Documentation/i2c/muxes/i2c-atr.rst | 78 +++++ MAINTAINERS | 7 + drivers/i2c/Kconfig | 9 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-atr.c | 497 ++++++++++++++++++++++++++++ include/linux/i2c-atr.h | 80 +++++ 7 files changed, 673 insertions(+) create mode 100644 Documentation/i2c/muxes/i2c-atr.rst create mode 100644 drivers/i2c/i2c-atr.c create mode 100644 include/linux/i2c-atr.h diff --git a/Documentation/i2c/index.rst b/Documentation/i2c/index.rst index 6270f1fd7d4e..3d177d4ec1d2 100644 --- a/Documentation/i2c/index.rst +++ b/Documentation/i2c/index.rst @@ -18,6 +18,7 @@ Introduction i2c-topology muxes/i2c-mux-gpio i2c-sysfs + muxes/i2c-atr =20 Writing device drivers =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D diff --git a/Documentation/i2c/muxes/i2c-atr.rst b/Documentation/i2c/muxes/= i2c-atr.rst new file mode 100644 index 000000000000..14597c9ec19b --- /dev/null +++ b/Documentation/i2c/muxes/i2c-atr.rst @@ -0,0 +1,78 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +Kernel driver i2c-atr +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Author: Luca Ceresoli + +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 t= he + 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:: + + =3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D + Client Alias + =3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D + X 0x20 + Y 0x30 + =3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D + +Transaction: + + - Slave X driver sends a transaction (on adapter B), slave address 0x10 + - ATR driver rewrites messages with address 0x20, forwards to adapter A + - Physical I2C transaction on bus A, slave address 0x20 + - ATR chip propagates transaction on bus B with address translated to 0x10 + - Slave X chip replies on bus B + - ATR chip forwards reply on bus A + - ATR driver rewrites messages with address 0x10 + - Slave X driver gets back the msgs[], with reply and address 0x10 + +Usage: + + 1. In your driver (typically in the probe function) add an ATR by + calling i2c_atr_new() passing your attach/detach callbacks + 2. When the attach callback is called pick an appropriate alias, + configure it in your chip and return the chosen alias in the + alias_id parameter + 3. When the detach callback is called, deconfigure the alias from + your chip and put it back in the pool for later usage diff --git a/MAINTAINERS b/MAINTAINERS index 379945f82a64..357de12cbca6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9568,6 +9568,13 @@ L: linux-acpi@vger.kernel.org S: Maintained F: drivers/i2c/i2c-core-acpi.c =20 +I2C ADDRESS TRANSLATOR (ATR) +M: Luca Ceresoli +L: linux-i2c@vger.kernel.org +S: Maintained +F: drivers/i2c/i2c-atr.c +F: include/linux/i2c-atr.h + I2C CONTROLLER DRIVER FOR NVIDIA GPU M: Ajay Gupta L: linux-i2c@vger.kernel.org diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 438905e2a1d0..c6d1a345ea6d 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -71,6 +71,15 @@ config I2C_MUX =20 source "drivers/i2c/muxes/Kconfig" =20 +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) +=3D i2c-core-of.o obj-$(CONFIG_I2C_SMBUS) +=3D i2c-smbus.o obj-$(CONFIG_I2C_CHARDEV) +=3D i2c-dev.o obj-$(CONFIG_I2C_MUX) +=3D i2c-mux.o +obj-$(CONFIG_I2C_ATR) +=3D i2c-atr.o obj-y +=3D algos/ busses/ muxes/ obj-$(CONFIG_I2C_STUB) +=3D i2c-stub.o obj-$(CONFIG_I2C_SLAVE_EEPROM) +=3D i2c-slave-eeprom.o diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c new file mode 100644 index 000000000000..5636bbd03b09 --- /dev/null +++ b/drivers/i2c/i2c-atr.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C Address Translator + * + * Copyright (c) 2019,2022 Luca Ceresoli + * + * Originally based on i2c-mux.c + */ + +#include +#include +#include +#include +#include +#include +#include + +/** + * struct i2c_atr_cli2alias_pair - Hold the alias assigned to a client. + * @node: List node + * @client: Pointer to the client on the child bus + * @alias: I2C alias address assigned by the driver. + * This is the address that will be used to issue I2C transactions + * on the parent (physical) bus. + */ +struct i2c_atr_cli2alias_pair { + struct list_head node; + const struct i2c_client *client; + u16 alias; +}; + +/* Data for each channel (child bus) */ +struct i2c_atr_chan { + struct i2c_adapter adap; + struct i2c_atr *atr; + u32 chan_id; + + struct list_head alias_list; + + u16 *orig_addrs; + unsigned int orig_addrs_size; + struct mutex orig_addrs_lock; /* Lock orig_addrs during xfer */ +}; + +static struct i2c_atr_cli2alias_pair * +i2c_atr_find_mapping_by_client(const struct list_head *list, + const struct i2c_client *client) +{ + struct i2c_atr_cli2alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client =3D=3D 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 =3D=3D 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 *msg= s, + int num) +{ + struct i2c_atr *atr =3D chan->atr; + static struct i2c_atr_cli2alias_pair *c2a; + int i; + + /* Ensure we have enough room to save the original addresses */ + if (unlikely(chan->orig_addrs_size < num)) { + void *new_buf =3D kmalloc_array(num, sizeof(chan->orig_addrs[0]), + GFP_KERNEL); + if (new_buf =3D=3D NULL) + return -ENOMEM; + + kfree(chan->orig_addrs); + chan->orig_addrs =3D new_buf; + chan->orig_addrs_size =3D num; + } + + for (i =3D 0; i < num; i++) { + chan->orig_addrs[i] =3D msgs[i].addr; + + c2a =3D 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 =3D 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 m= sgs[], + int num) +{ + int i; + + for (i =3D 0; i < num; i++) + msgs[i].addr =3D chan->orig_addrs[i]; +} + +static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg ms= gs[], + int num) +{ + struct i2c_atr_chan *chan =3D adap->algo_data; + struct i2c_atr *atr =3D chan->atr; + struct i2c_adapter *parent =3D atr->parent; + int ret; + + /* Switch to the right atr port */ + if (atr->ops->select) { + ret =3D atr->ops->select(atr, chan->chan_id); + if (ret < 0) + goto out_deselect; + } + + /* Translate addresses */ + mutex_lock(&chan->orig_addrs_lock); + ret =3D i2c_atr_map_msgs(chan, msgs, num); + if (ret < 0) + goto out_unlock_deselect; + + /* Perform the transfer */ + ret =3D 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 =3D adap->algo_data; + struct i2c_atr *atr =3D chan->atr; + struct i2c_adapter *parent =3D atr->parent; + struct i2c_atr_cli2alias_pair *c2a; + int ret =3D 0; + + c2a =3D 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 =3D atr->ops->select(atr, chan->chan_id); + if (!ret) + ret =3D 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 =3D adap->algo_data; + struct i2c_adapter *parent =3D chan->atr->parent; + + return parent->algo->functionality(parent); +} + +static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int fla= gs) +{ + struct i2c_atr_chan *chan =3D adapter->algo_data; + struct i2c_atr *atr =3D chan->atr; + + mutex_lock(&atr->lock); +} + +static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int f= lags) +{ + struct i2c_atr_chan *chan =3D adapter->algo_data; + struct i2c_atr *atr =3D chan->atr; + + return mutex_trylock(&atr->lock); +} + +static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int f= lags) +{ + struct i2c_atr_chan *chan =3D adapter->algo_data; + struct i2c_atr *atr =3D chan->atr; + + mutex_unlock(&atr->lock); +} + +static const struct i2c_lock_operations i2c_atr_lock_ops =3D { + .lock_bus =3D i2c_atr_lock_bus, + .trylock_bus =3D i2c_atr_trylock_bus, + .unlock_bus =3D i2c_atr_unlock_bus, +}; + +static int i2c_atr_attach_client(struct i2c_adapter *adapter, + const struct i2c_board_info *info, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan =3D adapter->algo_data; + struct i2c_atr *atr =3D chan->atr; + struct i2c_atr_cli2alias_pair *c2a; + u16 alias_id =3D 0; + int ret =3D 0; + + c2a =3D kzalloc(sizeof(*c2a), GFP_KERNEL); + if (!c2a) + return -ENOMEM; + + ret =3D atr->ops->attach_client(atr, chan->chan_id, info, client, + &alias_id); + if (ret) + goto err_free; + if (alias_id =3D=3D 0) { + ret =3D -EINVAL; + goto err_free; + } + + c2a->client =3D client; + c2a->alias =3D 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 =3D adapter->algo_data; + struct i2c_atr *atr =3D chan->atr; + struct i2c_atr_cli2alias_pair *c2a; + + atr->ops->detach_client(atr, chan->chan_id, client); + + c2a =3D i2c_atr_find_mapping_by_client(&chan->alias_list, client); + if (c2a) { + list_del(&c2a->node); + kfree(c2a); + } +} + +static const struct i2c_attach_operations i2c_atr_attach_ops =3D { + .attach_client =3D i2c_atr_attach_client, + .detach_client =3D i2c_atr_detach_client, +}; + +/** + * i2c_atr_add_adapter - Create a child ("downstream") I2C bus. + * @atr: The I2C ATR + * @chan_id: Index of the new adapter (0 .. max_adapters-1). This valu= e is + * passed to the callbacks in `struct i2c_atr_ops`. + * @bus_handle: The fwnode handle that points to the adapter's i2c + * peripherals, or NULL. + * + * After calling this function a new i2c bus will appear. Adding and + * removing devices on the downstream bus will result in calls to the + * `attach_client` and `detach_client` callbacks for the driver to assign + * an alias to the device. + * + * The adapter's fwnode is set to 'bus_handle', or if 'bus_handle' is NULL= the + * function looks for a child node whose 'reg' property matches the chan_id + * under the i2c-atr device's 'i2c-atr' node. + + * Call i2c_atr_del_adapter() to remove the adapter. + * + * Return: 0 on success, a negative error code otherwise. + */ +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct fwnode_handle *bus_handle) +{ + struct i2c_adapter *parent =3D atr->parent; + struct device *dev =3D atr->dev; + struct i2c_atr_chan *chan; + char *symlink_name; + int ret; + + if (chan_id >=3D 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 =3D kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + chan->atr =3D atr; + chan->chan_id =3D 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 =3D THIS_MODULE; + chan->adap.algo =3D &atr->algo; + chan->adap.algo_data =3D chan; + chan->adap.dev.parent =3D dev; + chan->adap.retries =3D parent->retries; + chan->adap.timeout =3D parent->timeout; + chan->adap.quirks =3D parent->quirks; + chan->adap.lock_ops =3D &i2c_atr_lock_ops; + chan->adap.attach_ops =3D &i2c_atr_attach_ops; + + if (bus_handle) { + device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle)); + } else { + struct fwnode_handle *atr_node; + struct fwnode_handle *child; + u32 reg; + + atr_node =3D device_get_named_child_node(dev, "i2c-atr"); + + fwnode_for_each_child_node(atr_node, child) { + ret =3D fwnode_property_read_u32(child, "reg", ®); + if (ret) + continue; + if (chan_id =3D=3D reg) + break; + } + + device_set_node(&chan->adap.dev, child); + fwnode_handle_put(atr_node); + } + + ret =3D i2c_add_adapter(&chan->adap); + if (ret) { + dev_err(dev, "failed to add atr-adapter %u (error=3D%d)\n", + chan_id, ret); + goto err_add_adapter; + } + + symlink_name =3D kasprintf(GFP_KERNEL, "channel-%u", chan_id); + + WARN(sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device"), + "can't create symlink to atr device\n"); + WARN(sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name), + "can't create symlink for channel %u\n", chan_id); + + kfree(symlink_name); + + dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap)); + + atr->adapter[chan_id] =3D &chan->adap; + return 0; + +err_add_adapter: + mutex_destroy(&chan->orig_addrs_lock); + kfree(chan); + return ret; +} +EXPORT_SYMBOL_GPL(i2c_atr_add_adapter); + +/** + * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by + * i2c_atr_del_adapter(). + * @atr: The I2C ATR + * @chan_id: Index of the `adapter to be removed (0 .. max_adapters-1) + */ +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id) +{ + char symlink_name[20]; + + struct i2c_adapter *adap =3D atr->adapter[chan_id]; + struct i2c_atr_chan *chan =3D adap->algo_data; + struct fwnode_handle *fwnode =3D adap->dev.fwnode; + struct device *dev =3D atr->dev; + + if (atr->adapter[chan_id] =3D=3D NULL) { + dev_err(dev, "Adapter %d does not exist\n", chan_id); + return; + } + + dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap)); + + atr->adapter[chan_id] =3D NULL; + + snprintf(symlink_name, sizeof(symlink_name), + "channel-%u", chan->chan_id); + sysfs_remove_link(&dev->kobj, symlink_name); + sysfs_remove_link(&chan->adap.dev.kobj, "atr_device"); + + i2c_del_adapter(adap); + fwnode_handle_put(fwnode); + mutex_destroy(&chan->orig_addrs_lock); + kfree(chan->orig_addrs); + kfree(chan); +} +EXPORT_SYMBOL_GPL(i2c_atr_del_adapter); + +/** + * i2c_atr_new() - Allocate and initialize an I2C ATR helper. + * @parent: The parent (upstream) adapter + * @dev: The device acting as an ATR + * @ops: Driver-specific callbacks + * @max_adapters: Maximum number of child adapters + * + * The new ATR helper is connected to the parent adapter but has no child + * adapters. Call i2c_atr_add_adapter() to add some. + * + * Call i2c_atr_delete() to remove. + * + * Return: pointer to the new ATR helper object, or ERR_PTR + */ +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters) +{ + struct i2c_atr *atr; + size_t atr_size; + + if (!ops || !ops->attach_client || !ops->detach_client) + return ERR_PTR(-EINVAL); + + atr_size =3D struct_size(atr, adapter, max_adapters); + if (atr_size =3D=3D SIZE_MAX) + return ERR_PTR(-EOVERFLOW); + + atr =3D devm_kzalloc(dev, atr_size, GFP_KERNEL); + if (!atr) + return ERR_PTR(-ENOMEM); + + mutex_init(&atr->lock); + + atr->parent =3D parent; + atr->dev =3D dev; + atr->ops =3D ops; + atr->max_adapters =3D max_adapters; + + if (parent->algo->master_xfer) + atr->algo.master_xfer =3D i2c_atr_master_xfer; + if (parent->algo->smbus_xfer) + atr->algo.smbus_xfer =3D i2c_atr_smbus_xfer; + atr->algo.functionality =3D i2c_atr_functionality; + + return atr; +} +EXPORT_SYMBOL_GPL(i2c_atr_new); + +/** + * i2c_atr_delete - Delete an I2C ATR helper. + * @atr: I2C ATR helper to be deleted. + * + * Precondition: all the adapters added with i2c_atr_add_adapter() mumst be + * removed by calling i2c_atr_del_adapter(). + */ +void i2c_atr_delete(struct i2c_atr *atr) +{ + mutex_destroy(&atr->lock); +} +EXPORT_SYMBOL_GPL(i2c_atr_delete); + +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_DESCRIPTION("I2C Address Translator"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/i2c-atr.h b/include/linux/i2c-atr.h new file mode 100644 index 000000000000..19ac2f1db96b --- /dev/null +++ b/include/linux/i2c-atr.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * I2C Address Translator + * + * Copyright (c) 2019,2022 Luca Ceresoli + * + * Based on i2c-mux.h + */ + +#ifndef _LINUX_I2C_ATR_H +#define _LINUX_I2C_ATR_H + +#include +#include +#include + +struct device; +struct i2c_atr; + +/** + * struct i2c_atr_ops - Callbacks from ATR to the device driver. + * @select: Ask the driver to select a child bus (optional) + * @deselect: Ask the driver to deselect a child bus (optional) + * @attach_client: Notify the driver of a new device connected on a child + * bus. The driver must choose an I2C alias, configure the + * hardware to use it and return it in `alias_id`. + * @detach_client: Notify the driver of a device getting disconnected. The + * driver must configure the hardware to stop using the + * alias. + * + * All these functions return 0 on success, a negative error code otherwis= e. + */ +struct i2c_atr_ops { + int (*select)(struct i2c_atr *atr, u32 chan_id); + int (*deselect)(struct i2c_atr *atr, u32 chan_id); + int (*attach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_board_info *info, + const struct i2c_client *client, u16 *alias_id); + void (*detach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client); +}; + +/** + * Helper to add I2C ATR features to a device driver. + */ +struct i2c_atr { + /* private: internal use only */ + + struct i2c_adapter *parent; + struct device *dev; + const struct i2c_atr_ops *ops; + + void *priv; + + struct i2c_algorithm algo; + struct mutex lock; + int max_adapters; + + struct i2c_adapter *adapter[0]; +}; + +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters); +void i2c_atr_delete(struct i2c_atr *atr); + +static inline void i2c_atr_set_clientdata(struct i2c_atr *atr, void *data) +{ + atr->priv =3D data; +} + +static inline void *i2c_atr_get_clientdata(struct i2c_atr *atr) +{ + return atr->priv; +} + +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct fwnode_handle *bus_np); +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id); + +#endif /* _LINUX_I2C_ATR_H */ --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4BC24FA3743 for ; Tue, 1 Nov 2022 13:21:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229993AbiKANVL (ORCPT ); Tue, 1 Nov 2022 09:21:11 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52090 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230323AbiKANVD (ORCPT ); Tue, 1 Nov 2022 09:21:03 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 0E6A0FD08; Tue, 1 Nov 2022 06:21:00 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 64CD31896; Tue, 1 Nov 2022 14:20:56 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308857; bh=NEKkHEBB4RH0Zj0qc6AixmaYtTyN8NOU/sWL3QoF3ng=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vVTr1tO7YW5IWWZf1vDbaQgegj/qLTUylNLPH1kjesEgpBdNOck3TuA74E/GpWq/0 elMhl70rRgR4dmT1TlT2+Nop2u2mAMk+Yj73RiGHD0qOjmjkh5ntWihACYV0MStWM7 jckHsF54Ayrh74Z+WW4u1K4KYrtN2TwM1XKL3dO0= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 3/8] dt-bindings: media: add bindings for TI DS90UB960 Date: Tue, 1 Nov 2022 15:20:27 +0200 Message-Id: <20221101132032.1542416-4-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add DT bindings for TI DS90UB960 FPDLink-3 Deserializer. Signed-off-by: Tomi Valkeinen --- .../bindings/media/i2c/ti,ds90ub960.yaml | 392 ++++++++++++++++++ 1 file changed, 392 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub96= 0.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml = b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml new file mode 100644 index 000000000000..4456d9b3e2c7 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml @@ -0,0 +1,392 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub960.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DS90UB9XX Family FPD-Link Deserializer Hubs + +maintainers: + - Tomi Valkeinen + +description: | + The TI DS90UB9XX devices are FPD-Link video deserializers with I2C and G= PIO + forwarding. + +properties: + compatible: + enum: + - ti,ds90ub960-q1 + - ti,ds90ub9702-q1 + + reg: + maxItems: 1 + description: + i2c addresses for the deserializer and the serializers + + reg-names: + items: + - const: main + + clocks: + maxItems: 1 + description: + Reference clock connected to the REFCLK pin. + + clock-names: + items: + - const: refclk + + powerdown-gpios: + maxItems: 1 + description: + Specifier for the GPIO connected to the PDB pin. + + i2c-alias-pool: + $ref: /schemas/types.yaml#/definitions/uint16-array + description: + i2c alias pool for remote devices. + + links: + type: object + additionalProperties: false + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + manual-strobe: + type: boolean + description: + Enable manual strobe position and EQ level + + patternProperties: + '^link@[0-9a-f]+$': + type: object + additionalProperties: false + properties: + reg: + description: The link number + maxItems: 1 + + i2c-alias: + description: | + The i2c address used for the serializer. Transactions to this + address on the i2c bus where the deserializer resides are + forwarded to the serializer. + + rx-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # RAW10 + - 1 # RAW12 HF + - 2 # RAW12 LF + - 3 # CSI2 SYNC + - 4 # CSI2 NON-SYNC + description: FPD-Link Input Mode + + cdr-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # FPD3 + - 1 # FPD4 + description: FPD-Link CDR Mode + + strobe-pos: + $ref: /schemas/types.yaml#/definitions/int32 + minimum: -13 + maximum: 13 + description: Manual strobe position, from -13 to 13 + + eq-level: + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 14 + description: Manual EQ level, from 0 to 14 + + serializer: + type: object + description: FPD-Link Serializer node + + required: + - reg + - i2c-alias + - rx-mode + - serializer + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: FPD-Link input 0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + description: FPD-Link input 1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + port@2: + $ref: /schemas/graph.yaml#/$defs/port-base + description: FPD-Link input 2 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + port@3: + $ref: /schemas/graph.yaml#/$defs/port-base + description: FPD-Link input 3 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + port@4: + $ref: /schemas/graph.yaml#/$defs/port-base + description: CSI-2 Output 0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + properties: + clock-lanes: + maxItems: 1 + + data-lanes: + minItems: 1 + maxItems: 4 + + port@5: + $ref: /schemas/graph.yaml#/$defs/port-base + description: CSI-2 Output 1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + properties: + clock-lanes: + maxItems: 1 + + data-lanes: + minItems: 1 + maxItems: 4 + +required: + - compatible + - reg + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + + i2c { + clock-frequency =3D <400000>; + #address-cells =3D <1>; + #size-cells =3D <0>; + + deser@3d { + compatible =3D "ti,ds90ub960-q1"; + + reg-names =3D "main"; + reg =3D <0x3d>; + + clock-names =3D "refclk"; + clocks =3D <&fixed_clock>; + + powerdown-gpios =3D <&pca9555 7 GPIO_ACTIVE_LOW>; + + i2c-alias-pool =3D /bits/ 16 <0x4a 0x4b 0x4c 0x4d 0x4e 0x4f>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + /* Port 0, Camera 0 */ + port@0 { + reg =3D <0>; + + ub960_fpd3_1_in: endpoint { + remote-endpoint =3D <&ub953_1_out>; + + rx-mode =3D <0>; + }; + }; + + /* Port 0, Camera 1 */ + port@1 { + reg =3D <1>; + + ub960_fpd3_2_in: endpoint { + remote-endpoint =3D <&ub913_2_out>; + + rx-mode =3D <0>; + }; + }; + + /* Port 4, CSI-2 TX */ + port@4 { + reg =3D <4>; + ds90ub960_0_csi_out: endpoint { + clock-lanes =3D <0>; + data-lanes =3D <1 2 3 4>; + link-frequencies =3D /bits/ 64 <800000000>; + remote-endpoint =3D <&csi2_phy0>; + }; + }; + }; + + links { + #address-cells =3D <1>; + #size-cells =3D <0>; + + /* Link 0 has DS90UB953 serializer and IMX390 sensor */ + + link@0 { + reg =3D <0>; + i2c-alias =3D <68>; + + rx-mode =3D <3>; + + serializer1: serializer { + compatible =3D "ti,ds90ub953-q1"; + + gpio-controller; + #gpio-cells =3D <2>; + + #clock-cells =3D <0>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + ub953_1_in: endpoint { + clock-lanes =3D <0>; + data-lanes =3D <1 2 3 4>; + remote-endpoint =3D <&sensor_1_out>; + }; + }; + + port@1 { + reg =3D <1>; + + ub953_1_out: endpoint { + remote-endpoint =3D <&ub960_fpd3_1_in>; + }; + }; + }; + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + sensor@21 { + compatible =3D "sony,imx390"; + reg =3D <0x21>; + + clocks =3D <&clk_cam_27M>; + clock-names =3D "inck"; + + xclr-gpios =3D <&serializer1 0 GPIO_ACTIVE_LOW>; + error0-gpios =3D <&serializer1 1 GPIO_ACTIVE_HIGH>; + error1-gpios =3D <&serializer1 2 GPIO_ACTIVE_HIGH>; + comready-gpios =3D <&serializer1 3 GPIO_ACTIVE_HIGH>; + + port { + sensor_1_out: endpoint { + remote-endpoint =3D <&ub953_1_in>; + }; + }; + }; + }; + }; + }; /* End of link@0 */ + + /* Link 1 has DS90UB913 serializer and OV10635 sensor */ + + link@1 { + reg =3D <1>; + i2c-alias =3D <69>; + + rx-mode =3D <0>; + + serializer2: serializer { + compatible =3D "ti,ds90ub913a-q1"; + + gpio-controller; + #gpio-cells =3D <2>; + + clocks =3D <&clk_cam_48M>; + clock-names =3D "clkin"; + + #clock-cells =3D <0>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + ub913_2_in: endpoint { + remote-endpoint =3D <&sensor_2_out>; + }; + }; + + port@1 { + reg =3D <1>; + + ub913_2_out: endpoint { + remote-endpoint =3D <&ub960_fpd3_2_in>; + }; + }; + }; + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + sensor@30 { + compatible =3D "ovti,ov10635"; + reg =3D <0x30>; + + clocks =3D <&serializer2>; + clock-names =3D "xvclk"; + + powerdown-gpios =3D <&serializer2 0 GPIO_ACTIVE_HIGH>; + + port { + sensor_2_out: endpoint { + remote-endpoint =3D <&ub913_2_in>; + hsync-active =3D <1>; + vsync-active =3D <1>; + pclk-sample =3D <0>; + bus-width =3D <10>; + }; + }; + }; + }; + }; + }; /* End of link@1 */ + }; + }; + }; +... --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C6001ECAAA1 for ; Tue, 1 Nov 2022 13:21:27 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230390AbiKANVY (ORCPT ); Tue, 1 Nov 2022 09:21:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52174 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230182AbiKANVG (ORCPT ); Tue, 1 Nov 2022 09:21:06 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2A1531A38C; Tue, 1 Nov 2022 06:21:03 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 9AB051CB4; Tue, 1 Nov 2022 14:20:57 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308858; bh=y0z/ZZE1Zs8U4VPLjOn4qeJVEuGdRKlmLTw0bzJ4lbI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QmxoOp36gJmJhPy8T1Mr4fCiEMRGXnV6cPtUDSvN8ZfG6ixFDc+qckYwmoGQnLl5a rKdMZ1LE/ZvTisuiIp9k+tvO7uJqu7dOXRRe7fXXQkHoWguixjx1tgitwj6/3MRy5S KZj0eLrPPbXD395XChA4cZd+JkjJJ4DyFEv5tjvA= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 4/8] dt-bindings: media: add bindings for TI DS90UB913 Date: Tue, 1 Nov 2022 15:20:28 +0200 Message-Id: <20221101132032.1542416-5-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add DT bindings for TI DS90UB913 FPDLink-3 Serializer. Signed-off-by: Tomi Valkeinen --- .../bindings/media/i2c/ti,ds90ub913.yaml | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub91= 3.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml = b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml new file mode 100644 index 000000000000..1e0d66704968 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml @@ -0,0 +1,127 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DS90UB913 FPD-Link 3 Serializer + +maintainers: + - Tomi Valkeinen + +description: | + The TI DS90UB913 is an FPD-Link 3 video serializer for parallel video. + +properties: + compatible: + enum: + - ti,ds90ub913a-q1 + + '#gpio-cells': + const: 2 + + gpio-controller: true + + clocks: + maxItems: 1 + description: + Reference clock connected to the CLKIN pin. + + clock-names: + items: + - const: clkin + + '#clock-cells': + const: 0 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: CSI-2 input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + description: FPD-Link 3 output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + i2c: + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + +required: + - compatible + - '#gpio-cells' + - gpio-controller + - '#clock-cells' + - ports + +additionalProperties: false + +examples: + - | + #include + + serializer { + compatible =3D "ti,ds90ub913a-q1"; + + gpio-controller; + #gpio-cells =3D <2>; + + clocks =3D <&clk_cam_48M>; + clock-names =3D "clkin"; + + #clock-cells =3D <0>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + ub913_in: endpoint { + remote-endpoint =3D <&sensor_out>; + }; + }; + + port@1 { + reg =3D <1>; + endpoint { + remote-endpoint =3D <&deser_fpd_in>; + }; + }; + }; + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + sensor@37 { + compatible =3D "ovti,ov10635"; + reg =3D <0x37>; + + clocks =3D <&fixed_clock>; + clock-names =3D "xvclk"; + + reset-gpios =3D <&gpio4 17 GPIO_ACTIVE_HIGH>; + powerdown-gpios =3D <&gpio5 11 GPIO_ACTIVE_HIGH>; + + port { + sensor_out: endpoint { + remote-endpoint =3D <&ub913_in>; + }; + }; + }; + }; + }; +... --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B6E19FA3743 for ; Tue, 1 Nov 2022 13:21:33 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229945AbiKANVb (ORCPT ); Tue, 1 Nov 2022 09:21:31 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52202 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230343AbiKANVH (ORCPT ); Tue, 1 Nov 2022 09:21:07 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 872841182D; Tue, 1 Nov 2022 06:21:04 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id DA8D33307; Tue, 1 Nov 2022 14:20:58 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308859; bh=WHz0ZfQ7Bf+utOyail1PxyTVN4OPrlob7z8JQ6wr28Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=S2lbDvmXu5Q/ByfvaKDS+y6KcHgFg6twnFBLEEzFRzOFIAJsMBvFsMjL/jVbxCxgh qxvuMbUCpQQXEpkXraI1er5S+z28D4k7NAPmmaYubA8/McowRunpr27E2XtDmrXk7k YubE/qkUS2CDRKOAr5dLEAaXoTIgTXRoFeoCITMI= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 5/8] dt-bindings: media: add bindings for TI DS90UB953 Date: Tue, 1 Nov 2022 15:20:29 +0200 Message-Id: <20221101132032.1542416-6-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add DT bindings for TI DS90UB953 FPDLink-3 Serializer. Signed-off-by: Tomi Valkeinen --- .../bindings/media/i2c/ti,ds90ub953.yaml | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub95= 3.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml = b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml new file mode 100644 index 000000000000..355f7d6a19fe --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml @@ -0,0 +1,120 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub953.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DS90UB953 FPD-Link 3 Serializer + +maintainers: + - Tomi Valkeinen + +description: | + The TI DS90UB953 is an FPD-Link 3 video serializer for MIPI CSI-2. + +properties: + compatible: + enum: + - ti,ds90ub953-q1 + - ti,ds90ub971-q1 + + '#gpio-cells': + const: 2 + + gpio-controller: true + + '#clock-cells': + const: 0 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + description: CSI-2 input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + description: FPD-Link 3 output port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + + i2c: + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + +required: + - compatible + - '#gpio-cells' + - gpio-controller + - '#clock-cells' + - ports + +additionalProperties: false + +examples: + - | + #include + + serializer { + compatible =3D "ti,ds90ub953-q1"; + + gpio-controller; + #gpio-cells =3D <2>; + + #clock-cells =3D <0>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + + port@0 { + reg =3D <0>; + ub953_in: endpoint { + clock-lanes =3D <0>; + data-lanes =3D <1 2 3 4>; + remote-endpoint =3D <&sensor_out>; + }; + }; + + port@1 { + reg =3D <1>; + endpoint { + remote-endpoint =3D <&deser_fpd_in>; + }; + }; + }; + + i2c { + #address-cells =3D <1>; + #size-cells =3D <0>; + + sensor@21 { + compatible =3D "sony,imx390"; + reg =3D <0x21>; + + clocks =3D <&clk_cam_27M>; + clock-names =3D "inck"; + + xclr-gpios =3D <&serializer 0 GPIO_ACTIVE_LOW>; + error0-gpios =3D <&serializer 1 GPIO_ACTIVE_HIGH>; + error1-gpios =3D <&serializer 2 GPIO_ACTIVE_HIGH>; + comready-gpios =3D <&serializer 3 GPIO_ACTIVE_HIGH>; + + port { + sensor_out: endpoint { + remote-endpoint =3D <&ub953_in>; + }; + }; + }; + }; + }; +... --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B2D3DC433FE for ; Tue, 1 Nov 2022 13:21:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230242AbiKANVi (ORCPT ); Tue, 1 Nov 2022 09:21:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52188 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230372AbiKANVV (ORCPT ); Tue, 1 Nov 2022 09:21:21 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 62F9512AB8; Tue, 1 Nov 2022 06:21:05 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 360EA330C; Tue, 1 Nov 2022 14:21:00 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308861; bh=YFFuIWqs2aYwS0fJwW055Z1+fPlZH+uxesXjVSpRBCA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=iIw526ZZw7RAOsS66f76HeT0lsnl/RSce4+nFkw3Cq28rCxYPhpGfkfPFx+a9A07z z636PH6BwK7mvl0g2JoSG9mKT/tfn0RUpTKkcLsuYd6vPfTbnEGLtXK9PtqYPDuS9B uwsAzT2Y+avATBfGECQ6lkKmUt7UDbwck2HaVXKU= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 6/8] media: i2c: add DS90UB960 driver Date: Tue, 1 Nov 2022 15:20:30 +0200 Message-Id: <20221101132032.1542416-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add driver for TI DS90UB960 FPDLink-3 Deserializer. Signed-off-by: Tomi Valkeinen --- drivers/media/i2c/Kconfig | 16 + drivers/media/i2c/Makefile | 2 + drivers/media/i2c/ds90ub960.c | 4198 +++++++++++++++++++++++++++++++++ include/media/i2c/ds90ub9xx.h | 16 + 4 files changed, 4232 insertions(+) create mode 100644 drivers/media/i2c/ds90ub960.c create mode 100644 include/media/i2c/ds90ub9xx.h diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..2a78889e3487 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1595,4 +1595,20 @@ config VIDEO_THS7303 =20 endmenu =20 +# +# Video serializers and deserializers (e.g. FPDLink) +# + +menu "Video serializers and deserializers" + +config VIDEO_DS90UB960 + tristate "TI DS90UB960 Deserializer" + depends on OF_GPIO + select I2C_ATR + help + Device driver for the Texas Instruments DS90UB960 + FPD-Link III Deserializer + +endmenu + endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..2735b00437bb 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -142,3 +142,5 @@ obj-$(CONFIG_VIDEO_VPX3220) +=3D vpx3220.o obj-$(CONFIG_VIDEO_VS6624) +=3D vs6624.o obj-$(CONFIG_VIDEO_WM8739) +=3D wm8739.o obj-$(CONFIG_VIDEO_WM8775) +=3D wm8775.o +obj-$(CONFIG_VIDEO_DS90UB960) +=3D ds90ub960.o + diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c new file mode 100644 index 000000000000..c653474b5efc --- /dev/null +++ b/drivers/media/i2c/ds90ub960.c @@ -0,0 +1,4198 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB960-Q1 video deserializer + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2022 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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_ALIAS= ES) + +#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) (UB96= 0_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_* =3D Shared register + * UB960_RR_* =3D FPD-Link RX, per-port paged register + * UB960_TR_* =3D CSI-2 TX, per-port paged register + * UB960_XR_* =3D Reserved register + * UB960_IR_* =3D Indirect register + */ + +#define UB960_SR_I2C_DEV_ID 0x00 +#define UB960_SR_RESET 0x01 +#define UB960_SR_GEN_CONFIG 0x02 +#define UB960_SR_REV_MASK 0x03 +#define UB960_SR_DEVICE_STS 0x04 +#define UB960_SR_PAR_ERR_THOLD_HI 0x05 +#define UB960_SR_PAR_ERR_THOLD_LO 0x06 +#define UB960_SR_BCC_WDOG_CTL 0x07 +#define UB960_SR_I2C_CTL1 0x08 +#define UB960_SR_I2C_CTL2 0x09 +#define UB960_SR_SCL_HIGH_TIME 0x0A +#define UB960_SR_SCL_LOW_TIME 0x0B +#define UB960_SR_RX_PORT_CTL 0x0C +#define UB960_SR_IO_CTL 0x0D +#define UB960_SR_GPIO_PIN_STS 0x0E +#define UB960_SR_GPIO_INPUT_CTL 0x0F +#define UB960_SR_GPIO_PIN_CTL(n) (0x10 + (n)) /* n < UB960_NUM_GPIOS */ +#define UB960_SR_FS_CTL 0x18 +#define UB960_SR_FS_HIGH_TIME_1 0x19 +#define UB960_SR_FS_HIGH_TIME_0 0x1A +#define UB960_SR_FS_LOW_TIME_1 0x1B +#define UB960_SR_FS_LOW_TIME_0 0x1C +#define UB960_SR_MAX_FRM_HI 0x1D +#define UB960_SR_MAX_FRM_LO 0x1E +#define UB960_SR_CSI_PLL_CTL 0x1F + +#define UB960_SR_FWD_CTL1 0x20 +#define UB960_SR_FWD_CTL1_PORT_DIS(n) BIT((n) + 4) + +#define UB960_SR_FWD_CTL2 0x21 +#define UB960_SR_FWD_STS 0x22 + +#define UB960_SR_INTERRUPT_CTL 0x23 +#define UB960_SR_INTERRUPT_CTL_INT_EN BIT(7) +#define UB960_SR_INTERRUPT_CTL_IE_CSI_TX0 BIT(4) +#define UB960_SR_INTERRUPT_CTL_IE_RX(n) BIT((n)) /* rxport[n] IRQ */ +#define UB960_SR_INTERRUPT_CTL_ALL 0x83 /* TODO 0x93 to enable CSI */ + +#define UB960_SR_INTERRUPT_STS 0x24 +#define UB960_SR_INTERRUPT_STS_INT BIT(7) +#define UB960_SR_INTERRUPT_STS_IS_CSI_TX(n) BIT(4 + (n)) /* txport[n] IRQ = */ +#define UB960_SR_INTERRUPT_STS_IS_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define UB960_SR_TS_CONFIG 0x25 +#define UB960_SR_TS_CONTROL 0x26 +#define UB960_SR_TS_LINE_HI 0x27 +#define UB960_SR_TS_LINE_LO 0x28 +#define UB960_SR_TS_STATUS 0x29 +#define UB960_SR_TIMESTAMP_P0_HI 0x2A +#define UB960_SR_TIMESTAMP_P0_LO 0x2B +#define UB960_SR_TIMESTAMP_P1_HI 0x2C +#define UB960_SR_TIMESTAMP_P1_LO 0x2D + +#define UB960_SR_CSI_PORT_SEL 0x32 + +#define UB960_TR_CSI_CTL 0x33 +#define UB960_TR_CSI_CTL_CSI_CAL_EN BIT(6) +#define UB960_TR_CSI_CTL_CSI_ENABLE BIT(0) + +#define UB960_TR_CSI_CTL2 0x34 +#define UB960_TR_CSI_STS 0x35 +#define UB960_TR_CSI_TX_ICR 0x36 + +#define UB960_TR_CSI_TX_ISR 0x37 +#define UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR BIT(3) +#define UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR BIT(1) + +#define UB960_TR_CSI_TEST_CTL 0x38 +#define UB960_TR_CSI_TEST_PATT_HI 0x39 +#define UB960_TR_CSI_TEST_PATT_LO 0x3A + +#define UB960_XR_SFILTER_CFG 0x41 +#define UB960_XR_AEQ_CTL1 0x42 +#define UB960_XR_AEQ_ERR_THOLD 0x43 + +#define UB960_RR_BCC_ERR_CTL 0x46 +#define UB960_RR_BCC_STATUS 0x47 +#define UB960_RR_BCC_STATUS_SEQ_ERROR BIT(5) +#define UB960_RR_BCC_STATUS_MASTER_ERR BIT(4) +#define UB960_RR_BCC_STATUS_MASTER_TO BIT(3) +#define UB960_RR_BCC_STATUS_SLAVE_ERR BIT(2) +#define UB960_RR_BCC_STATUS_SLAVE_TO BIT(1) +#define UB960_RR_BCC_STATUS_RESP_ERR BIT(0) + +#define UB960_RR_FPD3_CAP 0x4A +#define UB960_RR_RAW_EMBED_DTYPE 0x4B + +#define UB960_SR_FPD3_PORT_SEL 0x4C + +#define UB960_RR_RX_PORT_STS1 0x4D +#define UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR BIT(5) +#define UB960_RR_RX_PORT_STS1_LOCK_STS_CHG BIT(4) +#define UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR BIT(3) +#define UB960_RR_RX_PORT_STS1_PARITY_ERROR BIT(2) +#define UB960_RR_RX_PORT_STS1_PORT_PASS BIT(1) +#define UB960_RR_RX_PORT_STS1_LOCK_STS BIT(0) + +#define UB960_RR_RX_PORT_STS2 0x4E +#define UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE BIT(7) +#define UB960_RR_RX_PORT_STS2_LINE_LEN_CHG BIT(6) +#define UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR BIT(5) +#define UB960_RR_RX_PORT_STS2_BUFFER_ERROR BIT(4) +#define UB960_RR_RX_PORT_STS2_CSI_ERROR BIT(3) +#define UB960_RR_RX_PORT_STS2_FREQ_STABLE BIT(2) +#define UB960_RR_RX_PORT_STS2_CABLE_FAULT BIT(1) +#define UB960_RR_RX_PORT_STS2_LINE_CNT_CHG BIT(0) + +#define UB960_RR_RX_FREQ_HIGH 0x4F +#define UB960_RR_RX_FREQ_LOW 0x50 +#define UB960_RR_SENSOR_STS_0 0x51 +#define UB960_RR_SENSOR_STS_1 0x52 +#define UB960_RR_SENSOR_STS_2 0x53 +#define UB960_RR_SENSOR_STS_3 0x54 +#define UB960_RR_RX_PAR_ERR_HI 0x55 +#define UB960_RR_RX_PAR_ERR_LO 0x56 +#define UB960_RR_BIST_ERR_COUNT 0x57 + +#define UB960_RR_BCC_CONFIG 0x58 +#define UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH BIT(6) + +#define UB960_RR_DATAPATH_CTL1 0x59 +#define UB960_RR_DATAPATH_CTL2 0x5A +#define UB960_RR_SER_ID 0x5B +#define UB960_RR_SER_ALIAS_ID 0x5C + +/* For these two register sets: n < UB960_MAX_PORT_ALIASES */ +#define UB960_RR_SLAVE_ID(n) (0x5D + (n)) +#define UB960_RR_SLAVE_ALIAS(n) (0x65 + (n)) + +#define UB960_RR_PORT_CONFIG 0x6D +#define UB960_RR_BC_GPIO_CTL(n) (0x6E + (n)) /* n < 2 */ +#define UB960_RR_RAW10_ID 0x70 +#define UB960_RR_RAW12_ID 0x71 +#define UB960_RR_CSI_VC_MAP 0x72 +#define UB960_RR_LINE_COUNT_HI 0x73 +#define UB960_RR_LINE_COUNT_LO 0x74 +#define UB960_RR_LINE_LEN_1 0x75 +#define UB960_RR_LINE_LEN_0 0x76 +#define UB960_RR_FREQ_DET_CTL 0x77 +#define UB960_RR_MAILBOX_1 0x78 +#define UB960_RR_MAILBOX_2 0x79 + +#define UB960_RR_CSI_RX_STS 0x7A +#define UB960_RR_CSI_RX_STS_LENGTH_ERR BIT(3) +#define UB960_RR_CSI_RX_STS_CKSUM_ERR BIT(2) +#define UB960_RR_CSI_RX_STS_ECC2_ERR BIT(1) +#define UB960_RR_CSI_RX_STS_ECC1_ERR BIT(0) + +#define UB960_RR_CSI_ERR_COUNTER 0x7B +#define UB960_RR_PORT_CONFIG2 0x7C +#define UB960_RR_PORT_PASS_CTL 0x7D +#define UB960_RR_SEN_INT_RISE_CTL 0x7E +#define UB960_RR_SEN_INT_FALL_CTL 0x7F + +#define UB960_SR_CSI_FRAME_COUNT_HI(n) (0x90 + 8 * (n)) +#define UB960_SR_CSI_FRAME_COUNT_LO(n) (0x91 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_HI(n) (0x92 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_LO(n) (0x93 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_HI(n) (0x94 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_LO(n) (0x95 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_HI(n) (0x96 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_LO(n) (0x97 + 8 * (n)) + +#define UB960_XR_REFCLK_FREQ 0xA5 /* UB960 */ + +#define UB960_RR_VC_ID_MAP(x) (0xa0 + (x)) /* UB9702 */ + +#define UB960_SR_IND_ACC_CTL 0xB0 +#define UB960_SR_IND_ACC_CTL_IA_AUTO_INC BIT(1) + +#define UB960_SR_IND_ACC_ADDR 0xB1 +#define UB960_SR_IND_ACC_DATA 0xB2 +#define UB960_SR_BIST_CONTROL 0xB3 +#define UB960_SR_MODE_IDX_STS 0xB8 +#define UB960_SR_LINK_ERROR_COUNT 0xB9 +#define UB960_SR_FPD3_ENC_CTL 0xBA +#define UB960_SR_FV_MIN_TIME 0xBC +#define UB960_SR_GPIO_PD_CTL 0xBE + +#define UB960_SR_FPD_RATE_CFG 0xc2 /* UB9702 */ +#define UB960_SR_CSI_PLL_DIV 0xc9 /* UB9702 */ + +#define UB960_RR_PORT_DEBUG 0xD0 +#define UB960_RR_AEQ_CTL2 0xD2 +#define UB960_RR_AEQ_STATUS 0xD3 +#define UB960_RR_AEQ_BYPASS 0xD4 +#define UB960_RR_AEQ_MIN_MAX 0xD5 +#define UB960_RR_SFILTER_STS_0 0xD6 +#define UB960_RR_SFILTER_STS_1 0xD7 +#define UB960_RR_PORT_ICR_HI 0xD8 +#define UB960_RR_PORT_ICR_LO 0xD9 +#define UB960_RR_PORT_ISR_HI 0xDA +#define UB960_RR_PORT_ISR_LO 0xDB +#define UB960_RR_FC_GPIO_STS 0xDC +#define UB960_RR_FC_GPIO_ICR 0xDD +#define UB960_RR_SEN_INT_RISE_STS 0xDE +#define UB960_RR_SEN_INT_FALL_STS 0xDF + +#define UB960_RR_CHANNEL_MODE 0xe4 /* UB9702 */ + +#define UB960_SR_FPD3_RX_ID(n) (0xF0 + (n)) + +#define UB960_SR_I2C_RX_ID(n) (0xF8 + (n)) /* < UB960_FPD_RX_NPORTS */ + +/* Indirect register blocks */ +#define UB960_IND_TARGET_PAT_GEN 0x00 +#define UB960_IND_TARGET_RX_ANA(n) (0x01 + (n)) +#define UB960_IND_TARGET_CSI_CSIPLL_REG_1 0x92 /* UB9702 */ +#define UB960_IND_TARGET_CSI_ANA 0x07 + +/* UB960_IR_PGEN_*: Indirect Registers for Test Pattern Generator */ + +#define UB960_IR_PGEN_CTL 0x01 +#define UB960_IR_PGEN_CTL_PGEN_ENABLE BIT(0) + +#define UB960_IR_PGEN_CFG 0x02 +#define UB960_IR_PGEN_CSI_DI 0x03 +#define UB960_IR_PGEN_LINE_SIZE1 0x04 +#define UB960_IR_PGEN_LINE_SIZE0 0x05 +#define UB960_IR_PGEN_BAR_SIZE1 0x06 +#define UB960_IR_PGEN_BAR_SIZE0 0x07 +#define UB960_IR_PGEN_ACT_LPF1 0x08 +#define UB960_IR_PGEN_ACT_LPF0 0x09 +#define UB960_IR_PGEN_TOT_LPF1 0x0A +#define UB960_IR_PGEN_TOT_LPF0 0x0B +#define UB960_IR_PGEN_LINE_PD1 0x0C +#define UB960_IR_PGEN_LINE_PD0 0x0D +#define UB960_IR_PGEN_VBP 0x0E +#define UB960_IR_PGEN_VFP 0x0F +#define UB960_IR_PGEN_COLOR(n) (0x10 + (n)) /* n < 15 */ + +#define UB960_IR_RX_ANA_STROBE_SET_CLK 0x08 +#define UB960_IR_RX_ANA_STROBE_SET_DATA 0x09 + +/* EQ related */ + +#define UB960_MIN_AEQ_STROBE_POS -7 +#define UB960_MAX_AEQ_STROBE_POS 7 + +#define UB960_MIN_MANUAL_STROBE_POS -(7 + 6) +#define UB960_MAX_MANUAL_STROBE_POS (7 + 6) +#define UB960_NUM_MANUAL_STROBE_POS (UB960_MAX_MANUAL_STROBE_POS - UB960_M= IN_MANUAL_STROBE_POS + 1) + +#define UB960_MIN_EQ_LEVEL 0 +#define UB960_MAX_EQ_LEVEL 14 +#define UB960_NUM_EQ_LEVELS (UB960_MAX_EQ_LEVEL - UB960_MIN_EQ_LEVEL + 1) + +struct ub960_hw_data { + const char *model; + u8 num_rxports; + u8 num_txports; + bool ub9702; + bool fpdlink4; +}; + +enum ub960_rxport_mode { + RXPORT_MODE_RAW10 =3D 0, + RXPORT_MODE_RAW12_HF =3D 1, + RXPORT_MODE_RAW12_LF =3D 2, + RXPORT_MODE_CSI2_SYNC =3D 3, + RXPORT_MODE_CSI2_ASYNC =3D 4, + RXPORT_MODE_LAST =3D RXPORT_MODE_CSI2_ASYNC, +}; + +enum ub960_rxport_cdr { + RXPORT_CDR_FPD3 =3D 0, + RXPORT_CDR_FPD4 =3D 1, + RXPORT_CDR_LAST =3D RXPORT_CDR_FPD4, +}; + +struct ub960_rxport { + struct ub960_data *priv; + u8 nport; /* RX port number, and index in priv->rxpo= rt[] */ + + struct v4l2_subdev *sd; /* Connected subdev */ + struct fwnode_handle *fwnode; + + enum ub960_rxport_mode rx_mode; + enum ub960_rxport_cdr cdr_mode; + + struct device_node *remote_of_node; /* 'serializer' OF node */ + struct i2c_client *ser_client; /* Serializer */ + unsigned short ser_alias; /* Serializer i2c alias (lower 7 bits)= */ + + struct regulator *vpoc; + + /* EQ settings */ + struct { + bool manual_eq; + + s8 strobe_pos; + + union { + struct { + u8 eq_level_min; + u8 eq_level_max; + } aeq; + + struct { + u8 eq_level; + } manual; + }; + } eq; + + struct ds90ub9xx_platform_data ser_platform_data; +}; + +struct ub960_asd { + struct v4l2_async_subdev base; + struct ub960_rxport *rxport; +}; + +static inline struct ub960_asd *to_ub960_asd(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct ub960_asd, base); +} + +struct ub960_txport { + struct ub960_data *priv; + u8 nport; /* TX port number, and index in priv->txpo= rt[] */ + + 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 =3D 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[] =3D { + "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 >=3D 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[] =3D { + { .code =3D MEDIA_BUS_FMT_YUYV8_1X16, .bpp =3D 16, .datatype =3D 0x1e, }, + { .code =3D MEDIA_BUS_FMT_UYVY8_1X16, .bpp =3D 16, .datatype =3D 0x1e, }, + { .code =3D MEDIA_BUS_FMT_VYUY8_1X16, .bpp =3D 16, .datatype =3D 0x1e, }, + { .code =3D MEDIA_BUS_FMT_YVYU8_1X16, .bpp =3D 16, .datatype =3D 0x1e, }, + + /* Legacy */ + { .code =3D MEDIA_BUS_FMT_YUYV8_2X8, .bpp =3D 16, .datatype =3D 0x1e, }, + { .code =3D MEDIA_BUS_FMT_UYVY8_2X8, .bpp =3D 16, .datatype =3D 0x1e, }, + { .code =3D MEDIA_BUS_FMT_VYUY8_2X8, .bpp =3D 16, .datatype =3D 0x1e, }, + { .code =3D MEDIA_BUS_FMT_YVYU8_2X8, .bpp =3D 16, .datatype =3D 0x1e, }, + + /* RAW */ + { .code =3D MEDIA_BUS_FMT_SBGGR12_1X12, .bpp =3D 12, .datatype =3D 0x2c, = }, + { .code =3D MEDIA_BUS_FMT_SRGGB12_1X12, .bpp =3D 12, .datatype =3D 0x2c, = }, +}; + +static const struct ub960_format_info *ub960_find_format(u32 code) +{ + unsigned int i; + + for (i =3D 0; i < ARRAY_SIZE(ub960_formats); ++i) { + if (ub960_formats[i].code =3D=3D 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 =3D &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + else + *val =3D v; + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write(struct ub960_data *priv, u8 reg, u8 val) +{ + struct device *dev =3D &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D 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 =3D &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D 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 =3D &priv->client->dev; + int ret; + + if (priv->current_read_rxport =3D=3D nport && + priv->current_write_rxport_mask =3D=3D BIT(nport)) + return 0; + + ret =3D 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 =3D nport; + priv->current_write_rxport_mask =3D BIT(nport); + + return 0; +} + +static int ub960_rxport_read(struct ub960_data *priv, u8 nport, u8 reg, u8= *val) +{ + struct device *dev =3D &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret =3D regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + else + *val =3D v; + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_rxport_write(struct ub960_data *priv, u8 nport, u8 reg, u= 8 val) +{ + struct device *dev =3D &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret =3D 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 =3D &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret =3D 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 =3D &priv->client->dev; + int ret; + + if (priv->current_read_csiport =3D=3D nport && + priv->current_write_csiport_mask =3D=3D BIT(nport)) + return 0; + + ret =3D 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 =3D nport; + priv->current_write_csiport_mask =3D BIT(nport); + + return 0; +} + +static int ub960_csiport_read(struct ub960_data *priv, u8 nport, u8 reg, + u8 *val) +{ + struct device *dev =3D &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret =3D regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + else + *val =3D 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 =3D &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret =3D 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 =3D &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret =3D 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 =3D &priv->client->dev; + int ret; + + if (priv->current_indirect_target =3D=3D block) + return 0; + + ret =3D 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 =3D block; + + return 0; +} + +static int ub960_read_ind(struct ub960_data *priv, u8 block, u8 reg, u8 *v= al) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v); + if (ret) + goto out; + + *val =3D v; + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write_ind(struct ub960_data *priv, u8 block, u8 reg, u8 v= al) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D 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, u1= 6 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val >> 8); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg + 1); + if (ret) + goto out; + + ret =3D 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 =3D _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v); + if (ret) + goto out; + + v &=3D ~mask; + v |=3D val; + + ret =3D regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, v); + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +/* -----------------------------------------------------------------------= ------ + * I2C-ATR (address translator) + */ + +static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_board_info *info, + const struct i2c_client *client, + u16 *alias_id) +{ + struct ub960_data *priv =3D i2c_atr_get_clientdata(atr); + struct ub960_rxport *rxport =3D priv->rxports[chan_id]; + struct device *dev =3D &priv->client->dev; + struct atr_alias_table_entry *entry =3D NULL; + unsigned int reg_idx; + unsigned int pool_idx; + u16 alias; + int ret =3D 0; + u8 port_reg_idx_mask =3D 0; + + dev_dbg(dev, "rx%d: %s\n", chan_id, __func__); + + mutex_lock(&priv->atr_alias_table.lock); + + /* + * Go through the alias table and: + * 1. Look for an unreserved entry + * 2. Construct a bitmask of port's used alias entries + */ + + for (pool_idx =3D 0; pool_idx < priv->atr_alias_table.num_entries; pool_i= dx++) { + struct atr_alias_table_entry *e; + + e =3D &priv->atr_alias_table.entries[pool_idx]; + + if (!entry && !e->reserved) + entry =3D e; + + if (e->reserved && e->nport =3D=3D rxport->nport) + port_reg_idx_mask |=3D BIT(e->port_reg_idx); + } + + if (!entry) { + dev_err(dev, "rx%d: alias pool exhausted\n", rxport->nport); + ret =3D -EADDRNOTAVAIL; + goto out; + } + + if (port_reg_idx_mask =3D=3D (1 << UB960_MAX_PORT_ALIASES) - 1) { + dev_err(dev, "rx%d: all aliases in use\n", rxport->nport); + ret =3D -EADDRNOTAVAIL; + goto out; + } + + alias =3D entry->alias_id; + + reg_idx =3D ffz(port_reg_idx_mask); + + entry->reserved =3D true; + entry->nport =3D rxport->nport; + entry->slave_id =3D client->addr; + entry->port_reg_idx =3D 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 =3D alias; /* tell the atr which alias we chose */ + + dev_dbg(dev, "rx%d: client 0x%02x mapped at alias 0x%02x (%s)\n", + rxport->nport, client->addr, alias, client->name); + +out: + mutex_unlock(&priv->atr_alias_table.lock); + return ret; +} + +static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client) +{ + struct ub960_data *priv =3D i2c_atr_get_clientdata(atr); + struct ub960_rxport *rxport =3D priv->rxports[chan_id]; + struct device *dev =3D &priv->client->dev; + struct atr_alias_table_entry *entry; + unsigned int reg_idx; + unsigned int pool_idx; + u16 alias =3D 0; + + dev_dbg(dev, "rx%d: %s\n", chan_id, __func__); + + mutex_lock(&priv->atr_alias_table.lock); + + /* Find alias mapped to this client */ + + for (pool_idx =3D 0; pool_idx < priv->atr_alias_table.num_entries; pool_i= dx++) { + entry =3D &priv->atr_alias_table.entries[pool_idx]; + + if (entry->reserved && entry->nport =3D=3D rxport->nport && + entry->slave_id =3D=3D client->addr) + break; + } + + if (pool_idx =3D=3D priv->atr_alias_table.num_entries) { + dev_err(dev, "rx%d: client 0x%02x is not mapped!\n", + rxport->nport, client->addr); + goto out; + } + + alias =3D entry->alias_id; + + reg_idx =3D entry->port_reg_idx; + + /* Unmap */ + + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), 0); + + entry->reserved =3D false; + + dev_dbg(dev, "rx%d: client 0x%02x unmapped from alias 0x%02x (%s)\n", + rxport->nport, client->addr, alias, client->name); + +out: + mutex_unlock(&priv->atr_alias_table.lock); +} + +static const struct i2c_atr_ops ub960_atr_ops =3D { + .attach_client =3D ub960_atr_attach_client, + .detach_client =3D ub960_atr_detach_client, +}; + +/* -----------------------------------------------------------------------= ------ + * CSI ports + */ + +static int ub960_parse_dt_txport(struct ub960_data *priv, + const struct device_node *np, u8 nport) +{ + struct device *dev =3D &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 =3D kzalloc(sizeof(*txport), GFP_KERNEL); + if (!txport) + return -ENOMEM; + + txport->priv =3D priv; + txport->nport =3D nport; + + priv->txports[nport] =3D txport; + + ret =3D of_property_count_u32_elems(np, "data-lanes"); + + if (ret <=3D 0) { + dev_err(dev, "OF: %s: failed to parse data-lanes: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + txport->num_data_lanes =3D ret; + + ret =3D of_property_count_u64_elems(np, "link-frequencies"); + if (ret < 0) { + dev_err(dev, "OF: %s: failed to parse link-frequencies: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + if (ret !=3D 1) { + dev_err(dev, "OF: %s: link-frequencies must contain a single frequency: = %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + ret =3D of_property_read_u64(np, "link-frequencies", &freq); + if (ret) { + dev_err(dev, "OF: %s: failed to read link frequency: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + if (of_property_read_u32(np, "link-frequencies", &priv->tx_data_rate) != =3D 0) { + dev_err(dev, "OF: %s: missing \"data-rate\" node\n", + of_node_full_name(np)); + return -EINVAL; + } + + priv->tx_link_freq[0] =3D freq; + priv->tx_data_rate =3D freq * 2; + + if (priv->tx_data_rate !=3D 1600000000 && + priv->tx_data_rate !=3D 1200000000 && + priv->tx_data_rate !=3D 800000000 && + priv->tx_data_rate !=3D 400000000) { + dev_err(dev, "OF: %s: invalid \"link-frequencies\" value\n", + of_node_full_name(np)); + return -EINVAL; + } + + dev_dbg(dev, "Nominal data rate: %u", priv->tx_data_rate); + + return 0; + +err_free_txport: + kfree(txport); + + return ret; +} + +static void ub960_csi_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev =3D &priv->client->dev; + u8 csi_tx_isr; + int ret; + + ret =3D ub960_csiport_read(priv, nport, UB960_TR_CSI_TX_ISR, &csi_tx_isr); + + if (!ret) { + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR) + dev_warn(dev, "TX%u: CSI_SYNC_ERROR\n", nport); + + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR) + dev_warn(dev, "TX%u: CSI_PASS_ERROR\n", nport); + } +} + +/* -----------------------------------------------------------------------= ------ + * RX ports + */ + +static void ub960_rxport_clear_errors(struct ub960_data *priv, + unsigned int nport) +{ + u8 v; + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v); + ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &v); + ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &v); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v); +} + +static void ub960_clear_rx_errors(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport =3D 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 =3D v & BIT(3) ? 0 : 6; + + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, &v); + + data_delay =3D v & BIT(3) ? 0 : 6; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v); + if (ret) + return ret; + + clk_delay +=3D v & 0x7; + + ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_1, &v); + if (ret) + return ret; + + data_delay +=3D v & 0x7; + + *strobe_pos =3D 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 =3D BIT(3); + data_delay =3D BIT(3); + + if (strobe_pos < -7) + clk_delay =3D abs(strobe_pos) - 6; + else if (strobe_pos > 7) + data_delay =3D strobe_pos - 6; + else if (strobe_pos < 0) + clk_delay =3D abs(strobe_pos) | BIT(3); + else if (strobe_pos > 0) + data_delay =3D strobe_pos | BIT(3); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay); +} + +static void ub960_rxport_set_strobe_range(struct ub960_data *priv, + s8 strobe_min, s8 strobe_max) +{ + WARN_ON(strobe_min < UB960_MIN_AEQ_STROBE_POS); + WARN_ON(strobe_max > UB960_MAX_AEQ_STROBE_POS); + + strobe_min +=3D 7; + strobe_max +=3D 7; + + ub960_write(priv, UB960_XR_SFILTER_CFG, + (u8)strobe_min | ((u8)strobe_max << 4)); +} + +static int ub960_rxport_get_eq_level(struct ub960_data *priv, + unsigned int nport, u8 *eq_level) +{ + int ret; + u8 v; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_AEQ_STATUS, &v); + if (ret) + return ret; + + *eq_level =3D (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 <=3D 7) { + eq_stage_1_select_value =3D eq_level; + eq_stage_2_select_value =3D 0; + } else { + eq_stage_1_select_value =3D 7; + eq_stage_2_select_value =3D 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 &=3D ~((0x7 << 5) | (0x7 << 1)); + v |=3D eq_stage_1_select_value << 5; + v |=3D eq_stage_2_select_value << 1; + v |=3D BIT(0); /* Enable AEQ Bypass */ + + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v); +} + +static void ub960_rxport_set_eq_range(struct ub960_data *priv, + unsigned int nport, u8 eq_min, u8 eq_max) +{ + ub960_rxport_write(priv, nport, UB960_RR_AEQ_MIN_MAX, + eq_min | (eq_max << 4)); + + /* Enable AEQ min setting */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_CTL2, BIT(2), + BIT(2)); +} + +static void ub960_rxport_config_eq(struct ub960_data *priv, unsigned int n= port) +{ + struct ub960_rxport *rxport =3D priv->rxports[nport]; + + /* We also set common settings here. Should be moved elsewhere. */ + + if (priv->strobe.manual) { + /* Disable AEQ_SFILTER_EN */ + ub960_update_bits(priv, UB960_XR_AEQ_CTL1, BIT(0), 0); + } else { + /* Enable SFILTER and error control */ + ub960_write(priv, UB960_XR_AEQ_CTL1, (0x7 << 4) | BIT(0)); + + /* Set AEQ strobe range */ + ub960_rxport_set_strobe_range(priv, priv->strobe.min, + priv->strobe.max); + } + + /* The rest are port specific */ + + if (priv->strobe.manual) + ub960_rxport_set_strobe_pos(priv, nport, rxport->eq.strobe_pos); + else + ub960_rxport_set_strobe_pos(priv, nport, 0); + + if (rxport->eq.manual_eq) { + ub960_rxport_set_eq_level(priv, nport, + rxport->eq.manual.eq_level); + + /* Enable AEQ Bypass */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + BIT(0), BIT(0)); + } else { + ub960_rxport_set_eq_range(priv, nport, + rxport->eq.aeq.eq_level_min, + rxport->eq.aeq.eq_level_max); + + /* Disable AEQ Bypass */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + BIT(0), 0); + } +} + +static int ub960_rxport_link_ok(struct ub960_data *priv, unsigned int npor= t, + 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 =3D 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 =3D false; + return 0; + } + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2); + if (ret) + return ret; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &csi_rx_sts); + if (ret) + return ret; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, + &csi_err_cnt); + if (ret) + return ret; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &bcc_sts); + if (ret) + return ret; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + if (ret) + return ret; + + ret =3D ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + if (ret) + return ret; + + parity_errors =3D (v1 << 8) | v2; + + errors =3D (rx_port_sts1 & 0x2c) || (rx_port_sts2 & 0x20) || + (bcc_sts & 0x3f) || (csi_rx_sts & 0xf) || csi_err_cnt || + parity_errors; + + *ok =3D !errors; + + return 0; +} + +/* + * Wait for the RX ports to lock, have no errors and have stable strobe po= sition + * and EQ level. + */ +static int ub960_rxport_wait_locks(struct ub960_data *priv, + unsigned long port_mask, + unsigned int *lock_mask) +{ + struct device *dev =3D &priv->client->dev; + unsigned long timeout; + unsigned int link_ok_mask; + unsigned int missing; + unsigned int loops; + u8 nport; + int ret; + + if (port_mask =3D=3D 0) + return 0; + + if (port_mask >=3D BIT(priv->hw_data->num_rxports)) + return -EINVAL; + + timeout =3D jiffies + msecs_to_jiffies(1000); + loops =3D 0; + link_ok_mask =3D 0; + + while (time_before(jiffies, timeout)) { + missing =3D 0; + + for_each_set_bit(nport, &port_mask, + priv->hw_data->num_rxports) { + struct ub960_rxport *rxport =3D priv->rxports[nport]; + bool ok; + + if (!rxport) + continue; + + ret =3D ub960_rxport_link_ok(priv, nport, &ok); + if (ret) + return ret; + + if (!ok || !(link_ok_mask & BIT(nport))) + missing++; + + if (ok) + link_ok_mask |=3D BIT(nport); + else + link_ok_mask &=3D ~BIT(nport); + } + + loops++; + + if (missing =3D=3D 0) + break; + + msleep(50); + } + + if (lock_mask) + *lock_mask =3D 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 =3D 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 =3D ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos); + if (ret) + return ret; + + ret =3D 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 =3D &priv->client->dev; + struct i2c_adapter *parent_adap =3D priv->client->adapter; + + priv->atr =3D i2c_atr_new(parent_adap, dev, &ub960_atr_ops, + priv->hw_data->num_rxports); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_clientdata(priv->atr, priv); + + return 0; +} + +static void ub960_uninit_atr(struct ub960_data *priv) +{ + i2c_atr_delete(priv->atr); + priv->atr =3D 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 =3D 1; + div =3D 10; + break; + + case RXPORT_MODE_CSI2_SYNC: + mult =3D 2; + div =3D 1; + break; + + case RXPORT_MODE_CSI2_ASYNC: + mult =3D 2; + div =3D 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 =3D priv->rxports[nport]; + struct device *dev =3D &priv->client->dev; + struct ds90ub9xx_platform_data *ser_pdata =3D &rxport->ser_platform_data; + struct i2c_board_info ser_info =3D { + .of_node =3D rxport->remote_of_node, + .platform_data =3D ser_pdata, + }; + + ser_pdata->port =3D nport; + ser_pdata->atr =3D priv->atr; + if (priv->hw_data->ub9702) + ser_pdata->bc_rate =3D ub960_calc_bc_clk_rate_ub9702(priv, rxport); + else + ser_pdata->bc_rate =3D 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 =3D rxport->ser_alias; + rxport->ser_client =3D + i2c_new_client_device(priv->client->adapter, &ser_info); + if (!rxport->ser_client) { + dev_err(dev, "rx%d: cannot add %s i2c device", nport, + ser_info.type); + return -EIO; + } + + dev_dbg(dev, "rx%d: remote serializer at alias 0x%02x (%u-%04x)\n", + nport, rxport->ser_client->addr, + rxport->ser_client->adapter->nr, rxport->ser_client->addr); + + return 0; +} + +static void ub960_rxport_remove_serializer(struct ub960_data *priv, u8 npo= rt) +{ + struct ub960_rxport *rxport =3D priv->rxports[nport]; + + i2c_unregister_device(rxport->ser_client); + rxport->ser_client =3D 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 =3D 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport =3D priv->rxports[nport]; + + if (!rxport) + continue; + + ret =3D 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 =3D priv->rxports[nport - 1]; + + if (!rxport) + continue; + + rxport =3D 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 =3D 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport =3D 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 =3D txport->nport; + u8 csi_ctl =3D 0; + + /* + * From the datasheet: "initial CSI Skew-Calibration + * sequence [...] should be set when operating at 1.6 Gbps" + */ + if (priv->tx_data_rate =3D=3D 1600000000) + csi_ctl |=3D UB960_TR_CSI_CTL_CSI_CAL_EN; + + csi_ctl |=3D (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 =3D 0; + pll_div =3D 0x10; + break; + case 1200000000: + speed_select =3D 1; + break; + case 800000000: + speed_select =3D 2; + pll_div =3D 0x10; + break; + case 400000000: + speed_select =3D 3; + pll_div =3D 0x10; + break; + } + + ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select); + + if (priv->hw_data->ub9702) { + ub960_write(priv, UB960_SR_CSI_PLL_DIV, pll_div); + + switch (priv->tx_data_rate) { + case 1600000000: + default: + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0x80); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4B, 0x2A); + break; + case 800000000: + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0x90); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4F, 0x2A); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4B, 0x2A); + break; + case 400000000: + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0xA0); + break; + } + } + + for (nport =3D 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport =3D 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 =3D rxport->nport; + u32 bc_freq_val; + + /* + * Back channel frequency select. + * Override FREQ_SELECT from the strap. + * 0 - 2.5 Mbps (DS90UB913A-Q1 / DS90UB933-Q1) + * 2 - 10 Mbps + * 6 - 50 Mbps (DS90UB953-Q1) + * + * Note that changing this setting will result in some errors on the back + * channel for a short period of time. + */ + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + bc_freq_val =3D 0; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val =3D 2; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val =3D 6; + break; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + /* FPD3_MODE =3D RAW10 Mode (DS90UB913A-Q1 / DS90UB933-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3, + 0x3); + + /* + * RAW10_8BIT_CTL =3D 0b11 : 8-bit processing using lower 8 bits + * 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, + 0x3 << 6, 0x2 << 6); + + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + /* CSI-2 Mode (DS90UB953-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3, + 0x0); + + break; + } + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, 0x1); + + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07); + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH); + + /* Enable I2C communication to the serializer via the alias addr */ + ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID, + rxport->ser_alias << 1); + + /* Configure EQ related settings */ + ub960_rxport_config_eq(priv, nport); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport)); +} + +static void ub960_init_rx_port_ub9702_fpd3(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport =3D rxport->nport; + u8 bc_freq_val; + u8 fpd_func_mode; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + bc_freq_val =3D 0; + fpd_func_mode =3D 5; + break; + + case RXPORT_MODE_RAW12_HF: + bc_freq_val =3D 0; + fpd_func_mode =3D 4; + break; + + case RXPORT_MODE_RAW12_LF: + bc_freq_val =3D 0; + fpd_func_mode =3D 6; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val =3D 6; + fpd_func_mode =3D 2; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val =3D 2; + fpd_func_mode =3D 2; + break; + + default: + WARN_ON(true); + break; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, fpd_func_mode); + + /* set serdes_eq_mode =3D 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=3D4 */ + 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 =3D rxport->nport; + bool first_time_power_up =3D 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 =3D rxport->nport; + u8 bc_freq_val; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + bc_freq_val =3D 0; + break; + + case RXPORT_MODE_RAW12_HF: + bc_freq_val =3D 0; + break; + + case RXPORT_MODE_RAW12_LF: + bc_freq_val =3D 0; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val =3D 6; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val =3D 2; + break; + + default: + WARN_ON(true); + break; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + + /* FPD4 Sync Mode */ + ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, 0); + + /* add serdes_eq_offset of 4 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04); + + /* FPD4 serdes_start_eq in 0x27: assign default */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, 0x0); + /* FPD4 serdes_end_eq in 0x28: assign default */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, 0x23); + + /* set serdes_driver_mode into FPD IV mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x04, 0x00); + /* set FPD PBC drv into FPD IV mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1B, 0x00); + + /* set serdes_system_init to 0x2f */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x21, 0x2f); + /* set serdes_system_rst in reset mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0xC1); + + /* RX port to 7.55G mode */ + ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2), + 0 << (nport * 2)); + + ub960_init_rx_port_ub9702_fpd4_aeq(priv, rxport); +} + +static void ub960_init_rx_port_ub9702(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport =3D rxport->nport; + + if (rxport->cdr_mode =3D=3D 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 =3D 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 =3D=3D 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 =3D 0; nport < priv->hw_data->num_rxports; nport++) { + struct ub960_rxport *rxport =3D priv->rxports[nport]; + + if (!rxport) + continue; + + if (priv->hw_data->ub9702) + ub960_init_rx_port_ub9702(priv, rxport); + else + ub960_init_rx_port_ub960(priv, rxport); + } + + return 0; +} + +static void ub960_rxport_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev =3D &priv->client->dev; + u8 rx_port_sts1; + u8 rx_port_sts2; + u8 csi_rx_sts; + u8 bcc_sts; + int ret =3D 0; + + /* Read interrupts (also clears most of them) */ + if (!ret) + ret =3D ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &rx_port_sts1); + if (!ret) + ret =3D ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2); + if (!ret) + ret =3D ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, + &csi_rx_sts); + if (!ret) + ret =3D 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 pass= ed", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_CABLE_FAULT) ? "no clock" : "cloc= k ok", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FREQ_STABLE) ? "stable freq" : "u= nstable 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 =3D &state->routing; + u8 cur_vc[UB960_MAX_TX_NPORTS] =3D { 0 }; + u8 handled_mask =3D 0; + unsigned int i; + + for (i =3D 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route =3D &routing->routes[i]; + unsigned int rx, tx; + + rx =3D ub960_pad_to_port(priv, route->sink_pad); + + if (BIT(rx) & handled_mask) + continue; + + tx =3D ub960_pad_to_port(priv, route->source_pad); + + vc[rx] =3D cur_vc[tx]++; + handled_mask |=3D BIT(rx); + } +} + +static int ub960_enable_tx_port(struct ub960_data *priv, unsigned int npor= t) +{ + struct device *dev =3D &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 np= ort) +{ + struct device *dev =3D &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 npor= t) +{ + struct device *dev =3D &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 np= ort) +{ + struct device *dev =3D &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 =3D &state->routing; + unsigned int i; + unsigned int nport; + u8 fwd_ctl; + u32 active_tx_mask =3D 0; + struct { + u32 num_streams; + u8 pixel_dt; + u8 meta_dt; + u32 meta_lines; + u32 tx_port; + } rx_data[UB960_MAX_RX_NPORTS] =3D { 0 }; + u8 vc_map[UB960_MAX_RX_NPORTS] =3D { 0 }; + + ub960_get_vc_maps(priv, state, vc_map); + + for (i =3D 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route =3D &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 =3D ub960_pad_to_port(priv, route->sink_pad); + + rxport =3D priv->rxports[nport]; + if (!rxport) + return -EINVAL; + + txport =3D priv->txports[ub960_pad_to_port(priv, route->source_pad)]; + if (!txport) + return -EINVAL; + + active_tx_mask |=3D BIT(ub960_pad_to_port(priv, route->source_pad)); + + rx_data[nport].tx_port =3D 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 =3D=3D RXPORT_MODE_CSI2_SYNC || + rxport->rx_mode =3D=3D RXPORT_MODE_CSI2_ASYNC) + continue; + + if (rx_data[nport].num_streams > 2) + return -EPIPE; + + fmt =3D v4l2_subdev_state_get_stream_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EPIPE; + + ub960_fmt =3D ub960_find_format(fmt->code); + if (!ub960_fmt) + return -EPIPE; + + if (ub960_fmt->meta) { + if (fmt->height > 3) { + dev_err(&priv->client->dev, + "Unsupported metadata height %u\n", + fmt->height); + return -EPIPE; + } + + rx_data[nport].meta_dt =3D ub960_fmt->datatype; + rx_data[nport].meta_lines =3D fmt->height; + } else { + rx_data[nport].pixel_dt =3D ub960_fmt->datatype; + } + } + + /* Configure RX ports */ + + fwd_ctl =3D 0; + + for (nport =3D 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport =3D priv->rxports[nport]; + u8 vc =3D vc_map[nport]; + + if (rx_data[nport].num_streams =3D=3D 0) + continue; + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + ub960_rxport_write(priv, nport, UB960_RR_RAW10_ID, + rx_data[nport].pixel_dt | (vc << 6)); + + ub960_rxport_write(priv, rxport->nport, + UB960_RR_RAW_EMBED_DTYPE, + (rx_data[nport].meta_lines << 6) | rx_data[nport].meta_dt); + + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + if (!priv->hw_data->ub9702) { + /* Map all VCs from this port to the same VC */ + ub960_rxport_write(priv, nport, UB960_RR_CSI_VC_MAP, + (vc << 6) | (vc << 4) | + (vc << 2) | (vc << 0)); + } else { + unsigned int i; + + /* Map all VCs from this port to VC(nport) */ + for (i =3D 0; i < 8; ++i) + ub960_rxport_write(priv, nport, + UB960_RR_VC_ID_MAP(i), nport); + } + + break; + } + + /* Forwarding */ + + fwd_ctl |=3D BIT(4 + nport); /* forward disable */ + + if (rx_data[nport].tx_port =3D=3D 1) + fwd_ctl |=3D BIT(nport); /* forward to TX1 */ + else + fwd_ctl &=3D ~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 =3D false; + + for (i =3D 0; i < UB960_MAX_NPORTS; ++i) { + if (priv->stream_enable_mask[i]) { + streaming =3D true; + break; + } + } + + priv->streaming =3D 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 =3D sd_to_ub960(sd); + struct device *dev =3D &priv->client->dev; + const struct v4l2_subdev_krouting *routing; + unsigned int source_stream; + int ret; + u64 sink_streams[UB960_MAX_RX_NPORTS] =3D { 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 =3D &state->routing; + + if (!priv->streaming) { + dev_dbg(dev, "Prepare for streaming\n"); + ret =3D 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 =3D ub960_enable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + if (ret) + return ret; + } + + priv->stream_enable_mask[source_pad] |=3D source_streams_mask; + + /* Collect sink streams per pad which we need to enable */ + for (source_stream =3D 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 =3D=3D source_pad) || + !(route->source_stream =3D=3D source_stream)) + continue; + + nport =3D ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |=3D BIT_ULL(route->sink_stream); + } + } + + for (nport =3D 0; nport < priv->hw_data->num_rxports; ++nport) { + struct v4l2_subdev *source_sd; + struct media_pad *source_sd_pad; + + if (!sink_streams[nport]) + continue; + + /* Enable the RX port if not yet enabled */ + if (!priv->stream_enable_mask[nport]) { + ret =3D ub960_enable_rx_port(priv, nport); + if (ret) { + failed_port =3D nport; + goto err; + } + } + + priv->stream_enable_mask[nport] |=3D sink_streams[nport]; + + dev_dbg(dev, "Enable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + source_sd_pad =3D media_pad_remote_pad_first(&priv->pads[nport]); + source_sd =3D priv->rxports[nport]->sd; + + ret =3D v4l2_subdev_enable_streams( + source_sd, source_sd_pad->index, sink_streams[nport]); + if (ret) { + priv->stream_enable_mask[nport] &=3D ~sink_streams[nport]; + + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + + failed_port =3D nport; + goto err; + } + } + + priv->streaming =3D true; + + return 0; + +err: + for (nport =3D 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 =3D media_pad_remote_pad_first(&priv->pads[nport]); + source_sd =3D priv->rxports[nport]->sd; + + ret =3D v4l2_subdev_disable_streams( + source_sd, source_sd_pad->index, sink_streams[nport]); + WARN_ON(ret); + + priv->stream_enable_mask[nport] &=3D ~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] &=3D ~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 =3D sd_to_ub960(sd); + struct device *dev =3D &priv->client->dev; + const struct v4l2_subdev_krouting *routing; + int ret; + unsigned int source_stream; + u64 sink_streams[UB960_MAX_RX_NPORTS] =3D { 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) !=3D sou= rce_streams_mask) { + dev_err(dev, + "cannot disable already disabled streams on pad %u mask %#llx\n", + source_pad, source_streams_mask); + return -EBUSY; + } + + routing =3D &state->routing; + + /* Collect sink streams per pad which we need to disable */ + for (source_stream =3D 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 =3D=3D source_pad) || + !(route->source_stream =3D=3D source_stream)) + continue; + + nport =3D ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |=3D BIT_ULL(route->sink_stream); + } + } + + for (nport =3D 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 =3D media_pad_remote_pad_first(&priv->pads[nport]); + source_sd =3D priv->rxports[nport]->sd; + + ret =3D v4l2_subdev_disable_streams( + source_sd, source_sd_pad->index, sink_streams[nport]); + WARN_ON(ret); + + priv->stream_enable_mask[nport] &=3D ~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] &=3D ~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 =3D 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] =3D { 0 }; + unsigned int nport; + int ret =3D 0; + + state =3D v4l2_subdev_lock_and_get_active_state(sd); + + routing =3D &state->routing; + + for_each_active_route(routing, route) + pad_stream_masks[ub960_pad_to_port(priv, route->source_pad)] |=3D + BIT_ULL(route->source_stream); + + if (enable) { + for (nport =3D 0; nport < UB960_MAX_TX_NPORTS; ++nport) { + if (pad_stream_masks[nport] =3D=3D 0) + continue; + + ret =3D 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] =3D=3D 0) + continue; + + ub960_disable_streams( + sd, state, + priv->hw_data->num_rxports + + nport - 1, + pad_stream_masks[nport - 1]); + } + + break; + } + } + } else { + for (nport =3D 0; nport < UB960_MAX_TX_NPORTS; ++nport) { + if (pad_stream_masks[nport] =3D=3D 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 =3D { + .s_stream =3D 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 =3D { + .width =3D 640, + .height =3D 480, + .code =3D MEDIA_BUS_FMT_UYVY8_2X8, + .field =3D V4L2_FIELD_NONE, + .colorspace =3D V4L2_COLORSPACE_SRGB, + .ycbcr_enc =3D V4L2_YCBCR_ENC_601, + .quantization =3D V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func =3D 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 =3D v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret =3D 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 =3D sd_to_ub960(sd); + + if (which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + return _ub960_set_routing(sd, state, routing); +} + +static int ub960_get_source_frame_desc(struct ub960_data *priv, + struct v4l2_mbus_frame_desc *desc, + u8 nport) +{ + struct v4l2_subdev *sd; + struct media_pad *pad; + int ret; + + pad =3D media_pad_remote_pad_first(&priv->pads[nport]); + if (!pad) + return -EPIPE; + + sd =3D priv->rxports[nport]->sd; + + ret =3D v4l2_subdev_call(sd, pad, get_frame_desc, pad->index, desc); + if (ret) + return ret; + + return 0; +} + +static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub960_data *priv =3D sd_to_ub960(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret =3D 0; + struct device *dev =3D &priv->client->dev; + u8 vc_map[UB960_MAX_RX_NPORTS] =3D { 0 }; + + dev_dbg(dev, "%s for pad %d\n", __func__, pad); + + if (!ub960_pad_is_source(priv, pad)) + return -EINVAL; + + state =3D v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ub960_get_vc_maps(priv, state, vc_map); + + routing =3D &state->routing; + + memset(fd, 0, sizeof(*fd)); + + fd->type =3D V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + for_each_active_route(routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry =3D NULL; + struct v4l2_mbus_frame_desc source_fd; + unsigned int j; + unsigned int nrxport; + + if (route->source_pad !=3D pad) + continue; + + ret =3D ub960_get_source_frame_desc(priv, &source_fd, + route->sink_pad); + if (ret) { + dev_err(dev, + "Failed to get source frame desc for port %u\n", + route->sink_pad); + goto out; + } + + for (j =3D 0; j < source_fd.num_entries; ++j) + if (source_fd.entry[j].stream =3D=3D route->sink_stream) { + source_entry =3D &source_fd.entry[j]; + break; + } + + if (!source_entry) { + dev_err(dev, + "Failed to find stream from source frame desc\n"); + ret =3D -EPIPE; + goto out; + } + + fd->entry[fd->num_entries].stream =3D route->source_stream; + + fd->entry[fd->num_entries].flags =3D + V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd->entry[fd->num_entries].length =3D source_entry->length; + fd->entry[fd->num_entries].pixelcode =3D source_entry->pixelcode; + + nrxport =3D ub960_pad_to_port(priv, route->sink_pad); + + fd->entry[fd->num_entries].bus.csi2.vc =3D vc_map[nrxport]; + + if (source_fd.type =3D=3D V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + fd->entry[fd->num_entries].bus.csi2.dt =3D + source_entry->bus.csi2.dt; + } else { + const struct ub960_format_info *ub960_fmt; + struct v4l2_mbus_framefmt *fmt; + + fmt =3D v4l2_subdev_state_get_stream_format( + state, pad, route->source_stream); + + if (!fmt) { + ret =3D -EINVAL; + goto out; + } + + ub960_fmt =3D ub960_find_format(fmt->code); + if (!ub960_fmt) { + dev_err(dev, "Unable to find format\n"); + ret =3D -EINVAL; + goto out; + } + + fd->entry[fd->num_entries].bus.csi2.dt =3D + 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 =3D sd_to_ub960(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which =3D=3D 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 =3D v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + fmt =3D v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + return 0; +} + +static int ub960_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct ub960_data *priv =3D sd_to_ub960(sd); + + struct v4l2_subdev_route routes[] =3D { + { + .sink_pad =3D 0, + .sink_stream =3D 0, + .source_pad =3D priv->hw_data->num_rxports, + .source_stream =3D 0, + .flags =3D V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing =3D { + .num_routes =3D ARRAY_SIZE(routes), + .routes =3D routes, + }; + + return _ub960_set_routing(sd, state, &routing); +} + +static const struct v4l2_subdev_pad_ops ub960_pad_ops =3D { + .enable_streams =3D ub960_enable_streams, + .disable_streams =3D ub960_disable_streams, + + .set_routing =3D ub960_set_routing, + .get_frame_desc =3D ub960_get_frame_desc, + + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D ub960_set_fmt, + + .init_cfg =3D ub960_init_cfg, +}; + +static int ub960_log_status(struct v4l2_subdev *sd) +{ + struct ub960_data *priv =3D sd_to_ub960(sd); + struct device *dev =3D &priv->client->dev; + struct v4l2_subdev_state *state; + unsigned int nport; + u8 v =3D 0, v1 =3D 0, v2 =3D 0; + unsigned int i; + char id[7]; + + state =3D v4l2_subdev_lock_and_get_active_state(sd); + + for (i =3D 0; i < 6; ++i) + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]); + id[6] =3D 0; + + dev_info(dev, "ID '%s'\n", id); + + for (nport =3D 0; nport < priv->hw_data->num_txports; ++nport) { + struct ub960_txport *txport =3D 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 =3D 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport =3D priv->rxports[nport]; + u8 eq_level; + s8 strobe_pos; + unsigned int i; + + dev_info(dev, "RX %u\n", nport); + + if (!rxport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v); + + if (v & UB960_RR_RX_PORT_STS1_LOCK_STS) + dev_info(dev, "\tLocked\n"); + else + dev_info(dev, "\tNot locked\n"); + + dev_info(dev, "\trx_port_sts1 %#x\n", v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v); + dev_info(dev, "\trx_port_sts2 %#x\n", v); + + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2); + dev_info(dev, "\tlink freq %u MHz\n", v1 * 1000000 + v2 * 1000000 / 256); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + dev_info(dev, "\tparity errors %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2); + dev_info(dev, "\tlines per frame %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2); + dev_info(dev, "\tbytes per line %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v); + dev_info(dev, "\tcsi_err_counter %u\n", v); + + /* Strobe */ + + ub960_read(priv, UB960_XR_AEQ_CTL1, &v); + + dev_info(dev, "\t%s strobe\n", + (v & BIT(0)) ? "Adaptive" : "Manual"); + + if (v & BIT(0)) { + ub960_read(priv, UB960_XR_SFILTER_CFG, &v); + + dev_info(dev, "\tStrobe range [%d, %d]\n", + (v & 0xf) - 7, ((v >> 4) & 0xf) - 7); + } + + ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos); + + dev_info(dev, "\tStrobe pos %d\n", strobe_pos); + + /* EQ */ + + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v); + + dev_info(dev, "\t%s EQ\n", + (v & BIT(0)) ? "Manual" : "Adaptive"); + + if (!(v & BIT(0))) { + ub960_rxport_read(priv, nport, UB960_RR_AEQ_MIN_MAX, + &v); + + dev_info(dev, "\tEQ range [%u, %u]\n", v & 0xf, + (v >> 4) & 0xf); + } + + ub960_rxport_get_eq_level(priv, nport, &eq_level); + + dev_info(dev, "\tEQ level %u\n", eq_level); + + /* GPIOs */ + for (i =3D 0; i < UB960_NUM_BC_GPIOS; ++i) { + u8 ctl_reg; + u8 ctl_shift; + + ctl_reg =3D UB960_RR_BC_GPIO_CTL(i / 2); + ctl_shift =3D (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 =3D { + .log_status =3D ub960_log_status, + .subscribe_event =3D v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event =3D v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ub960_subdev_ops =3D { + .core =3D &ub960_subdev_core_ops, + .video =3D &ub960_video_ops, + .pad =3D &ub960_pad_ops, +}; + +static const struct media_entity_operations ub960_entity_ops =3D { + .get_fwnode_pad =3D v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate =3D v4l2_subdev_link_validate, + .has_pad_interdep =3D 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 =3D 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */ + u8 cbars_idx =3D tpg_num - TEST_PATTERN_V_COLOR_BARS_1; + u8 num_cbars =3D 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 =3D v4l2_subdev_get_locked_active_state(&priv->sd); + + vbp =3D 33; + vfp =3D 10; + blank_lines =3D vbp + vfp + 2; /* total blanking lines */ + + fmt =3D v4l2_subdev_state_get_stream_format(state, 4, 0); + if (!fmt) { + dev_err(&priv->client->dev, "failed to enable TPG\n"); + return; + } + + width =3D fmt->width; + height =3D fmt->height; + + line_size =3D width * bytespp; + bar_size =3D line_size / num_cbars; + act_lpf =3D height; + tot_lpf =3D act_lpf + blank_lines; + line_pd =3D 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 =3D + container_of(ctrl->handler, struct ub960_data, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + if (ctrl->val =3D=3D 0) + ub960_disable_tpg(priv); + else + ub960_enable_tpg(priv, ctrl->val); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops ub960_ctrl_ops =3D { + .s_ctrl =3D ub960_s_ctrl, +}; + +/* -----------------------------------------------------------------------= ------ + * Core + */ + +static irqreturn_t ub960_handle_events(int irq, void *arg) +{ + struct ub960_data *priv =3D arg; + unsigned int i; + u8 int_sts; + int ret; + + ret =3D ub960_read(priv, UB960_SR_INTERRUPT_STS, &int_sts); + + if (!ret && int_sts) { + u8 fwd_sts; + + dev_dbg(&priv->client->dev, "INTERRUPT_STS %x\n", int_sts); + + ub960_read(priv, UB960_SR_FWD_STS, &fwd_sts); + + dev_dbg(&priv->client->dev, "FWD_STS %#x\n", fwd_sts); + + for (i =3D 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 =3D 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 =3D to_delayed_work(work); + struct ub960_data *priv =3D + 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 =3D 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport =3D priv->txports[nport]; + + if (!txport) + continue; + + kfree(txport); + priv->txports[nport] =3D NULL; + } +} + +static void ub960_rxport_free_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport =3D 0; nport < priv->hw_data->num_rxports; nport++) { + struct ub960_rxport *rxport =3D priv->rxports[nport]; + + if (!rxport) + continue; + + if (rxport->vpoc) + regulator_disable(rxport->vpoc); + + fwnode_handle_put(rxport->fwnode); + of_node_put(rxport->remote_of_node); + + kfree(rxport); + priv->rxports[nport] =3D NULL; + } +} + +static int ub960_parse_dt_base(struct ub960_data *priv) +{ + struct device_node *np =3D priv->client->dev.of_node; + struct device *dev =3D &priv->client->dev; + size_t table_size; + u16 *aliases; + int ret; + + ret =3D of_property_count_u16_elems(np, "i2c-alias-pool"); + if (ret < 0) { + dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n", + ret); + return ret; + } + + table_size =3D ret; + priv->atr_alias_table.num_entries =3D ret; + + if (!table_size) + return 0; + + priv->atr_alias_table.entries =3D + devm_kcalloc(dev, table_size, + sizeof(struct atr_alias_table_entry), GFP_KERNEL); + if (!priv->atr_alias_table.entries) + return -ENOMEM; + + aliases =3D kcalloc(table_size, sizeof(u16), GFP_KERNEL); + if (!aliases) + return -ENOMEM; + + ret =3D of_property_read_u16_array(np, "i2c-alias-pool", aliases, + table_size); + if (ret < 0) { + dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n", + ret); + kfree(aliases); + return ret; + } + + for (unsigned int i =3D 0; i < table_size; ++i) + priv->atr_alias_table.entries[i].alias_id =3D aliases[i]; + + kfree(aliases); + + dev_dbg(dev, "i2c-alias-pool has %zu aliases", table_size); + + return 0; +} + +static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv, + struct device_node *np, + struct ub960_rxport *rxport) +{ + struct device *dev =3D &priv->client->dev; + int ret; + u32 rx_mode; + u32 cdr_mode; + s32 strobe_pos; + u32 eq_level; + unsigned int nport =3D rxport->nport; + + ret =3D of_property_read_u32(np, "cdr-mode", &cdr_mode); + if (ret =3D=3D -EINVAL) { + cdr_mode =3D RXPORT_CDR_FPD3; + } else if (ret < 0) { + dev_err(dev, "Failed to get RX port CDR mode: %d\n", ret); + return ret; + } + + if (cdr_mode > RXPORT_CDR_LAST) { + dev_err(dev, "Bad RX port cdr %u\n", cdr_mode); + return -EINVAL; + } + + if (!priv->hw_data->fpdlink4 && cdr_mode =3D=3D RXPORT_CDR_FPD4) { + dev_err(dev, "FPD-Link 4 CDR not supported on RX port %u\n", + nport); + return -EINVAL; + } + + rxport->cdr_mode =3D cdr_mode; + + ret =3D of_property_read_u32(np, "rx-mode", &rx_mode); + if (ret < 0) { + dev_err(dev, "Missing RX port RX mode: %d\n", ret); + return ret; + } + + if (rx_mode > RXPORT_MODE_LAST) { + dev_err(dev, "Bad RX port RX mode %u\n", rx_mode); + return -EINVAL; + } + + rxport->rx_mode =3D rx_mode; + + /* EQ & Strobe related */ + + /* Defaults */ + rxport->eq.manual_eq =3D false; + rxport->eq.aeq.eq_level_min =3D UB960_MIN_EQ_LEVEL; + rxport->eq.aeq.eq_level_max =3D UB960_MAX_EQ_LEVEL; + + ret =3D of_property_read_s32(np, "strobe-pos", &strobe_pos); + if (ret) { + if (ret !=3D -EINVAL) { + dev_err(dev, "Failed to read 'strobe-pos': %d\n", ret); + return ret; + } + } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS || + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) { + dev_err(dev, "Illegal 'strobe-pos' value: %d\n", strobe_pos); + } else { + // NOTE: ignored unless global manual strobe pos is set + rxport->eq.strobe_pos =3D strobe_pos; + if (!priv->strobe.manual) + dev_warn(dev, "'strobe-pos' ignored as 'manual-strobe' not set\n"); + } + + ret =3D of_property_read_u32(np, "eq-level", &eq_level); + if (ret) { + if (ret !=3D -EINVAL) { + dev_err(dev, "Failed to read 'eq-level': %d\n", ret); + return ret; + } + } else if (eq_level > UB960_MAX_EQ_LEVEL) { + dev_err(dev, "Illegal 'eq-level' value: %d\n", eq_level); + } else { + rxport->eq.manual_eq =3D true; + rxport->eq.manual.eq_level =3D eq_level; + } + + return 0; +} + +static int ub960_parse_dt_rxport(struct ub960_data *priv, + struct device_node *np) +{ + const char *vpoc_names[UB960_MAX_RX_NPORTS] =3D { "vpoc0", "vpoc1", + "vpoc2", "vpoc3" }; + struct device *dev =3D &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 =3D of_property_read_u32(np, "reg", &nport); + if (ret) { + dev_err(dev, "Failed to get link reg\n"); + return ret; + } + + if (priv->rxports[nport]) { + dev_err(dev, "OF: %s: reg value %d is duplicated\n", + of_node_full_name(np), nport); + return -EADDRINUSE; + } + + rxport =3D kzalloc(sizeof(*rxport), GFP_KERNEL); + if (!rxport) + return -ENOMEM; + + priv->rxports[nport] =3D rxport; + + rxport->nport =3D nport; + rxport->priv =3D priv; + + ret =3D ub960_parse_dt_rxport_link_properties(priv, np, rxport); + if (ret) + goto err_free_rxport; + + ret =3D 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 =3D ser_i2c_alias; + + rxport->vpoc =3D devm_regulator_get_optional(dev, vpoc_names[nport]); + if (IS_ERR(rxport->vpoc)) { + ret =3D PTR_ERR(rxport->vpoc); + if (ret =3D=3D -ENODEV) { + rxport->vpoc =3D NULL; + } else { + dev_err(dev, "Failed to get VPOC supply: %d\n", ret); + goto err_free_rxport; + } + } + + rxport->remote_of_node =3D 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 =3D -EINVAL; + goto err_free_rxport; + } + + /* We also parse the endpoint data here */ + + ep_np =3D 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 =3D -EINVAL; + goto err_ser_node_put; + } + + remote_ep_node =3D fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep_n= p)); + + of_node_put(ep_np); + + if (!remote_ep_node) { + dev_err(dev, "No remote endpoint for rxport%d\n", nport); + ret =3D -ENODEV; + goto err_ser_node_put; + } + + rxport->fwnode =3D remote_ep_node; + + // XXX enable somewhere else? + if (rxport->vpoc) { + ret =3D regulator_enable(rxport->vpoc); + if (ret) + goto err_ep_node_put; + } + + return 0; + +err_ep_node_put: + fwnode_handle_put(rxport->fwnode); +err_ser_node_put: + of_node_put(rxport->remote_of_node); +err_free_rxport: + priv->rxports[nport] =3D NULL; + kfree(rxport); + return ret; +} + +static struct device_node * +ub960_of_get_link_by_regs(struct device_node *links_np, unsigned int nport) +{ + struct device_node *link_np; + int ret; + + for_each_child_of_node(links_np, link_np) { + u32 link_num; + + if (!of_node_name_eq(link_np, "link")) + continue; + + ret =3D of_property_read_u32(link_np, "reg", &link_num); + if (ret) + return NULL; + + if (nport =3D=3D link_num) { + of_node_get(link_np); + return link_np; + } + } + + return NULL; +} + +static int ub960_parse_dt_rxports(struct ub960_data *priv) +{ + struct device_node *np =3D priv->client->dev.of_node; + struct device *dev =3D &priv->client->dev; + struct device_node *links_np; + unsigned int nport; + int ret; + + links_np =3D of_get_child_by_name(np, "links"); + if (!links_np) { + dev_err(dev, "links node missing\n"); + return -ENODEV; + } + + /* Defaults, recommended by TI */ + priv->strobe.min =3D 2; + priv->strobe.max =3D 3; + + priv->strobe.manual =3D of_property_read_bool(links_np, "manual-strobe"); + + for (nport =3D 0; nport < priv->hw_data->num_rxports; ++nport) { + struct device_node *link_np; + + link_np =3D ub960_of_get_link_by_regs(links_np, nport); + if (!link_np) + continue; + + ret =3D ub960_parse_dt_rxport(priv, link_np); + + of_node_put(link_np); + + if (ret) { + dev_err(dev, "Failed to parse link node\n"); + goto err_put_links; + } + } + + of_node_put(links_np); + + return 0; + +err_put_links: + of_node_put(links_np); + + return ret; +} + +static int ub960_parse_dt_txports(struct ub960_data *priv) +{ + struct device_node *np =3D priv->client->dev.of_node; + u32 nport; + int ret; + + for (nport =3D 0; nport < priv->hw_data->num_txports; ++nport) { + struct device_node *ep_np; + + ep_np =3D of_graph_get_endpoint_by_regs(np, nport + priv->hw_data->num_r= xports, 0); + if (!ep_np) + continue; + + ret =3D ub960_parse_dt_txport(priv, ep_np, nport); + + of_node_put(ep_np); + + if (ret) + break; + } + + return 0; +} + +static int ub960_parse_dt(struct ub960_data *priv) +{ + struct device_node *np =3D priv->client->dev.of_node; + struct device *dev =3D &priv->client->dev; + int ret; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + ret =3D ub960_parse_dt_base(priv); + if (ret) + return ret; + + ret =3D ub960_parse_dt_rxports(priv); + if (ret) + return ret; + + ret =3D 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 =3D sd_to_ub960(notifier->sd); + struct ub960_rxport *rxport =3D to_ub960_asd(asd)->rxport; + struct device *dev =3D &priv->client->dev; + u8 nport =3D rxport->nport; + unsigned int src_pad; + unsigned int i; + int ret; + + dev_dbg(dev, "Bind %s\n", subdev->name); + + ret =3D media_entity_get_fwnode_pad(&subdev->entity, rxport->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + rxport->sd =3D subdev; + src_pad =3D ret; + + ret =3D media_create_pad_link(&rxport->sd->entity, src_pad, + &priv->sd.entity, nport, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:%u\n", + rxport->sd->name, src_pad, priv->sd.name, nport); + return ret; + } + + dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name, src_pad, + nport); + + for (i =3D 0; i < priv->hw_data->num_rxports; ++i) { + if (priv->rxports[i] && !priv->rxports[i]->sd) { + dev_dbg(dev, "Waiting for more subdevs to be bound\n"); + return 0; + } + } + + dev_dbg(dev, "All subdevs bound\n"); + + return 0; +} + +static void ub960_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct ub960_data *priv =3D sd_to_ub960(notifier->sd); + struct ub960_rxport *rxport =3D to_ub960_asd(asd)->rxport; + struct device *dev =3D &priv->client->dev; + + dev_dbg(dev, "Unbind %s\n", subdev->name); + + rxport->sd =3D NULL; +} + +static const struct v4l2_async_notifier_operations ub960_notify_ops =3D { + .bound =3D ub960_notify_bound, + .unbind =3D ub960_notify_unbind, +}; + +static int ub960_v4l2_notifier_register(struct ub960_data *priv) +{ + struct device *dev =3D &priv->client->dev; + unsigned int i; + int ret; + + v4l2_async_nf_init(&priv->notifier); + + for (i =3D 0; i < priv->hw_data->num_rxports; ++i) { + struct ub960_rxport *rxport =3D priv->rxports[i]; + struct ub960_asd *asd; + + if (!rxport) + continue; + + asd =3D v4l2_async_nf_add_fwnode(&priv->notifier, rxport->fwnode, + struct ub960_asd); + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev for source %u: %ld", + i, PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + asd->rxport =3D rxport; + } + + priv->notifier.ops =3D &ub960_notify_ops; + + ret =3D v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub960_v4l2_notifier_unregister(struct ub960_data *priv) +{ + struct device *dev =3D &priv->client->dev; + + dev_dbg(dev, "Unregister async notif\n"); + + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub960_create_subdev(struct ub960_data *priv) +{ + struct device *dev =3D &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 =3D &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 =3D priv->ctrl_handler.error; + goto err_free_ctrl; + } + + priv->sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENT= S | + V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function =3D MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops =3D &ub960_entity_ops; + + for (i =3D 0; i < priv->hw_data->num_rxports + priv->hw_data->num_txports= ; i++) { + priv->pads[i].flags =3D ub960_pad_is_sink(priv, i) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + } + + ret =3D 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 =3D priv->sd.ctrl_handler->lock; + + ret =3D v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret =3D ub960_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret =3D 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 =3D { + .name =3D "ds90ub960", + + .reg_bits =3D 8, + .val_bits =3D 8, + + .max_register =3D 0xff, + + /* + * We do locking in the driver to cover the TX/RX port selection and the + * indirect register access. + */ + .disable_locking =3D true, +}; + +static void ub960_reset(struct ub960_data *priv, bool reset_regs) +{ + struct device *dev =3D &priv->client->dev; + unsigned int v; + int ret; + u8 bit; + + bit =3D reset_regs ? BIT(1) : BIT(0); + + ub960_write(priv, UB960_SR_RESET, bit); + + mutex_lock(&priv->reg_lock); + + ret =3D regmap_read_poll_timeout(priv->regmap, UB960_SR_RESET, v, + (v & bit) =3D=3D 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 =3D &priv->client->dev; + int ret; + + priv->regmap =3D devm_regmap_init_i2c(priv->client, &ub960_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->vddio =3D devm_regulator_get(dev, "vddio"); + if (IS_ERR(priv->vddio)) { + dev_err(dev, "cannot get VDDIO regulator\n"); + return PTR_ERR(priv->vddio); + } + + /* get power-down pin from DT */ + priv->pd_gpio =3D + devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(priv->pd_gpio)) { + ret =3D PTR_ERR(priv->pd_gpio); + if (ret !=3D -EPROBE_DEFER) + dev_err(dev, "Cannot get powerdown GPIO (%d)", ret); + return ret; + } + + priv->refclk =3D devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) { + ret =3D PTR_ERR(priv->refclk); + if (ret !=3D -EPROBE_DEFER) + dev_err(dev, "Cannot get REFCLK (%d)", ret); + return ret; + } + + return 0; +} + +static int ub960_enable_core_hw(struct ub960_data *priv) +{ + struct device *dev =3D &priv->client->dev; + u8 rev_mask; + int ret; + u8 dev_sts; + u8 refclk_freq; + + ret =3D regulator_enable(priv->vddio); + if (ret) { + dev_err(dev, "failed to enable VDDIO regulator\n"); + return ret; + } + + ret =3D clk_prepare_enable(priv->refclk); + if (ret) { + dev_err(dev, "Failed to renable refclk: %d\n", ret); + goto err_disable_vddio; + } + + if (priv->pd_gpio) { + gpiod_set_value_cansleep(priv->pd_gpio, 1); + /* wait min 2 ms for reset to complete */ + usleep_range(2000, 5000); + gpiod_set_value_cansleep(priv->pd_gpio, 0); + /* wait min 2 ms for power up to finish */ + usleep_range(2000, 5000); + } + + ub960_reset(priv, true); + + /* Runtime check register accessibility */ + ret =3D 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 =3D ub960_read(priv, UB960_SR_DEVICE_STS, &dev_sts); + if (ret) + goto err_pd_gpio; + + ret =3D 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 =3D &client->dev; + struct ub960_data *priv; + int ret; + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client =3D client; + + priv->hw_data =3D 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 =3D 0xff; + priv->current_read_rxport =3D 0xff; + priv->current_write_rxport_mask =3D 0xff; + priv->current_read_csiport =3D 0xff; + priv->current_write_csiport_mask =3D 0xff; + + ret =3D ub960_get_hw_resources(priv); + if (ret) + goto err_mutex_destroy; + + ret =3D ub960_enable_core_hw(priv); + if (ret) + goto err_mutex_destroy; + + /* release GPIO lock */ + if (priv->hw_data->ub9702) + ub960_update_bits(priv, UB960_SR_RESET, BIT(5), BIT(5)); + + ret =3D ub960_parse_dt(priv); + if (ret) + goto err_disable_core_hw; + + ret =3D ub960_init_tx_ports(priv); + if (ret) + goto err_free_ports; + + ret =3D ub960_init_rx_ports(priv); + if (ret) + goto err_free_ports; + + ub960_reset(priv, false); + + ub960_rxport_wait_locks(priv, 0xf, NULL); + + /* + * Clear any errors caused by switching the RX port settings while + * probing. + */ + ub960_clear_rx_errors(priv); + + ret =3D ub960_init_atr(priv); + if (ret) + goto err_free_ports; + + ret =3D ub960_rxport_add_serializers(priv); + if (ret) + goto err_uninit_atr; + + ret =3D ub960_create_subdev(priv); + if (ret) + goto err_free_sers; + + if (client->irq) { + dev_dbg(dev, "using IRQ %d\n", client->irq); + + ret =3D devm_request_threaded_irq(dev, client->irq, NULL, + ub960_handle_events, + IRQF_ONESHOT, client->name, + priv); + if (ret) { + dev_err(dev, "Cannot enable IRQ (%d)\n", ret); + goto err_irq; + } + + /* Disable GPIO3 as input */ + ub960_update_bits(priv, UB960_SR_GPIO_INPUT_CTL, BIT(3), 0); + /* Enable GPIO3 as output, active low interrupt */ + ub960_write(priv, UB960_SR_GPIO_PIN_CTL(3), 0xd1); + + ub960_write(priv, UB960_SR_INTERRUPT_CTL, + UB960_SR_INTERRUPT_CTL_ALL); + } else { + /* No IRQ, fallback to polling */ + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); + + dev_dbg(dev, "using polling mode\n"); + } + + dev_info(dev, "Successfully probed\n"); + + return 0; + +err_irq: + ub960_destroy_subdev(priv); +err_free_sers: + ub960_rxport_remove_serializers(priv); +err_uninit_atr: + ub960_uninit_atr(priv); +err_free_ports: + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); +err_disable_core_hw: + ub960_disable_core_hw(priv); +err_mutex_destroy: + mutex_destroy(&priv->atr_alias_table.lock); + mutex_destroy(&priv->reg_lock); + return ret; +} + +static void ub960_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct ub960_data *priv =3D sd_to_ub960(sd); + + dev_dbg(&client->dev, "Removing\n"); + + cancel_delayed_work_sync(&priv->poll_work); + + ub960_destroy_subdev(priv); + ub960_rxport_remove_serializers(priv); + ub960_uninit_atr(priv); + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); + ub960_disable_core_hw(priv); + mutex_destroy(&priv->atr_alias_table.lock); + mutex_destroy(&priv->reg_lock); + + dev_dbg(&client->dev, "Remove done\n"); +} + +static const struct ub960_hw_data ds90ub960_hw =3D { + .model =3D "ub960", + .num_rxports =3D 4, + .num_txports =3D 2, +}; + +static const struct ub960_hw_data ds90ub9702_hw =3D { + .model =3D "ub9702", + .num_rxports =3D 4, + .num_txports =3D 2, + .ub9702 =3D true, + .fpdlink4 =3D true, +}; + +static const struct i2c_device_id ub960_id[] =3D { + { "ds90ub960-q1", 0 }, + { "ds90ub9702-q1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub960_id); + +#ifdef CONFIG_OF +static const struct of_device_id ub960_dt_ids[] =3D { + { .compatible =3D "ti,ds90ub960-q1", .data =3D &ds90ub960_hw }, + { .compatible =3D "ti,ds90ub9702-q1", .data =3D &ds90ub9702_hw }, + { } +}; +MODULE_DEVICE_TABLE(of, ub960_dt_ids); +#endif + +static struct i2c_driver ds90ub960_driver =3D { + .probe_new =3D ub960_probe, + .remove =3D ub960_remove, + .id_table =3D ub960_id, + .driver =3D { + .name =3D "ds90ub960", + .owner =3D THIS_MODULE, + .of_match_table =3D of_match_ptr(ub960_dt_ids), + }, +}; + +module_i2c_driver(ds90ub960_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB960-Q1 FPDLink-3 deserializer = driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); 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 + +struct i2c_atr; + +struct ds90ub9xx_platform_data { + u32 port; + struct i2c_atr *atr; + unsigned long bc_rate; +}; + +#endif /* __MEDIA_I2C_DS90UB9XX_H__ */ --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8EF35FA374A for ; Tue, 1 Nov 2022 13:21:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230002AbiKANVe (ORCPT ); Tue, 1 Nov 2022 09:21:34 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52202 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230374AbiKANVV (ORCPT ); Tue, 1 Nov 2022 09:21:21 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 4C7871AF38; Tue, 1 Nov 2022 06:21:07 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8A14E3311; Tue, 1 Nov 2022 14:21:01 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308862; bh=Gyxug2On609My5qjpQfj/jvfv8KUIzuoIoscIqp5IB0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Lpe9AOdMeUGKsHn34Ms7ZsZWBQ91Ufz713B1ZyC0IzGOnNgpP9p6eKaATRXTj1eE8 /40sJzppAnJg8ZNZVNJdppgGHuhgkHgtfBJXZywAhlK3WdinlAQFkFkAjOHvETmekC 40ODyP1Q1Nx6l36CNJKFHR08WcAE3lx/sFYUADFo= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 7/8] media: i2c: add DS90UB913 driver Date: Tue, 1 Nov 2022 15:20:31 +0200 Message-Id: <20221101132032.1542416-8-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add driver for TI DS90UB913 FPDLink-3 Serializer. Signed-off-by: Tomi Valkeinen --- drivers/media/i2c/Kconfig | 6 + drivers/media/i2c/Makefile | 2 +- drivers/media/i2c/ds90ub913.c | 892 ++++++++++++++++++++++++++++++++++ 3 files changed, 899 insertions(+), 1 deletion(-) create mode 100644 drivers/media/i2c/ds90ub913.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 2a78889e3487..17185fd107d6 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1609,6 +1609,12 @@ config VIDEO_DS90UB960 Device driver for the Texas Instruments DS90UB960 FPD-Link III Deserializer =20 +config VIDEO_DS90UB913 + tristate "TI DS90UB913 Serializer" + help + Device driver for the Texas Instruments DS90UB913 + FPD-Link III Serializer. + endmenu =20 endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 2735b00437bb..376886f2ded6 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -143,4 +143,4 @@ obj-$(CONFIG_VIDEO_VS6624) +=3D vs6624.o obj-$(CONFIG_VIDEO_WM8739) +=3D wm8739.o obj-$(CONFIG_VIDEO_WM8775) +=3D wm8775.o obj-$(CONFIG_VIDEO_DS90UB960) +=3D ds90ub960.o - +obj-$(CONFIG_VIDEO_DS90UB913) +=3D ds90ub913.o diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c new file mode 100644 index 000000000000..6001a622e622 --- /dev/null +++ b/drivers/media/i2c/ds90ub913.c @@ -0,0 +1,892 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB913 video serializer + * + * Based on a driver from Luca Ceresoli + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2022 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define UB913_PAD_SINK 0 +#define UB913_PAD_SOURCE 1 + +/* + * UB913 has 4 gpios, but gpios 3 and 4 are reserved for external oscillat= or + * mode. Thus we only support 2 gpios for now. + */ +#define UB913_NUM_GPIOS 2 + +#define UB913_REG_RESET_CTL 0x01 +#define UB913_REG_RESET_CTL_DIGITAL_RESET_1 BIT(1) +#define UB913_REG_RESET_CTL_DIGITAL_RESET_0 BIT(0) + +#define UB913_REG_GENERAL_CFG 0x03 +#define UB913_REG_MODE_SEL 0x05 + +#define UB913_REG_CRC_ERRORS_LSB 0x0a +#define UB913_REG_CRC_ERRORS_MSB 0x0b + +#define UB913_REG_GENERAL_STATUS 0x0c + +#define UB913_REG_GPIO_CFG(n) (0x0d + (n)) +#define UB913_REG_GPIO_CFG_ENABLE(n) BIT(0 + (n) * 4) +#define UB913_REG_GPIO_CFG_DIR_INPUT(n) BIT(1 + (n) * 4) +#define UB913_REG_GPIO_CFG_REMOTE_EN(n) BIT(2 + (n) * 4) +#define UB913_REG_GPIO_CFG_OUT_VAL(n) BIT(3 + (n) * 4) +#define UB913_REG_GPIO_CFG_MASK(n) (0xf << ((n) * 4)) + +#define UB913_REG_SCL_HIGH_TIME 0x11 +#define UB913_REG_SCL_LOW_TIME 0x12 + +#define UB913_REG_PLL_OVR 0x35 + +struct ub913_data { + struct i2c_client *client; + struct regmap *regmap; + struct clk *clkin; + + u32 gpio_func[UB913_NUM_GPIOS]; + + struct gpio_chip gpio_chip; + char gpio_chip_name[64]; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + + u64 enabled_source_streams; + + struct device_node *tx_ep_np; + + struct clk_hw *clkout_clk_hw; + + struct ds90ub9xx_platform_data *plat_data; + + /* Have we succefully called i2c_atr_add_adapter() */ + bool has_i2c_adapter; +}; + +static inline struct ub913_data *sd_to_ub913(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub913_data, sd); +} + +static int ub913_read(const struct ub913_data *priv, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + ret =3D 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 =3D v; + return 0; +} + +static int ub913_write(const struct ub913_data *priv, u8 reg, u8 val) +{ + int ret; + + ret =3D 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 off= set) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int off= set, + int value) +{ + struct ub913_data *priv =3D gpiochip_get_data(gc); + unsigned int reg_idx; + unsigned int field_idx; + int ret; + + reg_idx =3D offset / 2; + field_idx =3D offset % 2; + + ret =3D regmap_update_bits( + priv->regmap, UB913_REG_GPIO_CFG(reg_idx), + UB913_REG_GPIO_CFG_MASK(field_idx), + UB913_REG_GPIO_CFG_ENABLE(field_idx) | + (value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : 0)); + + return ret; +} + +static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int = value) +{ + ub913_gpio_direction_out(gc, offset, value); +} + +static int ub913_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags =3D gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub913_gpiochip_probe(struct ub913_data *priv) +{ + struct device *dev =3D &priv->client->dev; + struct gpio_chip *gc =3D &priv->gpio_chip; + int ret; + + /* Initialize GPIOs 0 and 1 to local control, tri-state */ + ub913_write(priv, UB913_REG_GPIO_CFG(0), 0); + + scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s", + dev_name(dev)); + + gc->label =3D priv->gpio_chip_name; + gc->parent =3D dev; + gc->owner =3D THIS_MODULE; + gc->base =3D -1; + gc->can_sleep =3D 1; + gc->ngpio =3D UB913_NUM_GPIOS; + gc->get_direction =3D ub913_gpio_get_direction; + gc->direction_output =3D ub913_gpio_direction_out; + gc->set =3D ub913_gpio_set; + gc->of_xlate =3D ub913_gpio_of_xlate; + gc->of_node =3D priv->client->dev.of_node; + gc->of_gpio_n_cells =3D 2; + + ret =3D gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub913_gpiochip_remove(struct ub913_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +static int ub913_parse_dt(struct ub913_data *priv) +{ + struct device_node *np =3D priv->client->dev.of_node; + struct device *dev =3D &priv->client->dev; + int ret; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + /* optional, if absent all GPIO pins are unused */ + ret =3D of_property_read_u32_array(np, "gpio-functions", priv->gpio_func, + ARRAY_SIZE(priv->gpio_func)); + if (ret && ret !=3D -EINVAL) + dev_err(dev, "DT: invalid gpio-functions property (%d)", ret); + + return 0; +} + +static const struct regmap_config ub913_regmap_config =3D { + .name =3D "ds90ub913", + .reg_bits =3D 8, + .val_bits =3D 8, + .reg_format_endian =3D REGMAP_ENDIAN_DEFAULT, + .val_format_endian =3D 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 =3D 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 =3D v4l2_subdev_state_xlate_streams( + state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask); + + remote_pad =3D media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]); + + ret =3D v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |=3D 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 =3D sd_to_ub913(sd); + struct media_pad *remote_pad; + int ret; + u64 sink_streams; + + if ((streams_mask & priv->enabled_source_streams) !=3D streams_mask) + return -EALREADY; + + sink_streams =3D v4l2_subdev_state_xlate_streams( + state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask); + + remote_pad =3D media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]); + + ret =3D v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &=3D ~streams_mask; + + return 0; +} + +static int _ub913_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + const struct v4l2_mbus_framefmt format =3D { + .width =3D 640, + .height =3D 480, + .code =3D MEDIA_BUS_FMT_UYVY8_2X8, + .field =3D V4L2_FIELD_NONE, + .colorspace =3D V4L2_COLORSPACE_SRGB, + .ycbcr_enc =3D V4L2_YCBCR_ENC_601, + .quantization =3D V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func =3D 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 =3D v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret =3D 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 =3D sd_to_ub913(sd); + + if (which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_stream= s) + return -EBUSY; + + return _ub913_set_routing(sd, state, routing); +} + +static int ub913_get_source_frame_desc(struct ub913_data *priv, + struct v4l2_mbus_frame_desc *desc) +{ + struct media_pad *pad; + int ret; + + pad =3D media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret =3D v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index, + desc); + if (ret) + return ret; + + return 0; +} + +static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub913_data *priv =3D 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 =3D 0; + + if (pad !=3D 1) /* first tx pad */ + return -EINVAL; + + ret =3D ub913_get_source_frame_desc(priv, &source_fd); + if (ret) + return ret; + + state =3D v4l2_subdev_lock_and_get_active_state(sd); + + routing =3D &state->routing; + + memset(fd, 0, sizeof(*fd)); + + fd->type =3D V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; + + for_each_active_route(routing, route) { + unsigned int j; + + if (route->source_pad !=3D pad) + continue; + + for (j =3D 0; j < source_fd.num_entries; ++j) + if (source_fd.entry[j].stream =3D=3D route->sink_stream) + break; + + if (j =3D=3D source_fd.num_entries) { + dev_err(&priv->client->dev, + "Failed to find stream from source frame desc\n"); + ret =3D -EPIPE; + goto out; + } + + fd->entry[fd->num_entries].stream =3D route->source_stream; + + fd->entry[fd->num_entries].flags =3D + V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd->entry[fd->num_entries].length =3D source_fd.entry[j].length; + fd->entry[fd->num_entries].pixelcode =3D + source_fd.entry[j].pixelcode; + + fd->num_entries++; + } + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub913_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub913_data *priv =3D sd_to_ub913(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad =3D=3D 1) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt =3D v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + /* Propagate to source format */ + fmt =3D v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + return 0; +} + +static int ub913_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] =3D { + { + .sink_pad =3D 0, + .sink_stream =3D 0, + .source_pad =3D 1, + .source_stream =3D 0, + .flags =3D V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing =3D { + .num_routes =3D ARRAY_SIZE(routes), + .routes =3D routes, + }; + + return _ub913_set_routing(sd, state, &routing); +} + +static int ub913_log_status(struct v4l2_subdev *sd) +{ + struct ub913_data *priv =3D sd_to_ub913(sd); + struct device *dev =3D &priv->client->dev; + u8 v, v1, v2; + + ub913_read(priv, UB913_REG_MODE_SEL, &v); + dev_info(dev, "MODE_SEL %#x\n", v); + + ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1); + ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2); + dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8)); + + ub913_read(priv, UB913_REG_GENERAL_STATUS, &v); + dev_info(dev, "GENERAL_STATUS %#x\n", v); + + ub913_read(priv, UB913_REG_PLL_OVR, &v); + dev_info(dev, "PLL_OVR %#x\n", v); + + /* clear CRC errors */ + ub913_read(priv, UB913_REG_GENERAL_CFG, &v); + ub913_write(priv, UB913_REG_GENERAL_CFG, v | BIT(5)); + ub913_write(priv, UB913_REG_GENERAL_CFG, v); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub913_subdev_core_ops =3D { + .log_status =3D ub913_log_status, +}; + +static const struct v4l2_subdev_pad_ops ub913_pad_ops =3D { + .enable_streams =3D ub913_enable_streams, + .disable_streams =3D ub913_disable_streams, + .set_routing =3D ub913_set_routing, + .get_frame_desc =3D ub913_get_frame_desc, + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D ub913_set_fmt, + .init_cfg =3D ub913_init_cfg, +}; + +static const struct v4l2_subdev_ops ub913_subdev_ops =3D { + .core =3D &ub913_subdev_core_ops, + .pad =3D &ub913_pad_ops, +}; + +static const struct media_entity_operations ub913_entity_ops =3D { + .link_validate =3D 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 =3D sd_to_ub913(notifier->sd); + struct device *dev =3D &priv->client->dev; + unsigned int src_pad; + int ret; + + dev_dbg(dev, "Bind %s\n", source_subdev->name); + + ret =3D 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 =3D source_subdev; + src_pad =3D ret; + + ret =3D media_create_pad_link(&source_subdev->entity, src_pad, + &priv->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, src_pad, priv->sd.name); + return ret; + } + + dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad); + + dev_dbg(dev, "All subdevs bound\n"); + + return 0; +} + +static void ub913_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_subdev *asd) +{ + struct ub913_data *priv =3D sd_to_ub913(notifier->sd); + struct device *dev =3D &priv->client->dev; + + dev_dbg(dev, "Unbind %s\n", source_subdev->name); +} + +static const struct v4l2_async_notifier_operations ub913_notify_ops =3D { + .bound =3D ub913_notify_bound, + .unbind =3D ub913_notify_unbind, +}; + +static int ub913_v4l2_notifier_register(struct ub913_data *priv) +{ + struct device *dev =3D &priv->client->dev; + struct v4l2_async_subdev *asd; + struct device_node *ep_node; + int ret; + + dev_dbg(dev, "register async notif\n"); + + ep_node =3D of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + if (!ep_node) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_nf_init(&priv->notifier); + + asd =3D v4l2_async_nf_add_fwnode_remote(&priv->notifier, + of_fwnode_handle(ep_node), + struct v4l2_async_subdev); + + of_node_put(ep_node); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops =3D &ub913_notify_ops; + + ret =3D v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub913_v4l2_nf_unregister(struct ub913_data *priv) +{ + struct device *dev =3D &priv->client->dev; + + dev_dbg(dev, "Unregister async notif\n"); + + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub913_register_clkout(struct ub913_data *priv) +{ + struct device *dev =3D &priv->client->dev; + const char *name; + int ret; + + name =3D kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev)); + + priv->clkout_clk_hw =3D 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 =3D 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 =3D 600 + 300; /* high period + rise time, ns */ + u32 scl_low =3D 1300 + 300; /* low period + fall time, ns */ + unsigned long ref; + int ret; + + ref =3D clk_get_rate(priv->clkin) / 2; + + scl_high =3D div64_u64((u64)scl_high * ref, 1000000000); + scl_low =3D div64_u64((u64)scl_low * ref, 1000000000); + + ret =3D ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high); + if (ret) + return ret; + + ret =3D 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 =3D &priv->client->dev; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle =3D device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + ret =3D i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port, + i2c_handle); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + priv->has_i2c_adapter =3D true; + + return 0; +} + +static void ub913_remove_i2c_adapter(struct ub913_data *priv) +{ + if (priv->has_i2c_adapter) + i2c_atr_del_adapter(priv->plat_data->atr, + priv->plat_data->port); +} + +static int ub913_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct ub913_data *priv; + int ret; + u8 v; + bool mode_override; + u8 mode; + + dev_dbg(dev, "probing, addr 0x%02x\n", client->addr); + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client =3D client; + + priv->plat_data =3D dev_get_platdata(&client->dev); + if (!priv->plat_data) { + dev_err(dev, "Platform data missing\n"); + return -ENODEV; + } + + priv->regmap =3D 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 =3D devm_clk_get(dev, "clkin"); + if (IS_ERR(priv->clkin)) { + ret =3D PTR_ERR(priv->clkin); + if (ret !=3D -EPROBE_DEFER) + dev_err(dev, "Cannot get CLKIN (%d)", ret); + return ret; + } + + ret =3D ub913_parse_dt(priv); + if (ret) + return ret; + + ret =3D ub913_read(priv, UB913_REG_MODE_SEL, &v); + if (ret) + return ret; + + if (!(v & BIT(4))) { + dev_err(dev, "Mode value not stabilized\n"); + return -ENODEV; + } + + mode_override =3D v & BIT(5); + mode =3D v & 0xf; + + dev_dbg(dev, "mode from %s: %#x\n", + mode_override ? "reg" : "deserializer", mode); + + ret =3D ub913_i2c_master_init(priv); + if (ret) { + dev_err(dev, "i2c master init failed: %d\n", ret); + return ret; + } + + ret =3D ub913_gpiochip_probe(priv); + if (ret) { + dev_err(dev, "Failed to init gpiochip\n"); + return ret; + } + + ret =3D ub913_register_clkout(priv); + if (ret) { + dev_err(dev, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops); + priv->sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function =3D MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops =3D &ub913_entity_ops; + + priv->pads[0].flags =3D MEDIA_PAD_FL_SINK; + priv->pads[1].flags =3D MEDIA_PAD_FL_SOURCE; + + ret =3D media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) { + dev_err(dev, "Failed to init pads\n"); + goto err_gpiochip_remove; + } + + priv->tx_ep_np =3D of_graph_get_endpoint_by_regs(dev->of_node, 1, 0); + if (priv->tx_ep_np) + priv->sd.fwnode =3D of_fwnode_handle(priv->tx_ep_np); + + ret =3D v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret =3D ub913_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret =3D v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + ret =3D ub913_add_i2c_adapter(priv); + if (ret) { + dev_err(dev, "failed to add remote i2c adapter\n"); + goto err_unreg_async_subdev; + } + + dev_dbg(dev, "Successfully probed\n"); + + return 0; + +err_unreg_async_subdev: + v4l2_async_unregister_subdev(&priv->sd); +err_unreg_notif: + ub913_v4l2_nf_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + if (priv->tx_ep_np) + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); +err_gpiochip_remove: + ub913_gpiochip_remove(priv); + + return ret; +} + +static void ub913_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct ub913_data *priv =3D sd_to_ub913(sd); + + dev_dbg(&client->dev, "Removing\n"); + + ub913_remove_i2c_adapter(priv); + + v4l2_async_unregister_subdev(&priv->sd); + + ub913_v4l2_nf_unregister(priv); + + v4l2_subdev_cleanup(&priv->sd); + + if (priv->tx_ep_np) + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); + + ub913_gpiochip_remove(priv); +} + +static const struct i2c_device_id ub913_id[] =3D { { "ds90ub913a-q1", 0 },= {} }; +MODULE_DEVICE_TABLE(i2c, ub913_id); + +#ifdef CONFIG_OF +static const struct of_device_id ub913_dt_ids[] =3D { + { .compatible =3D "ti,ds90ub913a-q1", }, + {} +}; +MODULE_DEVICE_TABLE(of, ub913_dt_ids); +#endif + +static struct i2c_driver ds90ub913_driver =3D { + .probe_new =3D ub913_probe, + .remove =3D ub913_remove, + .id_table =3D ub913_id, + .driver =3D { + .name =3D "ds90ub913a", + .owner =3D THIS_MODULE, + .of_match_table =3D of_match_ptr(ub913_dt_ids), + }, +}; + +module_i2c_driver(ds90ub913_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB913 serializer driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); --=20 2.34.1 From nobody Thu Apr 9 01:59:33 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D5F5CFA3745 for ; Tue, 1 Nov 2022 13:22:00 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230522AbiKANV7 (ORCPT ); Tue, 1 Nov 2022 09:21:59 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:52666 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230314AbiKANVb (ORCPT ); Tue, 1 Nov 2022 09:21:31 -0400 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B30EA1B9F1; Tue, 1 Nov 2022 06:21:10 -0700 (PDT) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CAEB8338E; Tue, 1 Nov 2022 14:21:02 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1667308863; bh=aaF0uijIMZbPhKEVAPTc7OEKd8B63ra18ptyRpxbaa8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Vf+ghi27baHud8VcoVEqHsgsPiVUvWUS+ar+vH/U6EBQoQawnDPeCMGUZf4p4Y9rl +yV9U5sGnNQF8nA7KMh/gk70Qtefphx5GlX9osvwJ02ALNul/HSH2yQy1J+kvktVb5 tVuXFrKMsyPf1ZziYCqYHgNbtTIoXmk+ihZ/OElA= From: Tomi Valkeinen To: devicetree@vger.kernel.org, linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org, linux-media@vger.kernel.org Cc: Hans Verkuil , Jacopo Mondi , Kieran Bingham , Laurent Pinchart , Luca Ceresoli , Mark Rutland , Matti Vaittinen , Mauro Carvalho Chehab , Peter Rosin , Rob Herring , Sakari Ailus , Vladimir Zapolskiy , Wolfram Sang , satish.nagireddy@getcruise.com, Tomi Valkeinen Subject: [PATCH v4 8/8] media: i2c: add DS90UB953 driver Date: Tue, 1 Nov 2022 15:20:32 +0200 Message-Id: <20221101132032.1542416-9-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> References: <20221101132032.1542416-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add driver for TI DS90UB953 FPDLink-3 Serializer. Signed-off-by: Tomi Valkeinen --- drivers/media/i2c/Kconfig | 6 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ds90ub953.c | 1607 +++++++++++++++++++++++++++++++++ 3 files changed, 1614 insertions(+) create mode 100644 drivers/media/i2c/ds90ub953.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 17185fd107d6..1563fa0abf0e 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1615,6 +1615,12 @@ config VIDEO_DS90UB913 Device driver for the Texas Instruments DS90UB913 FPD-Link III Serializer. =20 +config VIDEO_DS90UB953 + tristate "TI DS90UB953 Serializer" + help + Device driver for the Texas Instruments DS90UB953 + FPD-Link III Serializer. + endmenu =20 endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 376886f2ded6..377cdf6fa30b 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -144,3 +144,4 @@ obj-$(CONFIG_VIDEO_WM8739) +=3D wm8739.o obj-$(CONFIG_VIDEO_WM8775) +=3D wm8775.o obj-$(CONFIG_VIDEO_DS90UB960) +=3D ds90ub960.o obj-$(CONFIG_VIDEO_DS90UB913) +=3D ds90ub913.o +obj-$(CONFIG_VIDEO_DS90UB953) +=3D ds90ub953.o diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c new file mode 100644 index 000000000000..c7f5d08e07ef --- /dev/null +++ b/drivers/media/i2c/ds90ub953.c @@ -0,0 +1,1607 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB953 video serializer + * + * Based on a driver from Luca Ceresoli + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2022 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define UB953_PAD_SINK 0 +#define UB953_PAD_SOURCE 1 + +#define UB953_NUM_GPIOS 4 + +#define UB953_REG_RESET_CTL 0x01 +#define UB953_REG_RESET_CTL_DIGITAL_RESET_1 BIT(1) +#define UB953_REG_RESET_CTL_DIGITAL_RESET_0 BIT(0) + +#define UB953_REG_GENERAL_CFG 0x02 +#define UB953_REG_MODE_SEL 0x03 + +#define UB953_REG_CLKOUT_CTRL0 0x06 +#define UB953_REG_CLKOUT_CTRL1 0x07 + +#define UB953_REG_SCL_HIGH_TIME 0x0B +#define UB953_REG_SCL_LOW_TIME 0x0C + +#define UB953_REG_LOCAL_GPIO_DATA 0x0d +#define UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(n) BIT(4 + (n)) +#define UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(n) BIT(0 + (n)) + +#define UB953_REG_GPIO_INPUT_CTRL 0x0e +#define UB953_REG_GPIO_INPUT_CTRL_OUT_EN(n) BIT(4 + (n)) +#define UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(n) BIT(0 + (n)) + +#define UB953_REG_REV_MASK_ID 0x50 +#define UB953_REG_GENERAL_STATUS 0x52 + +#define UB953_REG_GPIO_PIN_STS 0x53 +#define UB953_REG_GPIO_PIN_STS_GPIO_STS(n) BIT(0 + (n)) + +#define UB953_REG_BIST_ERR_CNT 0x54 +#define UB953_REG_CRC_ERR_CNT1 0x55 +#define UB953_REG_CRC_ERR_CNT2 0x56 + +#define UB953_REG_CSI_ERR_CNT 0x5c +#define UB953_REG_CSI_ERR_STATUS 0x5D +#define UB953_REG_CSI_ERR_DLANE01 0x5E +#define UB953_REG_CSI_ERR_DLANE23 0x5F +#define UB953_REG_CSI_ERR_CLK_LANE 0x60 +#define UB953_REG_CSI_PKT_HDR_VC_ID 0x61 +#define UB953_REG_PKT_HDR_WC_LSB 0x62 +#define UB953_REG_PKT_HDR_WC_MSB 0x63 +#define UB953_REG_CSI_ECC 0x64 + +#define UB953_REG_IND_ACC_CTL 0xb0 +#define UB953_REG_IND_ACC_ADDR 0xb1 +#define UB953_REG_IND_ACC_DATA 0xb2 + +#define UB953_REG_FPD3_RX_ID(n) (0xf0 + (n)) + +/* Indirect register blocks */ +#define UB953_IND_TARGET_PAT_GEN 0x00 +#define UB953_IND_TARGET_FPD3_TX 0x01 +#define UB953_IND_TARGET_DIE_ID 0x02 + +#define UB953_IND_PGEN_CTL 0x01 +#define UB953_IND_PGEN_CTL_PGEN_ENABLE BIT(0) +#define UB953_IND_PGEN_CFG 0x02 +#define UB953_IND_PGEN_CSI_DI 0x03 +#define UB953_IND_PGEN_LINE_SIZE1 0x04 +#define UB953_IND_PGEN_LINE_SIZE0 0x05 +#define UB953_IND_PGEN_BAR_SIZE1 0x06 +#define UB953_IND_PGEN_BAR_SIZE0 0x07 +#define UB953_IND_PGEN_ACT_LPF1 0x08 +#define UB953_IND_PGEN_ACT_LPF0 0x09 +#define UB953_IND_PGEN_TOT_LPF1 0x0A +#define UB953_IND_PGEN_TOT_LPF0 0x0B +#define UB953_IND_PGEN_LINE_PD1 0x0C +#define UB953_IND_PGEN_LINE_PD0 0x0D +#define UB953_IND_PGEN_VBP 0x0E +#define UB953_IND_PGEN_VFP 0x0F +#define UB953_IND_PGEN_COLOR(n) (0x10 + (n)) /* n <=3D 15 */ + +/* Note: Only sync mode supported for now */ +enum ub953_mode { + /* FPD-Link 3 CSI-2 synchronous mode */ + UB953_MODE_SYNC, + /* FPD-Link 3 CSI-2 non-synchronous mode, external ref clock */ + UB953_MODE_NONSYNC_EXT, + /* FPD-Link 3 CSI-2 non-synchronous mode, internal ref clock */ + UB953_MODE_NONSYNC_INT, + /* FPD-Link 3 DVP mode */ + UB953_MODE_DVP, +}; + +struct ub953_hw_data { + const char *model; + bool ub971; +}; + +struct ub953_data { + const struct ub953_hw_data *hw_data; + + struct i2c_client *client; + struct regmap *regmap; + + u32 num_data_lanes; + + struct gpio_chip gpio_chip; + char gpio_chip_name[64]; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + + struct v4l2_ctrl_handler ctrl_handler; + + u64 enabled_source_streams; + + struct device_node *tx_ep_np; + + /* lock for register access */ + struct mutex reg_lock; + + u8 current_indirect_target; + + struct clk_hw clkout_clk_hw; + + enum ub953_mode mode; + + struct ds90ub9xx_platform_data *plat_data; + + /* Have we succefully called i2c_atr_add_adapter() */ + bool has_i2c_adapter; +}; + +static inline struct ub953_data *sd_to_ub953(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub953_data, sd); +} + +/* + * HW Access + */ + +static int ub953_read(struct ub953_data *priv, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(&priv->client->dev, + "Cannot read register 0x%02x: %d!\n", reg, ret); + else + *val =3D 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 =3D 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 =3D &priv->client->dev; + int ret; + + if (priv->current_indirect_target =3D=3D block) + return 0; + + ret =3D 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 =3D block; + + return 0; +} + +__maybe_unused +static int ub953_read_ind(struct ub953_data *priv, u8 block, u8 reg, u8 *v= al) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D _ub953_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v); + if (ret) + goto out; + + *val =3D v; + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write_ind(struct ub953_data *priv, u8 block, u8 reg, u8 v= al) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D _ub953_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val); + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write_ind16(struct ub953_data *priv, u8 block, u8 reg, u1= 6 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret =3D _ub953_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val >> 8); + if (ret) + goto out; + + ret =3D regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg + 1); + if (ret) + goto out; + + ret =3D 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 off= set) +{ + struct ub953_data *priv =3D gpiochip_get_data(gc); + int ret; + u8 v; + + ret =3D 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 offs= et) +{ + struct ub953_data *priv =3D 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 off= set, + int value) +{ + struct ub953_data *priv =3D gpiochip_get_data(gc); + int ret; + + ret =3D 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 =3D gpiochip_get_data(gc); + int ret; + u8 v; + + ret =3D 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 =3D 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 =3D gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub953_gpiochip_probe(struct ub953_data *priv) +{ + struct device *dev =3D &priv->client->dev; + struct gpio_chip *gc =3D &priv->gpio_chip; + int ret; + + /* Set all GPIOs to local input mode */ + ub953_write(priv, UB953_REG_LOCAL_GPIO_DATA, 0); + ub953_write(priv, UB953_REG_GPIO_INPUT_CTRL, 0xf); + + scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s", + dev_name(dev)); + + gc->label =3D priv->gpio_chip_name; + gc->parent =3D dev; + gc->owner =3D THIS_MODULE; + gc->base =3D -1; + gc->can_sleep =3D 1; + gc->ngpio =3D UB953_NUM_GPIOS; + gc->get_direction =3D ub953_gpio_get_direction; + gc->direction_input =3D ub953_gpio_direction_in; + gc->direction_output =3D ub953_gpio_direction_out; + gc->get =3D ub953_gpio_get; + gc->set =3D ub953_gpio_set; + gc->of_xlate =3D ub953_gpio_of_xlate; + gc->of_node =3D priv->client->dev.of_node; + gc->of_gpio_n_cells =3D 2; + + ret =3D gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub953_gpiochip_remove(struct ub953_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +/* + * V4L2 + */ + +static int _ub953_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + const struct v4l2_mbus_framefmt format =3D { + .width =3D 640, + .height =3D 480, + .code =3D MEDIA_BUS_FMT_UYVY8_2X8, + .field =3D V4L2_FIELD_NONE, + .colorspace =3D V4L2_COLORSPACE_SRGB, + .ycbcr_enc =3D V4L2_YCBCR_ENC_601, + .quantization =3D V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func =3D 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 =3D v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret =3D 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 =3D sd_to_ub953(sd); + + if (which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_stream= s) + return -EBUSY; + + return _ub953_set_routing(sd, state, routing); +} + +static int ub953_get_source_frame_desc(struct ub953_data *priv, + struct v4l2_mbus_frame_desc *desc) +{ + struct media_pad *pad; + int ret; + + pad =3D media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret =3D v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index, + desc); + if (ret) + return ret; + + return 0; +} + +static int ub953_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub953_data *priv =3D 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 =3D 0; + + if (pad !=3D 1) /* first tx pad */ + return -EINVAL; + + ret =3D ub953_get_source_frame_desc(priv, &source_fd); + if (ret) + return ret; + + state =3D v4l2_subdev_lock_and_get_active_state(sd); + + routing =3D &state->routing; + + memset(fd, 0, sizeof(*fd)); + + fd->type =3D V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + for_each_active_route(routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry =3D NULL; + unsigned int j; + + if (route->source_pad !=3D pad) + continue; + + for (j =3D 0; j < source_fd.num_entries; ++j) + if (source_fd.entry[j].stream =3D=3D route->sink_stream) { + source_entry =3D &source_fd.entry[j]; + break; + } + + if (!source_entry) { + dev_err(&priv->client->dev, + "Failed to find stream from source frame desc\n"); + ret =3D -EPIPE; + goto out; + } + + fd->entry[fd->num_entries].stream =3D route->source_stream; + + fd->entry[fd->num_entries].flags =3D + V4L2_MBUS_FRAME_DESC_FL_LEN_MAX; + fd->entry[fd->num_entries].length =3D source_entry->length; + fd->entry[fd->num_entries].pixelcode =3D source_entry->pixelcode; + fd->entry[fd->num_entries].bus.csi2.vc =3D + source_entry->bus.csi2.vc; + fd->entry[fd->num_entries].bus.csi2.dt =3D + 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 =3D sd_to_ub953(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which =3D=3D V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad =3D=3D 1) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt =3D v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + /* Propagate to source format */ + fmt =3D v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt =3D format->format; + + return 0; +} + +static int ub953_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] =3D { + { + .sink_pad =3D 0, + .sink_stream =3D 0, + .source_pad =3D 1, + .source_stream =3D 0, + .flags =3D V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing =3D { + .num_routes =3D ARRAY_SIZE(routes), + .routes =3D routes, + }; + + return _ub953_set_routing(sd, state, &routing); +} + +static int ub953_log_status(struct v4l2_subdev *sd) +{ + struct ub953_data *priv =3D sd_to_ub953(sd); + struct device *dev =3D &priv->client->dev; + u8 v =3D 0, v1 =3D 0, v2 =3D 0; + unsigned int i; + char id[7]; + u8 gpio_local_data; + u8 gpio_input_ctrl; + u8 gpio_pin_sts; + + for (i =3D 0; i < 6; ++i) + ub953_read(priv, UB953_REG_FPD3_RX_ID(i), &id[i]); + id[6] =3D 0; + + dev_info(dev, "ID '%s'\n", id); + + ub953_read(priv, UB953_REG_GENERAL_STATUS, &v); + dev_info(dev, "GENERAL_STATUS %#x\n", v); + + ub953_read(priv, UB953_REG_CRC_ERR_CNT1, &v1); + ub953_read(priv, UB953_REG_CRC_ERR_CNT2, &v2); + dev_info(dev, "CRC error count %u\n", v1 | (v2 << 8)); + + ub953_read(priv, UB953_REG_CSI_ERR_CNT, &v); + dev_info(dev, "CSI error count %u\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_STATUS, &v); + dev_info(dev, "CSI_ERR_STATUS %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v); + dev_info(dev, "CSI_ERR_DLANE01 %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v); + dev_info(dev, "CSI_ERR_DLANE23 %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v); + dev_info(dev, "CSI_ERR_CLK_LANE %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_PKT_HDR_VC_ID, &v); + dev_info(dev, "CSI packet header VC %u ID %u\n", v >> 6, v & 0x3f); + + ub953_read(priv, UB953_REG_PKT_HDR_WC_LSB, &v1); + ub953_read(priv, UB953_REG_PKT_HDR_WC_MSB, &v2); + dev_info(dev, "CSI packet header WC %u\n", (v2 << 8) | v1); + + ub953_read(priv, UB953_REG_CSI_ECC, &v); + dev_info(dev, "CSI ECC %#x\n", v); + + ub953_read(priv, UB953_REG_LOCAL_GPIO_DATA, &gpio_local_data); + ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &gpio_input_ctrl); + ub953_read(priv, UB953_REG_GPIO_PIN_STS, &gpio_pin_sts); + + for (i =3D 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 =3D 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 =3D v4l2_subdev_state_xlate_streams( + state, UB953_PAD_SOURCE, UB953_PAD_SINK, &streams_mask); + + remote_pad =3D media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]); + + ret =3D v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |=3D 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 =3D sd_to_ub953(sd); + struct media_pad *remote_pad; + int ret; + u64 sink_streams; + + if ((streams_mask & priv->enabled_source_streams) !=3D streams_mask) + return -EALREADY; + + sink_streams =3D v4l2_subdev_state_xlate_streams( + state, UB953_PAD_SOURCE, UB953_PAD_SINK, &streams_mask); + + remote_pad =3D media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]); + + ret =3D v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &=3D ~streams_mask; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ub953_pad_ops =3D { + .enable_streams =3D ub953_enable_streams, + .disable_streams =3D ub953_disable_streams, + .set_routing =3D ub953_set_routing, + .get_frame_desc =3D ub953_get_frame_desc, + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D ub953_set_fmt, + .init_cfg =3D ub953_init_cfg, +}; + +static const struct v4l2_subdev_core_ops ub953_subdev_core_ops =3D { + .log_status =3D ub953_log_status, + .subscribe_event =3D v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event =3D v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ub953_subdev_ops =3D { + .core =3D &ub953_subdev_core_ops, + .pad =3D &ub953_pad_ops, +}; + +static const struct media_entity_operations ub953_entity_ops =3D { + .link_validate =3D v4l2_subdev_link_validate, +}; + +enum { + TEST_PATTERN_DISABLED =3D 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[] =3D { + "Disabled", + "1 vertical color bar", + "2 vertical color bars", + "4 vertical color bars", + "8 vertical color bars", +}; + +static void ub953_enable_tpg(struct ub953_data *priv, int tpg_num) +{ + struct v4l2_subdev *sd =3D &priv->sd; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + u8 vbp, vfp; + u16 blank_lines; + u16 width; + u16 height; + + u16 bytespp =3D 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */ + u8 cbars_idx =3D tpg_num - TEST_PATTERN_V_COLOR_BARS_1; + u8 num_cbars =3D 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 =3D 30; + + vbp =3D 33; + vfp =3D 10; + blank_lines =3D vbp + vfp + 2; /* total blanking lines */ + + state =3D v4l2_subdev_get_locked_active_state(sd); + + fmt =3D v4l2_subdev_state_get_stream_format(state, UB953_PAD_SOURCE, 0); + + width =3D fmt->width; + height =3D fmt->height; + + line_size =3D width * bytespp; + bar_size =3D line_size / num_cbars; + act_lpf =3D height; + tot_lpf =3D act_lpf + blank_lines; + line_pd =3D 100000000 / fps / tot_lpf; + + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CTL, + UB953_IND_PGEN_CTL_PGEN_ENABLE); + + /* YUV422 8bit: 2 bytes/block, CSI-2 data type 0x1e */ + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CFG, + cbars_idx << 4 | 0x2); + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CSI_DI, + 0x1e); + + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_LINE_SIZE1, line_size); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_BAR_SIZE1, bar_size); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_ACT_LPF1, act_lpf); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_TOT_LPF1, tot_lpf); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_LINE_PD1, line_pd); + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_VBP, + vbp); + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_VFP, + vfp); +} + +static void ub953_disable_tpg(struct ub953_data *priv) +{ + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CTL, + 0x0); +} + +static int ub953_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ub953_data *priv =3D + container_of(ctrl->handler, struct ub953_data, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + if (ctrl->val =3D=3D 0) + ub953_disable_tpg(priv); + else + ub953_enable_tpg(priv, ctrl->val); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops ub953_ctrl_ops =3D { + .s_ctrl =3D 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 =3D sd_to_ub953(notifier->sd); + struct device *dev =3D &priv->client->dev; + unsigned int src_pad; + int ret; + + dev_dbg(dev, "Bind %s\n", source_subdev->name); + + ret =3D 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 =3D source_subdev; + src_pad =3D ret; + + ret =3D media_create_pad_link( + &source_subdev->entity, src_pad, &priv->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, src_pad, priv->sd.name); + return ret; + } + + dev_dbg(dev, "Bound %s:%u\n", source_subdev->name, src_pad); + + dev_dbg(dev, "All subdevs bound\n"); + + return 0; +} + +static void ub953_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_subdev *asd) +{ + struct ub953_data *priv =3D sd_to_ub953(notifier->sd); + struct device *dev =3D &priv->client->dev; + + dev_dbg(dev, "Unbind %s\n", source_subdev->name); +} + +static const struct v4l2_async_notifier_operations ub953_notify_ops =3D { + .bound =3D ub953_notify_bound, + .unbind =3D ub953_notify_unbind, +}; + +static int ub953_v4l2_notifier_register(struct ub953_data *priv) +{ + struct device *dev =3D &priv->client->dev; + struct v4l2_async_subdev *asd; + struct device_node *ep_node; + int ret; + + dev_dbg(dev, "register async notif\n"); + + ep_node =3D of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + if (!ep_node) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_nf_init(&priv->notifier); + + asd =3D v4l2_async_nf_add_fwnode_remote(&priv->notifier, + of_fwnode_handle(ep_node), + struct v4l2_async_subdev); + + of_node_put(ep_node); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops =3D &ub953_notify_ops; + + ret =3D v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub953_v4l2_notifier_unregister(struct ub953_data *priv) +{ + struct device *dev =3D &priv->client->dev; + + dev_dbg(dev, "Unregister async notif\n"); + + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +/* + * Probing + */ + +static int ub953_i2c_master_init(struct ub953_data *priv) +{ + /* i2c fast mode */ + u32 ref =3D 26250000; + u32 scl_high =3D 915; /* ns */ + u32 scl_low =3D 1641; /* ns */ + int ret; + + scl_high =3D div64_u64((u64)scl_high * ref, 1000000000) - 5; + scl_low =3D div64_u64((u64)scl_low * ref, 1000000000) - 5; + + ret =3D ub953_write(priv, UB953_REG_SCL_HIGH_TIME, scl_high); + if (ret) + return ret; + + ret =3D ub953_write(priv, UB953_REG_SCL_LOW_TIME, scl_low); + if (ret) + return ret; + + return 0; +} + +static int ub953_parse_dt(struct ub953_data *priv) +{ + struct device_node *np =3D priv->client->dev.of_node; + struct device *dev =3D &priv->client->dev; + struct device_node *ep_np; + int ret; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + ep_np =3D of_graph_get_endpoint_by_regs(np, 0, 0); + if (!ep_np) { + dev_err(dev, "OF: no endpoint\n"); + return -ENOENT; + } + + ret =3D of_property_count_u32_elems(ep_np, "data-lanes"); + if (ret <=3D 0) { + dev_err(dev, "OF: failed to parse data-lanes: %d\n", ret); + return ret; + } + + if (ret !=3D 1 && ret !=3D 2 && ret !=3D 4) { + dev_err(dev, "OF: bad number of data-lanes: %d\n", ret); + return -EINVAL; + } + + priv->num_data_lanes =3D ret; + + return 0; +} + +static const struct regmap_config ub953_regmap_config =3D { + .name =3D "ds90ub953", + .reg_bits =3D 8, + .val_bits =3D 8, + .reg_format_endian =3D REGMAP_ENDIAN_DEFAULT, + .val_format_endian =3D REGMAP_ENDIAN_DEFAULT, +}; + +static u64 ub953_get_fc_rate(struct ub953_data *priv) +{ + if (priv->hw_data->ub971) { + switch (priv->mode) { + case UB953_MODE_SYNC: + return priv->plat_data->bc_rate * 160ull; + default: + /* Not supported */ + return 0; + } + } else { + switch (priv->mode) { + case UB953_MODE_SYNC: + return priv->plat_data->bc_rate / 2 * 160ull; + default: + /* Not supported */ + return 0; + } + } +} + +static unsigned long ub953_calc_clkout_ub953(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *hs_div, u8 *m, u8 *n) +{ + /* + * We always use 4 as a pre-divider (HS_CLK_DIV =3D 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 =3D 4; + + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout =3D fc / hs_clk_div * m / n */ + + fc_divided =3D div_u64(fc, hs_clk_div); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res =3D div_u64(fc_divided * mul, div); + + *hs_div =3D hs_clk_div; + *m =3D mul; + *n =3D 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 =3D fc * m / (8 * n) */ + + fc_divided =3D div_u64(fc, 8); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res =3D div_u64(fc_divided * mul, div); + + *m =3D mul; + *n =3D div; + + return res; +} + +static unsigned long ub953_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ub953_data *priv =3D container_of(hw, struct ub953_data, clkout_cl= k_hw); + struct device *dev =3D &priv->client->dev; + u8 ctrl0, ctrl1; + u32 mul, div; + u64 fc_rate; + u32 hs_clk_div; + u64 rate; + int ret; + + ret =3D ub953_read(priv, UB953_REG_CLKOUT_CTRL0, &ctrl0); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL0: %d\n", ret); + return 0; + } + + ret =3D 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 =3D ub953_get_fc_rate(priv); + + if (priv->hw_data->ub971) { + mul =3D ctrl0 & 0x1f; + div =3D ctrl1; + + if (div =3D=3D 0) + return 0; + + rate =3D div_u64(fc_rate * mul, 8 * div); + + dev_dbg(dev, "clkout: fc rate %llu, mul %u, div %u =3D %llu\n", + fc_rate, mul, div, rate); + } else { + mul =3D ctrl0 & 0x1f; + hs_clk_div =3D 1 << (ctrl0 >> 5); + div =3D ctrl1; + + if (div =3D=3D 0) + return 0; + + rate =3D 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 =3D %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 =3D container_of(hw, struct ub953_data, clkout_cl= k_hw); + struct device *dev =3D &priv->client->dev; + unsigned long res; + u64 fc_rate; + u8 hs_div, m, n; + + fc_rate =3D ub953_get_fc_rate(priv); + + if (priv->hw_data->ub971) { + res =3D ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n); + + dev_dbg(dev, + "%s %llu * %u / (8 * %u) =3D %lu (requested %lu)", + __func__, fc_rate, m, n, res, rate); + } else { + res =3D ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n); + + dev_dbg(dev, + "%s %llu / %u * %u / %u =3D %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 =3D container_of(hw, struct ub953_data, clkout_cl= k_hw); + u64 fc_rate; + u8 hs_div, m, n; + unsigned long res; + + fc_rate =3D ub953_get_fc_rate(priv); + + if (priv->hw_data->ub971) { + res =3D 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 =3D 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 =3D { + .recalc_rate =3D ub953_clkout_recalc_rate, + .round_rate =3D ub953_clkout_round_rate, + .set_rate =3D ub953_clkout_set_rate, +}; + +static void ub953_init_clkout_ub953(struct ub953_data *priv) +{ + u64 fc_rate; + u8 hs_div, m, n; + + fc_rate =3D 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 =3D 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 =3D &priv->client->dev; + const struct clk_init_data init =3D { + .name =3D kasprintf(GFP_KERNEL, "ds90%s.%s.clk_out", + priv->hw_data->model, dev_name(dev)), + .ops =3D &ub953_clkout_ops, + }; + int ret; + + /* Initialize clkout to 25MHz by default */ + if (priv->hw_data->ub971) + ub953_init_clkout_ub971(priv); + else + ub953_init_clkout_ub953(priv); + + priv->clkout_clk_hw.init =3D &init; + + ret =3D 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 =3D 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 =3D &priv->client->dev; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle =3D device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + ret =3D i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port, + i2c_handle); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + priv->has_i2c_adapter =3D true; + + return 0; +} + +static void ub953_remove_i2c_adapter(struct ub953_data *priv) +{ + if (priv->has_i2c_adapter) + i2c_atr_del_adapter(priv->plat_data->atr, + priv->plat_data->port); +} + +static int ub953_probe(struct i2c_client *client) +{ + struct device *dev =3D &client->dev; + struct ub953_data *priv; + int ret; + u8 v; + bool mode_override; + + dev_dbg(dev, "probing, addr 0x%02x\n", client->addr); + + priv =3D devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client =3D client; + + priv->hw_data =3D of_device_get_match_data(dev); + if (!priv->hw_data) + return -ENODEV; + + priv->plat_data =3D 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 =3D 0xff; + + priv->regmap =3D devm_regmap_init_i2c(client, &ub953_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Failed to init regmap\n"); + ret =3D PTR_ERR(priv->regmap); + goto err_mutex_destroy; + } + + ret =3D ub953_parse_dt(priv); + if (ret) + goto err_mutex_destroy; + + ret =3D ub953_read(priv, UB953_REG_MODE_SEL, &v); + if (ret) + goto err_mutex_destroy; + + if (!(v & BIT(3))) { + dev_err(dev, "Mode value not stabilized\n"); + ret =3D -ENODEV; + goto err_mutex_destroy; + } + + mode_override =3D v & BIT(4); + + switch (v & 0x7) { + case 0: + priv->mode =3D UB953_MODE_SYNC; + break; + case 2: + priv->mode =3D UB953_MODE_NONSYNC_EXT; + break; + case 3: + priv->mode =3D UB953_MODE_NONSYNC_INT; + break; + case 5: + priv->mode =3D UB953_MODE_DVP; + break; + default: + dev_err(dev, "Illegal mode in mode register\n"); + ret =3D -ENODEV; + goto err_mutex_destroy; + } + + dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap", + priv->mode); + + if (priv->mode !=3D UB953_MODE_SYNC) { + dev_err(dev, "Only synchronous mode supported\n"); + ret =3D -ENODEV; + goto err_mutex_destroy; + } + + ret =3D ub953_read(priv, UB953_REG_REV_MASK_ID, &v); + if (ret) { + dev_err(dev, "Failed to read revision: %d", ret); + goto err_mutex_destroy; + } + + dev_info(dev, "Found %s rev/mask %#04x\n", priv->hw_data->model, v); + + ret =3D ub953_read(priv, UB953_REG_GENERAL_CFG, &v); + if (ret) + goto err_mutex_destroy; + + dev_dbg(dev, "i2c strap setting %s V\n", (v & 1) ? "1.8" : "3.3"); + + ret =3D ub953_i2c_master_init(priv); + if (ret) { + dev_err(dev, "i2c init failed: %d\n", ret); + goto err_mutex_destroy; + } + + ret =3D ub953_gpiochip_probe(priv); + if (ret) { + dev_err(dev, "Failed to init gpiochip\n"); + goto err_mutex_destroy; + } + + ub953_write(priv, UB953_REG_GENERAL_CFG, + (1 << 6) | /* continuous clk */ + ((priv->num_data_lanes - 1) << 4) | + (1 << 1)); /* CRC TX gen */ + + ret =3D ub953_register_clkout(priv); + if (ret) { + dev_err(dev, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops); + + v4l2_ctrl_handler_init(&priv->ctrl_handler, + ARRAY_SIZE(ub953_tpg_qmenu) - 1); + priv->sd.ctrl_handler =3D &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub953_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ub953_tpg_qmenu) - 1, 0, 0, + ub953_tpg_qmenu); + + if (priv->ctrl_handler.error) { + ret =3D priv->ctrl_handler.error; + goto err_gpiochip_remove; + } + + priv->sd.flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENT= S | + V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function =3D MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops =3D &ub953_entity_ops; + + priv->pads[0].flags =3D MEDIA_PAD_FL_SINK; + priv->pads[1].flags =3D MEDIA_PAD_FL_SOURCE; + + ret =3D media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) { + dev_err(dev, "Failed to init pads\n"); + goto err_remove_ctrls; + } + + priv->tx_ep_np =3D of_graph_get_endpoint_by_regs(dev->of_node, 1, 0); + priv->sd.fwnode =3D of_fwnode_handle(priv->tx_ep_np); + + priv->sd.state_lock =3D priv->sd.ctrl_handler->lock; + + ret =3D v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret =3D ub953_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret =3D v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + ret =3D ub953_add_i2c_adapter(priv); + if (ret) { + dev_err(dev, "failed to add remote i2c adapter\n"); + goto err_unreg_async_subdev; + } + + dev_dbg(dev, "Successfully probed\n"); + + return 0; + +err_unreg_async_subdev: + v4l2_async_unregister_subdev(&priv->sd); +err_unreg_notif: + ub953_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + if (priv->tx_ep_np) + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); +err_remove_ctrls: + v4l2_ctrl_handler_free(&priv->ctrl_handler); +err_gpiochip_remove: + ub953_gpiochip_remove(priv); +err_mutex_destroy: + mutex_destroy(&priv->reg_lock); + + return ret; +} + +static void ub953_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd =3D i2c_get_clientdata(client); + struct ub953_data *priv =3D sd_to_ub953(sd); + + dev_dbg(&client->dev, "Removing\n"); + + ub953_remove_i2c_adapter(priv); + + v4l2_async_unregister_subdev(&priv->sd); + + ub953_v4l2_notifier_unregister(priv); + + v4l2_subdev_cleanup(&priv->sd); + + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); + + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + ub953_gpiochip_remove(priv); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub953_hw_data ds90ub953_hw =3D { + .model =3D "ub953", +}; + +static const struct ub953_hw_data ds90ub971_hw =3D { + .model =3D "ub971", + .ub971 =3D true, +}; + +static const struct i2c_device_id ub953_id[] =3D { + { "ds90ub953-q1", 0 }, + { "ds90ub971-q1", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ub953_id); + +#ifdef CONFIG_OF +static const struct of_device_id ub953_dt_ids[] =3D { + { .compatible =3D "ti,ds90ub953-q1", .data =3D &ds90ub953_hw }, + { .compatible =3D "ti,ds90ub971-q1", .data =3D &ds90ub971_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, ub953_dt_ids); +#endif + +static struct i2c_driver ds90ub953_driver =3D { + .probe_new =3D ub953_probe, + .remove =3D ub953_remove, + .id_table =3D ub953_id, + .driver =3D { + .name =3D "ds90ub953", + .owner =3D THIS_MODULE, + .of_match_table =3D of_match_ptr(ub953_dt_ids), + }, +}; + +module_i2c_driver(ds90ub953_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB953 serializer driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); --=20 2.34.1