From patchwork Fri Jan 20 15:34:11 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46422 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp268661wrn; Fri, 20 Jan 2023 07:40:50 -0800 (PST) X-Google-Smtp-Source: AMrXdXt/kAWX9xudKnbtBMslgpbZtZJIhfXa5JHLoSKsHltUq70aK3bIqa0FK3nN8I5tT7qCaRSX X-Received: by 2002:a17:906:d8ab:b0:86e:3531:555f with SMTP id qc11-20020a170906d8ab00b0086e3531555fmr17466828ejb.32.1674229249768; Fri, 20 Jan 2023 07:40:49 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229249; cv=none; d=google.com; s=arc-20160816; b=TFFF4WVm6WRDWQ5bVqsXh5lf3l9bE8Vs72BGRtC/s5IyNuw1VDNzmQkej4xrOu5QbC 4IEB/MOaZEHR6QlPtnx/LOzqBRQCIpRP2mUCjehSgUj5o6NoCJ1VVPaP2xBfAy82GaTJ KUBCz+ElfSSvo9SdwOEy47t3RX1BCFKfqYuiIP1kI3NFa6GZb0d51kWymjWdRNPu7aue lpn0m6tjv42yLz/OeyA1bwzdMdUc2bl3mdALWt7dUJO5COUrmOvLgn08P5q0Z8/fiS4z sEiZUOzarMvRxFq+fauzCQu52oSM6tCu6/CRY/aP1FBenWmV3pPgWfh8WC0rOxOB2BIz jj0A== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=IVhJBul0DoZYvY8UT8FRccCXgaJRaJIirmGxbLI+ZkA=; b=c3np7OGsthMMUNOG7Y+HYeG9sYXnqrWp7U1IfBNv1ybSQLK79/PuuoTVIrk30wGefO 2RFIb5izy32W0F/jVA+KyrzQjWpioDYW4ujlOr6ZYnpBetolRR/Dujcekqk+x8imk9RU wMX3gtxilBht1MBtN8DvDkoZ1L2ajUJ3FWi7FlmWWKYts3Yi9Xer4TpowBLX+mPBc7ga 5o5dfYz+KU09uyUWcHThk0cT5jOvA93VGxMyRKrxS6Tzh/G6NFUb77sr8VOs03NQQVC1 N0wDDHXd0iGjXgk0fr0e0rd8ylHcKp6t3IX5vTHad9R82EpRYOx3SMMVT2aitveD6Zmp ZSVQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b="bXU9dL4/"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id hp7-20020a1709073e0700b0086e70cdc492si20051074ejc.1.2023.01.20.07.40.25; Fri, 20 Jan 2023 07:40:49 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b="bXU9dL4/"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231472AbjATPem (ORCPT + 99 others); Fri, 20 Jan 2023 10:34:42 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:36980 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229568AbjATPel (ORCPT ); Fri, 20 Jan 2023 10:34:41 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3EED5C79C2; Fri, 20 Jan 2023 07:34:38 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id AE3DC558; Fri, 20 Jan 2023 16:34:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228876; bh=EqRKPDLyIinQlGT6KdxR3g5yAoqSvxED7NBFKsRj4b4=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bXU9dL4/Lclh8TDWW7sPstrFo+hr87W/2VOHrPC2OGkNnBF25x41q35HM+aQ0HMBS avxPLybt+WYdljgNCcDlTreLGNTSoK4BMhyDgfgByFOYSRawixNkpW/B1C1Hc8zyw1 LDN6c5w56SV7QSPComKRYqXf92Qw+itk94d7R72I= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Luca Ceresoli , Tomi Valkeinen Subject: [PATCH v8 1/7] i2c: add I2C Address Translator (ATR) support Date: Fri, 20 Jan 2023 17:34:11 +0200 Message-Id: <20230120153417.1156207-2-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556609865517902?= X-GMAIL-MSGID: =?utf-8?q?1755556609865517902?= 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 it 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 | 83 +++++ MAINTAINERS | 8 + drivers/i2c/Kconfig | 9 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-atr.c | 532 ++++++++++++++++++++++++++++ include/linux/i2c-atr.h | 113 ++++++ 7 files changed, 747 insertions(+) create mode 100644 Documentation/i2c/muxes/i2c-atr.rst create mode 100644 drivers/i2c/i2c-atr.c create mode 100644 include/linux/i2c-atr.h diff --git a/Documentation/i2c/index.rst b/Documentation/i2c/index.rst index 6270f1fd7d4e..aaf33d1315f4 100644 --- a/Documentation/i2c/index.rst +++ b/Documentation/i2c/index.rst @@ -16,6 +16,7 @@ Introduction instantiating-devices busses/index i2c-topology + muxes/i2c-atr muxes/i2c-mux-gpio i2c-sysfs diff --git a/Documentation/i2c/muxes/i2c-atr.rst b/Documentation/i2c/muxes/i2c-atr.rst new file mode 100644 index 000000000000..c7e060ca682d --- /dev/null +++ b/Documentation/i2c/muxes/i2c-atr.rst @@ -0,0 +1,83 @@ +.. SPDX-License-Identifier: GPL-2.0 + +===================== +Kernel driver i2c-atr +===================== + +Author: Luca Ceresoli + +Description +----------- + +An I2C Address Translator (ATR) is a device with an I2C slave parent +("upstream") port and N I2C master child ("downstream") ports, and +forwards transactions from upstream to the appropriate downstream port +with a modified slave address. The address used on the parent bus is +called the "alias" and is (potentially) different from the physical +slave address of the child bus. Address translation is done by the +hardware. + +An ATR looks similar to an i2c-mux except: + - the address on the parent and child busses can be different + - there is normally no need to select the child port; the alias used on the + parent bus implies it + +The ATR functionality can be provided by a chip with many other +features. This file provides a helper to implement an ATR within your +driver. + +The ATR creates a new I2C "child" adapter on each child bus. Adding +devices on the child bus ends up in invoking the driver code to select +an available alias. Maintaining an appropriate pool of available aliases +and picking one for each new device is up to the driver implementer. The +ATR maintains an table of currently assigned alias and uses it to modify +all I2C transactions directed to devices on the child buses. + +A typical example follows. + +Topology:: + + Slave X @ 0x10 + .-----. | + .-----. | |---+---- B + | CPU |--A--| ATR | + `-----' | |---+---- C + `-----' | + Slave Y @ 0x10 + +Alias table: + +.. table:: + + ====== ===== + Client Alias + ====== ===== + X 0x20 + Y 0x30 + ====== ===== + +Transaction: + + - Slave X driver sends a transaction (on adapter B), slave address 0x10 + - ATR driver rewrites messages with address 0x20, forwards to adapter A + - Physical I2C transaction on bus A, slave address 0x20 + - ATR chip propagates transaction on bus B with address translated to 0x10 + - Slave X chip replies on bus B + - ATR chip forwards reply on bus A + - ATR driver rewrites messages with address 0x10 + - Slave X driver gets back the msgs[], with reply and address 0x10 + +Usage: + + 1. In your driver (typically in the probe function) add an ATR by + calling i2c_atr_new() passing your attach/detach callbacks + 2. When the attach callback is called pick an appropriate alias, + configure it in your chip and return the chosen alias in the + alias_id parameter + 3. When the detach callback is called, deconfigure the alias from + your chip and put it back in the pool for later usage + +I2C ATR functions and data structures +------------------------------------- + +.. kernel-doc:: include/linux/i2c-atr.h diff --git a/MAINTAINERS b/MAINTAINERS index 1daadaa4d48b..ba716f2861cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9563,6 +9563,14 @@ L: linux-acpi@vger.kernel.org S: Maintained F: drivers/i2c/i2c-core-acpi.c +I2C ADDRESS TRANSLATOR (ATR) +M: Tomi Valkeinen +R: 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 source "drivers/i2c/muxes/Kconfig" +config I2C_ATR + tristate "I2C Address Translator (ATR) support" + help + Enable support for I2C Address Translator (ATR) chips. + + An ATR allows accessing multiple I2C busses from a single + physical bus via address translation instead of bus selection as + i2c-muxes do. + config I2C_HELPER_AUTO bool "Autoselect pertinent helper modules" default y diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index c1d493dc9bac..3f71ce4711e3 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -13,6 +13,7 @@ i2c-core-$(CONFIG_OF) += i2c-core-of.o obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o obj-$(CONFIG_I2C_CHARDEV) += i2c-dev.o obj-$(CONFIG_I2C_MUX) += i2c-mux.o +obj-$(CONFIG_I2C_ATR) += i2c-atr.o obj-y += algos/ busses/ muxes/ obj-$(CONFIG_I2C_STUB) += i2c-stub.o obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o diff --git a/drivers/i2c/i2c-atr.c b/drivers/i2c/i2c-atr.c new file mode 100644 index 000000000000..c872647ae808 --- /dev/null +++ b/drivers/i2c/i2c-atr.c @@ -0,0 +1,532 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * I2C Address Translator + * + * Copyright (c) 2019,2022 Luca Ceresoli + * Copyright (c) 2022,2023 Tomi Valkeinen + * + * Originally based on i2c-mux.c + */ + +#include +#include +#include +#include +#include +#include +#include + +#define ATR_MAX_ADAPTERS 100 /* Just a sanity limit */ +#define ATR_MAX_SYMLINK_LEN 11 /* Longest name is 10 chars: "channel-99" */ + +/** + * struct i2c_atr_cli2alias_pair - Holds the alias assigned to a client. + * @node: List node + * @client: Pointer to the client on the child bus + * @alias: I2C alias address assigned by the driver. + * This is the address that will be used to issue I2C transactions + * on the parent (physical) bus. + */ +struct i2c_atr_cli2alias_pair { + struct list_head node; + const struct i2c_client *client; + u16 alias; +}; + +/** + * struct i2c_atr_chan - Data for a channel. + * @adap: The &struct i2c_adapter for the channel + * @atr: The parent I2C ATR + * @chan_id: The ID of this channel + * @alias_list: List of @struct i2c_atr_cli2alias_pair containing the + * assigned aliases + * @orig_addrs_lock: Mutex protecting @orig_addrs + * @orig_addrs: Buffer used to store the original addresses during transmit + * @orig_addrs_size: Size of @orig_addrs + */ +struct i2c_atr_chan { + struct i2c_adapter adap; + struct i2c_atr *atr; + u32 chan_id; + + struct list_head alias_list; + + /* Lock orig_addrs during xfer */ + struct mutex orig_addrs_lock; + u16 *orig_addrs; + unsigned int orig_addrs_size; +}; + +/** + * struct i2c_atr - The I2C ATR instance + * @parent: The parent &struct i2c_adapter + * @dev: The device that owns the I2C ATR instance + * @ops: &struct i2c_atr_ops + * @priv: Private driver data, set with i2c_atr_set_driver_data() + * @algo: The &struct i2c_algorithm for adapters + * @lock: Lock for the I2C bus segment (see &struct i2c_lock_operations) + * @max_adapters: Maximum number of adapters this I2C ATR can have + * @adapter: Array of adapters + */ +struct i2c_atr { + struct i2c_adapter *parent; + struct device *dev; + const struct i2c_atr_ops *ops; + + void *priv; + + struct i2c_algorithm algo; + /* lock for the I2C bus segment (see struct i2c_lock_operations) */ + struct mutex lock; + int max_adapters; + + struct notifier_block i2c_nb; + + struct i2c_adapter *adapter[]; +}; + +static struct i2c_atr_cli2alias_pair * +i2c_atr_find_mapping_by_client(const struct list_head *list, + const struct i2c_client *client) +{ + struct i2c_atr_cli2alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client == client) + return c2a; + } + + return NULL; +} + +static struct i2c_atr_cli2alias_pair * +i2c_atr_find_mapping_by_addr(const struct list_head *list, u16 phys_addr) +{ + struct i2c_atr_cli2alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client->addr == phys_addr) + return c2a; + } + + return NULL; +} + +/* + * Replace all message addresses with their aliases, saving the original + * addresses. + * + * This function is internal for use in i2c_atr_master_xfer(). It must be + * followed by i2c_atr_unmap_msgs() to restore the original addresses. + */ +static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, + int num) +{ + struct i2c_atr *atr = chan->atr; + static struct i2c_atr_cli2alias_pair *c2a; + int i; + + /* Ensure we have enough room to save the original addresses */ + if (unlikely(chan->orig_addrs_size < num)) { + u16 *new_buf; + + new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL); + if (!new_buf) + return -ENOMEM; + + kfree(chan->orig_addrs); + chan->orig_addrs = new_buf; + chan->orig_addrs_size = num; + } + + for (i = 0; i < num; i++) { + chan->orig_addrs[i] = msgs[i].addr; + + c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, + msgs[i].addr); + if (!c2a) { + dev_err(atr->dev, "client 0x%02x not mapped!\n", + msgs[i].addr); + return -ENXIO; + } + + msgs[i].addr = c2a->alias; + } + + return 0; +} + +/* + * Restore all message address aliases with the original addresses. This + * function is internal for use in i2c_atr_master_xfer(). + * + * @see i2c_atr_map_msgs() + */ +static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, + int num) +{ + int i; + + for (i = 0; i < num; i++) + msgs[i].addr = chan->orig_addrs[i]; +} + +static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct i2c_atr_chan *chan = adap->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_adapter *parent = atr->parent; + int ret; + + /* Translate addresses */ + mutex_lock(&chan->orig_addrs_lock); + + ret = i2c_atr_map_msgs(chan, msgs, num); + if (ret < 0) + goto err_unlock; + + /* Perform the transfer */ + ret = i2c_transfer(parent, msgs, num); + + /* Restore addresses */ + i2c_atr_unmap_msgs(chan, msgs, num); + +err_unlock: + mutex_unlock(&chan->orig_addrs_lock); + + return ret; +} + +static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct i2c_atr_chan *chan = adap->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_adapter *parent = atr->parent; + struct i2c_atr_cli2alias_pair *c2a; + + c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, addr); + if (!c2a) { + dev_err(atr->dev, "client 0x%02x not mapped!\n", addr); + return -ENXIO; + } + + return i2c_smbus_xfer(parent, c2a->alias, flags, read_write, command, + size, data); +} + +static u32 i2c_atr_functionality(struct i2c_adapter *adap) +{ + struct i2c_atr_chan *chan = adap->algo_data; + struct i2c_adapter *parent = chan->atr->parent; + + return parent->algo->functionality(parent); +} + +static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + + mutex_lock(&atr->lock); +} + +static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + + return mutex_trylock(&atr->lock); +} + +static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + + mutex_unlock(&atr->lock); +} + +static const struct i2c_lock_operations i2c_atr_lock_ops = { + .lock_bus = i2c_atr_lock_bus, + .trylock_bus = i2c_atr_trylock_bus, + .unlock_bus = i2c_atr_unlock_bus, +}; + +static int i2c_atr_attach_client(struct i2c_adapter *adapter, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_atr_cli2alias_pair *c2a; + u16 alias_id; + int ret; + + c2a = kzalloc(sizeof(*c2a), GFP_KERNEL); + if (!c2a) + return -ENOMEM; + + ret = atr->ops->attach_client(atr, chan->chan_id, client, &alias_id); + if (ret) + goto err_free; + + c2a->client = client; + c2a->alias = alias_id; + list_add(&c2a->node, &chan->alias_list); + + return 0; + +err_free: + kfree(c2a); + + return ret; +} + +static void i2c_atr_detach_client(struct i2c_adapter *adapter, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_atr_cli2alias_pair *c2a; + + atr->ops->detach_client(atr, chan->chan_id, client); + + c2a = i2c_atr_find_mapping_by_client(&chan->alias_list, client); + if (c2a) { + list_del(&c2a->node); + kfree(c2a); + } +} + +static int i2c_atr_bus_notifier_call(struct notifier_block *nb, + unsigned long event, void *device) +{ + struct i2c_atr *atr = container_of(nb, struct i2c_atr, i2c_nb); + struct device *dev = device; + struct i2c_client *client; + u32 chan_id; + int ret; + + client = i2c_verify_client(dev); + if (!client) + return NOTIFY_DONE; + + /* Is the client in one of our adapters? */ + for (chan_id = 0; chan_id < atr->max_adapters; ++chan_id) { + if (client->adapter == atr->adapter[chan_id]) + break; + } + + if (chan_id == atr->max_adapters) + return NOTIFY_DONE; + + switch (event) { + case BUS_NOTIFY_ADD_DEVICE: + ret = i2c_atr_attach_client(client->adapter, client); + if (ret) + dev_err(atr->dev, + "Failed to attach remote client '%s': %d\n", + dev_name(dev), ret); + break; + + case BUS_NOTIFY_DEL_DEVICE: + i2c_atr_detach_client(client->adapter, client); + break; + + default: + break; + } + + return NOTIFY_DONE; +} + +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters) +{ + struct i2c_atr *atr; + int ret; + + if (max_adapters > ATR_MAX_ADAPTERS) + return ERR_PTR(-EINVAL); + + if (!ops || !ops->attach_client || !ops->detach_client) + return ERR_PTR(-EINVAL); + + atr = kzalloc(struct_size(atr, adapter, max_adapters), GFP_KERNEL); + if (!atr) + return ERR_PTR(-ENOMEM); + + mutex_init(&atr->lock); + + atr->parent = parent; + atr->dev = dev; + atr->ops = ops; + atr->max_adapters = max_adapters; + + if (parent->algo->master_xfer) + atr->algo.master_xfer = i2c_atr_master_xfer; + if (parent->algo->smbus_xfer) + atr->algo.smbus_xfer = i2c_atr_smbus_xfer; + atr->algo.functionality = i2c_atr_functionality; + + atr->i2c_nb.notifier_call = i2c_atr_bus_notifier_call; + ret = bus_register_notifier(&i2c_bus_type, &atr->i2c_nb); + if (ret) { + mutex_destroy(&atr->lock); + return ERR_PTR(ret); + } + + return atr; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_new, I2C_ATR); + +void i2c_atr_delete(struct i2c_atr *atr) +{ + bus_unregister_notifier(&i2c_bus_type, &atr->i2c_nb); + mutex_destroy(&atr->lock); + kfree(atr); +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, I2C_ATR); + +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct fwnode_handle *bus_handle) +{ + struct i2c_adapter *parent = atr->parent; + struct device *dev = atr->dev; + struct i2c_atr_chan *chan; + char symlink_name[ATR_MAX_SYMLINK_LEN]; + int ret; + + if (chan_id >= atr->max_adapters) { + dev_err(dev, "No room for more i2c-atr adapters\n"); + return -EINVAL; + } + + if (atr->adapter[chan_id]) { + dev_err(dev, "Adapter %d already present\n", chan_id); + return -EEXIST; + } + + chan = kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + chan->atr = atr; + chan->chan_id = chan_id; + INIT_LIST_HEAD(&chan->alias_list); + mutex_init(&chan->orig_addrs_lock); + + snprintf(chan->adap.name, sizeof(chan->adap.name), "i2c-%d-atr-%d", + i2c_adapter_id(parent), chan_id); + chan->adap.owner = THIS_MODULE; + chan->adap.algo = &atr->algo; + chan->adap.algo_data = chan; + chan->adap.dev.parent = dev; + chan->adap.retries = parent->retries; + chan->adap.timeout = parent->timeout; + chan->adap.quirks = parent->quirks; + chan->adap.lock_ops = &i2c_atr_lock_ops; + + if (bus_handle) { + device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle)); + } else { + struct fwnode_handle *atr_node; + struct fwnode_handle *child; + u32 reg; + + atr_node = device_get_named_child_node(dev, "i2c-atr"); + + fwnode_for_each_child_node(atr_node, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + continue; + if (chan_id == reg) + break; + } + + device_set_node(&chan->adap.dev, child); + fwnode_handle_put(atr_node); + } + + atr->adapter[chan_id] = &chan->adap; + + ret = i2c_add_adapter(&chan->adap); + if (ret) { + dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", + chan_id, ret); + goto err_fwnode_put; + } + + snprintf(symlink_name, sizeof(symlink_name), "channel-%u", + chan->chan_id); + + ret = sysfs_create_link(&chan->adap.dev.kobj, &dev->kobj, "atr_device"); + if (ret) + dev_warn(dev, "can't create symlink to atr device\n"); + ret = sysfs_create_link(&dev->kobj, &chan->adap.dev.kobj, symlink_name); + if (ret) + dev_warn(dev, "can't create symlink for channel %u\n", chan_id); + + dev_dbg(dev, "Added ATR child bus %d\n", i2c_adapter_id(&chan->adap)); + + return 0; + +err_fwnode_put: + fwnode_handle_put(dev_fwnode(&chan->adap.dev)); + mutex_destroy(&chan->orig_addrs_lock); + kfree(chan); + return ret; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_add_adapter, I2C_ATR); + +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id) +{ + char symlink_name[ATR_MAX_SYMLINK_LEN]; + struct i2c_adapter *adap; + struct i2c_atr_chan *chan; + struct fwnode_handle *fwnode; + struct device *dev = atr->dev; + + adap = atr->adapter[chan_id]; + if (!adap) + return; + + chan = adap->algo_data; + fwnode = dev_fwnode(&adap->dev); + + dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap)); + + snprintf(symlink_name, sizeof(symlink_name), "channel-%u", + chan->chan_id); + sysfs_remove_link(&dev->kobj, symlink_name); + sysfs_remove_link(&chan->adap.dev.kobj, "atr_device"); + + i2c_del_adapter(adap); + + atr->adapter[chan_id] = NULL; + + fwnode_handle_put(fwnode); + mutex_destroy(&chan->orig_addrs_lock); + kfree(chan->orig_addrs); + kfree(chan); +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_del_adapter, I2C_ATR); + +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data) +{ + atr->priv = data; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_set_driver_data, I2C_ATR); + +void *i2c_atr_get_driver_data(struct i2c_atr *atr) +{ + return atr->priv; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_get_driver_data, I2C_ATR); + +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +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..721d08a6ff9b --- /dev/null +++ b/include/linux/i2c-atr.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * I2C Address Translator + * + * Copyright (c) 2019,2022 Luca Ceresoli + * Copyright (c) 2022,2023 Tomi Valkeinen + * + * Based on i2c-mux.h + */ + +#ifndef _LINUX_I2C_ATR_H +#define _LINUX_I2C_ATR_H + +#include +#include + +struct device; +struct fwnode_handle; +struct i2c_atr; + +/** + * struct i2c_atr_ops - Callbacks from ATR to the device driver. + * @attach_client: Notify the driver of a new device connected on a child + * bus. The driver must choose an I2C alias, configure the + * hardware to use it and return it in `alias_id`. + * @detach_client: Notify the driver of a device getting disconnected. The + * driver must configure the hardware to stop using the + * alias. + * + * All these functions return 0 on success, a negative error code otherwise. + */ +struct i2c_atr_ops { + int (*attach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client, u16 *alias_id); + void (*detach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client); +}; + +/** + * i2c_atr_new() - Allocate and initialize an I2C ATR helper. + * @parent: The parent (upstream) adapter + * @dev: The device acting as an ATR + * @ops: Driver-specific callbacks + * @max_adapters: Maximum number of child adapters + * + * The new ATR helper is connected to the parent adapter but has no child + * adapters. Call i2c_atr_add_adapter() to add some. + * + * Call i2c_atr_delete() to remove. + * + * Return: pointer to the new ATR helper object, or ERR_PTR + */ +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters); + +/** + * i2c_atr_delete - Delete an I2C ATR helper. + * @atr: I2C ATR helper to be deleted. + * + * Precondition: all the adapters added with i2c_atr_add_adapter() mumst be + * removed by calling i2c_atr_del_adapter(). + */ +void i2c_atr_delete(struct i2c_atr *atr); + +/** + * i2c_atr_add_adapter - Create a child ("downstream") I2C bus. + * @atr: The I2C ATR + * @chan_id: Index of the new adapter (0 .. max_adapters-1). This value is + * passed to the callbacks in `struct i2c_atr_ops`. + * @bus_handle: The fwnode handle that points to the adapter's i2c + * peripherals, or NULL. + * + * After calling this function a new i2c bus will appear. Adding and removing + * devices on the downstream bus will result in calls to the + * &i2c_atr_ops->attach_client and &i2c_atr_ops->detach_client callbacks for the + * driver to assign an alias to the device. + * + * The adapter's fwnode is set to @bus_handle, or if @bus_handle is NULL the + * function looks for a child node whose 'reg' property matches the chan_id + * under the i2c-atr device's 'i2c-atr' node. + * + * Call i2c_atr_del_adapter() to remove the adapter. + * + * Return: 0 on success, a negative error code otherwise. + */ +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct fwnode_handle *bus_handle); + +/** + * i2c_atr_del_adapter - Remove a child ("downstream") I2C bus added by + * i2c_atr_add_adapter(). If no I2C bus has been added + * this function is a no-op. + * @atr: The I2C ATR + * @chan_id: Index of the adapter to be removed (0 .. max_adapters-1) + */ +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id); + +/** + * i2c_atr_set_driver_data - Set private driver data to the i2c-atr instance. + * @atr: The I2C ATR + * @data: Pointer to the data to store + */ +void i2c_atr_set_driver_data(struct i2c_atr *atr, void *data); + +/** + * i2c_atr_get_driver_data - Get the stored drive data. + * @atr: The I2C ATR + * + * Return: Pointer to the stored data + */ +void *i2c_atr_get_driver_data(struct i2c_atr *atr); + +#endif /* _LINUX_I2C_ATR_H */ From patchwork Fri Jan 20 15:34:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46423 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp268770wrn; Fri, 20 Jan 2023 07:41:05 -0800 (PST) X-Google-Smtp-Source: AMrXdXssNL2C9s2sgoWGIdbu0vHTQgpcnUphp0GZcpN6Y8JOjHkAeqp56WTi+bS38W/ihare8DEs X-Received: by 2002:aa7:956c:0:b0:58d:9be3:f5e5 with SMTP id x12-20020aa7956c000000b0058d9be3f5e5mr16245714pfq.25.1674229264777; Fri, 20 Jan 2023 07:41:04 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229264; cv=none; d=google.com; s=arc-20160816; b=u4QV/Bb0gt1rQwCxpkPo9hTirLopbYq4vItxc1HFOcSUSGsSmkbvu/yaaa4UKWfCci VQ+7vDTAEzbnHAZTXnefgKwLij8fo8ztzyq4fpIW3kjAX8hhdZRYjECyqkNitjRyg5Np h1OPRKlHTP+BXLCg7swbFtKPXTWfDM7hhC9GFwDOz/7yJujKycZdP8HeIPM/3AgtzKXf TsYtLustF7v+UCr8ONOx/fTA38GsruVgxiA/7JImx/dGpGFw2CxmiMoru0on6TBBdGJR ufhbq4NW43RumO7MeFS7Bj9ui96hnnguc5sRE6a0+tP2k7tX9aRp6vbiQvduuUcQ3a2J 1jaw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=Etj4jteyOTUDd/0f+nZiE3cWsTcEqhDUavvGZZNUAl0=; b=tvwmuTMoIUhYMQDUvDciyq2B7hrjfNFGDmB/uOTVE/0pHiWWbRMUvAna6DfaC7NlDH N/kRNSckDYKuky/Tzz0cwzgsmzSjfhH5CEK0tyszqZ2G9VDDT0bjD5tLrGVoHpAk64pv UfksPyQFpo9Bh+5Fpk92xIU0dc20sx9aPY2kAO7+K2Zp7jIm/ZrSFednnBpOVvhPQ8pV YPaaNLCOSPa8W1BGsTZbswA+m16mQySv5cy2sI2ZN2PieD1YuE0kq5Iv990JdInT4NPr dFtp7Dzt3y03k3JJ+dAuqFW3kx21+u4dWhE1AStjnPHLw64KZPz9c4rNnlrLnNzvMVOE jW7w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=dwIop4wX; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id j125-20020a625583000000b005815349139fsi39846444pfb.81.2023.01.20.07.40.52; Fri, 20 Jan 2023 07:41:04 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=dwIop4wX; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231511AbjATPeq (ORCPT + 99 others); Fri, 20 Jan 2023 10:34:46 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37028 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229568AbjATPen (ORCPT ); Fri, 20 Jan 2023 10:34:43 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 407D310431; Fri, 20 Jan 2023 07:34:42 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 3EB6FA4C; Fri, 20 Jan 2023 16:34:36 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228877; bh=TyApQ6qpr4eiku+THwel+cmwld8/fczSmL0rhfymuCk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dwIop4wX89bEi2qG4QeI1eAMwpgAOJsN2smdXllU5t2TdHgTmGDBqdRNDTzuoeAvc +g79fcczAcL9xj3ZlbRzMAarW6J7H7zEkv30ylaOIPDJ9p+TPgmf46L8Xq086jfAJG XvDIXBHEiGhx3QuhTEOJIjBIJXpz1wUjuk/ghcu8= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Tomi Valkeinen , Laurent Pinchart , Rob Herring Subject: [PATCH v8 2/7] dt-bindings: media: add TI DS90UB913 FPD-Link III Serializer Date: Fri, 20 Jan 2023 17:34:12 +0200 Message-Id: <20230120153417.1156207-3-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556625453529459?= X-GMAIL-MSGID: =?utf-8?q?1755556625453529459?= Add DT bindings for TI DS90UB913 FPD-Link III Serializer. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Reviewed-by: Rob Herring --- .../bindings/media/i2c/ti,ds90ub913.yaml | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml new file mode 100644 index 000000000000..f6612bb0f667 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub913.yaml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub913.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DS90UB913 FPD-Link III Serializer + +maintainers: + - Tomi Valkeinen + +description: + The TI DS90UB913 is an FPD-Link III video serializer for parallel video. + +properties: + compatible: + enum: + - ti,ds90ub913a-q1 + + '#gpio-cells': + const: 2 + description: + First cell is the GPO pin number, second cell is the flags. The GPO pin + number must be in range of [0, 3]. Note that GPOs 2 and 3 are not + available in external oscillator mode. + + gpio-controller: true + + clocks: + maxItems: 1 + description: + Reference clock connected to the CLKIN pin. + + clock-names: + items: + - const: clkin + + '#clock-cells': + const: 0 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Parallel input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + required: + - pclk-sample + + port@1: + $ref: /schemas/graph.yaml#/properties/port + unevaluatedProperties: false + description: FPD-Link III output port + + required: + - port@0 + - port@1 + + i2c: + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + +required: + - compatible + - '#gpio-cells' + - gpio-controller + - '#clock-cells' + - ports + +additionalProperties: false + +examples: + - | + #include + + serializer { + compatible = "ti,ds90ub913a-q1"; + + gpio-controller; + #gpio-cells = <2>; + + clocks = <&clk_cam_48M>; + clock-names = "clkin"; + + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ub913_in: endpoint { + remote-endpoint = <&sensor_out>; + pclk-sample = <1>; + }; + }; + + port@1 { + reg = <1>; + endpoint { + remote-endpoint = <&deser_fpd_in>; + }; + }; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@48 { + compatible = "aptina,mt9v111"; + reg = <0x48>; + + clocks = <&fixed_clock>; + + port { + sensor_out: endpoint { + remote-endpoint = <&ub913_in>; + }; + }; + }; + }; + }; +... From patchwork Fri Jan 20 15:34:13 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46424 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp268880wrn; Fri, 20 Jan 2023 07:41:18 -0800 (PST) X-Google-Smtp-Source: AMrXdXtSdHdQ+Z6OqY+URBMpHf1rvmuv7obcCn/kENNZh7f6R29mX/C6LOEZE6VjZrZPAvSDLTXl X-Received: by 2002:a17:907:6d0e:b0:871:7b6f:9c53 with SMTP id sa14-20020a1709076d0e00b008717b6f9c53mr21669599ejc.30.1674229278551; Fri, 20 Jan 2023 07:41:18 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229278; cv=none; d=google.com; s=arc-20160816; b=cGhx2ZavB4f1FZrtBIhCYURz+teusDR/nE+u4Kvf6ynciU0CgmeqIjJSyr7FnmRR0L XnAsPb6TgSYH5fq0fCv9xehGQgpfyQD+nNNeMgiZe4woZ4hiGfqBi4MzdIl9W6CAcj/L KZSaaD5UI5s1SsMVXqk68lNfktfnLJFGfgyyKX5a0Q8zosGH/5LogVIyp8+0hiaZpRy0 HyIg1xR8kuuWmjcIkLFFcHjIVuecVx+fj4a5gwzUcPahZ3yxL58bEWLttpsle9l37y// 0Hr6dgpYcrxOPUGMS62ARe3KNUXHdPbFCM2vVeBdRQdI8kxSre0zCY/mVlUHO5Q3F+sT fgIA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=5XLB2M4b2PTbd70modM3hnQhpVfeerOKRv7lf9bZILY=; b=JB7EFGoGE4Zr+hcLmoHd58u16JDvHkXtfi0NfQtjZ3vYKjJw13lBneBc24V3mfW9Fb rzBnqxsN7sBQrX2i+cKPQLfVzDLhhrA0AIaogRGmg+oL/8e7hbTTLIAHDP/CSIFs38eL r0S2V8BqJzZ7aqyd50RziqkJK/vGDya1w8v/OThtD/3BKnexQ/YJOSlEKNAzlnh5Rv+t riClSIND/Vrki44g06MvWJXEW5ZkCtRHeFmpFEAahj4tyLkMbqi88nQuhKnXFm3809BH OGUnMTyg9hcuYCggXQjO+Ll40jVcsHRzqyw1RsoH/N64s9JfE9cShK+PEco4psP7ZTbm Uynw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b="Nyx7/iz3"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id qa16-20020a170907869000b0087760800a8dsi8847451ejc.623.2023.01.20.07.40.55; Fri, 20 Jan 2023 07:41:18 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b="Nyx7/iz3"; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231519AbjATPew (ORCPT + 99 others); Fri, 20 Jan 2023 10:34:52 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37050 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231482AbjATPen (ORCPT ); Fri, 20 Jan 2023 10:34:43 -0500 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 8E4F0C79DD; Fri, 20 Jan 2023 07:34:42 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id CCB51104C; Fri, 20 Jan 2023 16:34:37 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228879; bh=H5byX9BR3izXApOzFQA8+xqAPcha46UtXqsX7Usjt0I=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Nyx7/iz3nf9zj40Ve0nJXVc53uRCH77AG4IkYRg4Ql9zkqYA2QYhKhL7Li6AIEUL8 YUG/GHtOKk7yMPsfZjyhVC828URFbq2Lnvz6CJNb1b/BFenMgq41nHzPfPjds2VpcL tpzc90T1p+vuF4ZZoWOtLm4g5sUFFuiTg0KBR9qo= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Tomi Valkeinen , Laurent Pinchart , Rob Herring Subject: [PATCH v8 3/7] dt-bindings: media: add TI DS90UB953 FPD-Link III Serializer Date: Fri, 20 Jan 2023 17:34:13 +0200 Message-Id: <20230120153417.1156207-4-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556640136313642?= X-GMAIL-MSGID: =?utf-8?q?1755556640136313642?= Add DT bindings for TI DS90UB953 FPD-Link III Serializer. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Reviewed-by: Rob Herring --- .../bindings/media/i2c/ti,ds90ub953.yaml | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml new file mode 100644 index 000000000000..2030366994d1 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub953.yaml @@ -0,0 +1,134 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/i2c/ti,ds90ub953.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Texas Instruments DS90UB953 FPD-Link III Serializer + +maintainers: + - Tomi Valkeinen + +description: + The TI DS90UB953 is an FPD-Link III video serializer for MIPI CSI-2. + +properties: + compatible: + enum: + - ti,ds90ub953-q1 + - ti,ds90ub971-q1 + + '#gpio-cells': + const: 2 + description: + First cell is the GPIO pin number, second cell is the flags. The GPIO pin + number must be in range of [0, 3]. + + gpio-controller: true + + clocks: + maxItems: 1 + description: + Reference clock connected to the CLKIN pin. + + clock-names: + items: + - const: clkin + + '#clock-cells': + const: 0 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 input port + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/properties/port + unevaluatedProperties: false + description: FPD-Link III output port + + required: + - port@0 + - port@1 + + i2c: + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + +required: + - compatible + - '#gpio-cells' + - gpio-controller + - '#clock-cells' + - ports + +additionalProperties: false + +examples: + - | + #include + + serializer { + compatible = "ti,ds90ub953-q1"; + + gpio-controller; + #gpio-cells = <2>; + + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ub953_in: endpoint { + clock-lanes = <0>; + data-lanes = <1 2 3 4>; + remote-endpoint = <&sensor_out>; + }; + }; + + port@1 { + reg = <1>; + endpoint { + remote-endpoint = <&deser_fpd_in>; + }; + }; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1a { + compatible = "sony,imx274"; + reg = <0x1a>; + + reset-gpios = <&serializer 0 GPIO_ACTIVE_LOW>; + + clocks = <&serializer>; + clock-names = "inck"; + + port { + sensor_out: endpoint { + remote-endpoint = <&ub953_in>; + }; + }; + }; + }; + }; +... From patchwork Fri Jan 20 15:34:14 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46425 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp269007wrn; Fri, 20 Jan 2023 07:41:35 -0800 (PST) X-Google-Smtp-Source: AMrXdXs3uDk7148JDNH9lms7nkB3/zSk0Nw3i8L8Fe9lK6cT520plmeLp8Gs2gEKbgdHBJeQ/2aA X-Received: by 2002:a05:6402:27cf:b0:49e:2e15:3984 with SMTP id c15-20020a05640227cf00b0049e2e153984mr21225275ede.36.1674229295080; Fri, 20 Jan 2023 07:41:35 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229295; cv=none; d=google.com; s=arc-20160816; b=0mAQ694GhVN6+rshgKB3+Iles1mF36d+vRSAFz0VMyASS179Hh7P/F4g0bIyZM+x3C XB67jWCWYkvXBRDfj6rUSQi1tMFtJOHNrRW1DdCZbAr4gftNlAsaDxgLgaPVqsledK35 kKM7TBAdNvnohCZiwEwYvd2OqwKJizWoJwZLJd/eT9yYsqHrD8ViW78LCYpGWrLe4Kz/ /Xtiuv2KHXz1B0LYRORL2c1iyGS4sW+NWcihyihX8W2bN6PI/HT0e7nKcMBZYRtk/dft jpWjCrmQeTVydY00Gj+qzYLfFmBRkNHMyQ6xfAtx9CBOvuysRWTgd4KAQhhcdw8ZAMGf O9yg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=IP5PE+KtkA3uemfuNHgPwfy7I5iPPxp0bXsajlI7kQ8=; b=B19x1nd7u8+1iBgkxiUPbWhHtYU2NGmOEO+x7sCM6Bv5fB/WWjKkIUcrz2xwlZQNiX G6RQ2APKHaoThCtXONN6jADUDUOESfhAKeEYOeIjvXHe2ua5WW/7gLHf/yIKQiwEiQQd Wo5OfKAAOf+1wCF6SXM+MSHQWM7mTwnRC7zyD8RcsmYdI849Qaalnkjh+bwP6sC8e761 Qy4NYkhPQt1906ToytLNRygCaLLy/uZAqdQmF1ye6Jsz8Se8MV7CaKg8Tkb3zD8ai2b6 NrAEuJMV/Dhmds8dmpKnbLYCVYGdi58PuqIegUIKDFv6Sewgv7mZpsmAO7cmpWs1u0Iy hFEw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=cVjcHs6Y; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id d12-20020a50ea8c000000b004854e1d36dasi43277121edo.317.2023.01.20.07.41.10; Fri, 20 Jan 2023 07:41:35 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=cVjcHs6Y; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231546AbjATPfD (ORCPT + 99 others); Fri, 20 Jan 2023 10:35:03 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37448 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231532AbjATPey (ORCPT ); Fri, 20 Jan 2023 10:34:54 -0500 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 CD946C9271; Fri, 20 Jan 2023 07:34:44 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 67A5512B9; Fri, 20 Jan 2023 16:34:39 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228880; bh=zYJMofLIf50MSqCRgL/c7r+rPiFUeNculg39rTDLOzs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=cVjcHs6Y2dlq5tYvh94YgT5DasKjUsTrzWvmnUZVzlU772O3GvK/HsrkU7Ma66Qdl rgRSZ/MEY4xMWIyeEMqqUDOoKf8lhUD3e5D4gzQim4VjTV7fxVFfKPAvis3OjWyOXT vVAv99wG9z2rcsE2aL2FQ1UHp/3jFTreD6oUvmBY= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Tomi Valkeinen , Rob Herring , Laurent Pinchart Subject: [PATCH v8 4/7] dt-bindings: media: add TI DS90UB960 FPD-Link III Deserializer Date: Fri, 20 Jan 2023 17:34:14 +0200 Message-Id: <20230120153417.1156207-5-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556657002782090?= X-GMAIL-MSGID: =?utf-8?q?1755556657002782090?= Add DT bindings for TI DS90UB960 FPD-Link III Deserializer. Signed-off-by: Tomi Valkeinen Reviewed-by: Rob Herring Reviewed-by: Laurent Pinchart --- .../bindings/media/i2c/ti,ds90ub960.yaml | 431 ++++++++++++++++++ 1 file changed, 431 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml diff --git a/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml new file mode 100644 index 000000000000..bb921caa85a7 --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml @@ -0,0 +1,431 @@ +# 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 GPIO + forwarding. + +properties: + compatible: + enum: + - ti,ds90ub960-q1 + - ti,ds90ub9702-q1 + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + description: + Reference clock connected to the REFCLK pin. + + clock-names: + items: + - const: refclk + + powerdown-gpios: + maxItems: 1 + description: + Specifier for the GPIO connected to the PDB pin. + + i2c-alias-pool: + $ref: /schemas/types.yaml#/definitions/uint16-array + description: + I2C alias pool is a pool of I2C addresses on the main I2C bus that can be + used to access the remote peripherals on the serializer's I2C bus. The + addresses must be available, not used by any other peripheral. Each + remote peripheral is assigned an alias from the pool, and transactions to + that address will be forwarded to the remote peripheral, with the address + translated to the remote peripheral's real address. This property is not + needed if there are no I2C addressable remote peripherals. + + links: + type: object + additionalProperties: false + + properties: + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + ti,manual-strobe: + type: boolean + description: + Enable manual strobe position and EQ level + + patternProperties: + '^link@[0-3]$': + type: object + additionalProperties: false + properties: + reg: + description: The link number + maxItems: 1 + + i2c-alias: + description: + The I2C address used for the serializer. Transactions to this + address on the I2C bus where the deserializer resides are + forwarded to the serializer. + + ti,rx-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # RAW10 + - 1 # RAW12 HF + - 2 # RAW12 LF + - 3 # CSI2 SYNC + - 4 # CSI2 NON-SYNC + description: + FPD-Link Input Mode. This should reflect the hardware and the + default mode of the connected device. + + ti,cdr-mode: + $ref: /schemas/types.yaml#/definitions/uint32 + enum: + - 0 # FPD-Link III + - 1 # FPD-Link IV + description: + FPD-Link CDR Mode. This should reflect the hardware and the + default mode of the connected device. + + ti,strobe-pos: + $ref: /schemas/types.yaml#/definitions/int32 + minimum: -13 + maximum: 13 + description: Manual strobe position + + ti,eq-level: + $ref: /schemas/types.yaml#/definitions/uint32 + maximum: 14 + description: Manual EQ level + + serializer: + type: object + description: FPD-Link Serializer node + + required: + - reg + - i2c-alias + - ti,rx-mode + - serializer + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: FPD-Link input 0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + description: + Endpoint for FPD-Link port. If the RX mode for this port is RAW, + hsync-active and vsync-active must be defined. + + port@1: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: FPD-Link input 1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + description: + Endpoint for FPD-Link port. If the RX mode for this port is RAW, + hsync-active and vsync-active must be defined. + + port@2: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: FPD-Link input 2 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + description: + Endpoint for FPD-Link port. If the RX mode for this port is RAW, + hsync-active and vsync-active must be defined. + + port@3: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: FPD-Link input 3 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + description: + Endpoint for FPD-Link port. If the RX mode for this port is RAW, + hsync-active and vsync-active must be defined. + + port@4: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 Output 0 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + link-frequencies: + maxItems: 1 + + required: + - data-lanes + - link-frequencies + + port@5: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 Output 1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + link-frequencies: + maxItems: 1 + + required: + - data-lanes + - link-frequencies + + required: + - port@0 + - port@1 + - port@2 + - port@3 + - port@4 + - port@5 + +required: + - compatible + - reg + - clocks + - clock-names + - ports + +additionalProperties: false + +examples: + - | + #include + + i2c { + clock-frequency = <400000>; + #address-cells = <1>; + #size-cells = <0>; + + deser@3d { + compatible = "ti,ds90ub960-q1"; + reg = <0x3d>; + + clock-names = "refclk"; + clocks = <&fixed_clock>; + + powerdown-gpios = <&pca9555 7 GPIO_ACTIVE_LOW>; + + i2c-alias-pool = /bits/ 16 <0x4a 0x4b 0x4c 0x4d 0x4e 0x4f>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + /* Port 0, Camera 0 */ + port@0 { + reg = <0>; + + ub960_fpd3_1_in: endpoint { + remote-endpoint = <&ub953_1_out>; + }; + }; + + /* Port 1, Camera 1 */ + port@1 { + reg = <1>; + + ub960_fpd3_2_in: endpoint { + remote-endpoint = <&ub913_2_out>; + hsync-active = <0>; + vsync-active = <1>; + }; + }; + + /* Port 2, unconnected */ + port@2 { + reg = <2>; + }; + + /* Port 3, unconnected */ + port@3 { + reg = <3>; + }; + + /* Port 4, CSI-2 TX */ + port@4 { + reg = <4>; + ds90ub960_0_csi_out: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <800000000>; + remote-endpoint = <&csi2_phy0>; + }; + }; + + /* Port 5, unconnected */ + port@5 { + reg = <5>; + }; + }; + + links { + #address-cells = <1>; + #size-cells = <0>; + + /* Link 0 has DS90UB953 serializer and IMX274 sensor */ + + link@0 { + reg = <0>; + i2c-alias = <0x44>; + + ti,rx-mode = <3>; + + serializer1: serializer { + compatible = "ti,ds90ub953-q1"; + + gpio-controller; + #gpio-cells = <2>; + + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ub953_1_in: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&sensor_1_out>; + }; + }; + + port@1 { + reg = <1>; + + ub953_1_out: endpoint { + remote-endpoint = <&ub960_fpd3_1_in>; + }; + }; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1a { + compatible = "sony,imx274"; + reg = <0x1a>; + + reset-gpios = <&serializer1 0 GPIO_ACTIVE_LOW>; + + port { + sensor_1_out: endpoint { + remote-endpoint = <&ub953_1_in>; + }; + }; + }; + }; + }; + }; /* End of link@0 */ + + /* Link 1 has DS90UB913 serializer and MT9V111 sensor */ + + link@1 { + reg = <1>; + i2c-alias = <0x45>; + + ti,rx-mode = <0>; + + serializer2: serializer { + compatible = "ti,ds90ub913a-q1"; + + gpio-controller; + #gpio-cells = <2>; + + clocks = <&clk_cam_48M>; + clock-names = "clkin"; + + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ub913_2_in: endpoint { + remote-endpoint = <&sensor_2_out>; + pclk-sample = <1>; + }; + }; + + port@1 { + reg = <1>; + + ub913_2_out: endpoint { + remote-endpoint = <&ub960_fpd3_2_in>; + }; + }; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@48 { + compatible = "aptina,mt9v111"; + reg = <0x48>; + + clocks = <&serializer2>; + + port { + sensor_2_out: endpoint { + remote-endpoint = <&ub913_2_in>; + }; + }; + }; + }; + }; + }; /* End of link@1 */ + }; + }; + }; +... From patchwork Fri Jan 20 15:34:15 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46426 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp269009wrn; Fri, 20 Jan 2023 07:41:35 -0800 (PST) X-Google-Smtp-Source: AMrXdXtOT6M0alRaqS4C6q3K38jPBZu4XINGlCf71UpFkWG10GseI4An/d9XHGTC+TArW/RDCKq3 X-Received: by 2002:a17:902:f542:b0:195:f412:5e6f with SMTP id h2-20020a170902f54200b00195f4125e6fmr500087plf.48.1674229295229; Fri, 20 Jan 2023 07:41:35 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229295; cv=none; d=google.com; s=arc-20160816; b=Xv6lrpghxSwd2JX3//d5bX/R9kptTDoGP9fUqjP++scx95EY4Di6uPd+noV975Crn4 0I+PMOCJVgZRKerFHGfsmjcEjticC/17p4rWfmSZrE8lrq3emNp5gpib/KOFEvK954zv 6iYZrqJ/5tzR3YFoFgHR1zfhRLLBEmqxj8F2io6uFYk07uVCpfVPqZYIk8oAFrsruDcS VTMS56wnILzM0EW7CwNXrRHAvJUC09b90XUN2fMq3QzkFSgoJMm6je5cG6ygEqjY5u87 k+Y1ytQDPcNo3ddvVAB3r7UM+a43wHDBLCBwFuGkVLQZpcKIaFI7eNk+3DnIAD/nonmu QpVw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=OE9Z/lngsndL2jNO5uGHk7P0LH8plq+GEIDBPnPdVg4=; b=WIEw6C0aJ8HFLl3qCqdj+xCB2svsB/lTF5l3W7WIzvjYpCUyHuoLjoAF6Zn4mdvuXK wQy8ApI+CFRSQDPNGfvHCGk7POqf3s0cOPhzJoELTPic+TK+WZLXKtj0SF+Zu2hm42Nk TTRlV24KAQIWuuuBbfFWNU/R4nNWJmvfo64ISghWyYh464+bEvJMcWKtzM4/YN8CP9Ye SguPzItIzjKGaQ6GCn2aJAU4wPUyZjTlEX7fH+pvgldhYMY/VrRixJl5o8dlfUbCmLbN LZrc+Wryrxp0mW39GDhZXg+XfctFRTXBt6xQ51zkxUGv9cS+xNEwc7HX5WXpk7w3z14e bfGQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=N7Lt0L2u; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id u12-20020a17090341cc00b00189ac5a25e8si14257694ple.160.2023.01.20.07.41.23; Fri, 20 Jan 2023 07:41:35 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=N7Lt0L2u; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231562AbjATPfK (ORCPT + 99 others); Fri, 20 Jan 2023 10:35:10 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37482 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231548AbjATPez (ORCPT ); Fri, 20 Jan 2023 10:34:55 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DD64FC9272; Fri, 20 Jan 2023 07:34:44 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id F3E6512F3; Fri, 20 Jan 2023 16:34:40 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228882; bh=sZb7+86KXl7IKa/dY19G4aY4TbkBM1TaoxJii4GJ65U=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=N7Lt0L2uggpeotlh/1SVvSuJD9JkWlysbyPYXE/CEFwaByO0u+elZ1N/hlDRLk9pp i/ioN6kAcxqCf412mS4lML5HrprlIKUiogKkj/R7ZYFXrOpSOy7wDW0V/uRNGvd1FY mKentaqJ/bhz22O96rrIvGTooRvnhSOeU9CFHYfM= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Tomi Valkeinen Subject: [PATCH v8 5/7] media: i2c: add DS90UB960 driver Date: Fri, 20 Jan 2023 17:34:15 +0200 Message-Id: <20230120153417.1156207-6-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556657475223413?= X-GMAIL-MSGID: =?utf-8?q?1755556657475223413?= Add driver for TI DS90UB960 FPD-Link III Deserializer. Signed-off-by: Tomi Valkeinen --- MAINTAINERS | 8 + drivers/media/i2c/Kconfig | 21 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ds90ub960.c | 4278 +++++++++++++++++++++++++++++++++ include/media/i2c/ds90ub9xx.h | 16 + 5 files changed, 4324 insertions(+) create mode 100644 drivers/media/i2c/ds90ub960.c create mode 100644 include/media/i2c/ds90ub9xx.h diff --git a/MAINTAINERS b/MAINTAINERS index ba716f2861cf..d0dc8572191d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20626,6 +20626,14 @@ F: drivers/misc/tifm* F: drivers/mmc/host/tifm_sd.c F: include/linux/tifm.h +TI FPD-LINK DRIVERS +M: Tomi Valkeinen +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/i2c/ti,ds90* +F: drivers/media/i2c/ds90* +F: include/media/i2c/ds90* + TI KEYSTONE MULTICORE NAVIGATOR DRIVERS M: Nishanth Menon M: Santosh Shilimkar diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 7806d4b81716..dc1c7c80dc1c 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1595,4 +1595,25 @@ config VIDEO_THS7303 endmenu +# +# Video serializers and deserializers (e.g. FPD-Link) +# + +menu "Video serializers and deserializers" + +config VIDEO_DS90UB960 + tristate "TI FPD-Link III/IV Deserializers" + depends on OF && I2C && VIDEO_DEV + select I2C_ATR + select MEDIA_CONTROLLER + select OF_GPIO + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Texas Instruments DS90UB960 + FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer. + +endmenu + endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..4cd680f3b953 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -142,3 +142,4 @@ obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_WM8739) += wm8739.o obj-$(CONFIG_VIDEO_WM8775) += wm8775.o +obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c new file mode 100644 index 000000000000..eb391f0259b3 --- /dev/null +++ b/drivers/media/i2c/ds90ub960.c @@ -0,0 +1,4278 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB960-Q1 video deserializer + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2023 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MHZ(v) ((u32)((v) * 1000000U)) + +#define UB960_POLL_TIME_MS 500 + +#define UB960_MAX_RX_NPORTS 4 +#define UB960_MAX_TX_NPORTS 2 +#define UB960_MAX_NPORTS (UB960_MAX_RX_NPORTS + UB960_MAX_TX_NPORTS) + +#define UB960_MAX_PORT_ALIASES 8 +#define UB960_MAX_POOL_ALIASES (UB960_MAX_RX_NPORTS * UB960_MAX_PORT_ALIASES) + +#define UB960_NUM_BC_GPIOS 4 + +/* + * Register map + * + * 0x00-0x32 Shared (UB960_SR) + * 0x33-0x3A CSI-2 TX (per-port paged on DS90UB960, shared on 954) (UB960_TR) + * 0x4C Shared (UB960_SR) + * 0x4D-0x7F FPD-Link RX, per-port paged (UB960_RR) + * 0xB0-0xBF Shared (UB960_SR) + * 0xD0-0xDF FPD-Link RX, per-port paged (UB960_RR) + * 0xF0-0xF5 Shared (UB960_SR) + * 0xF8-0xFB Shared (UB960_SR) + * All others Reserved + * + * Register prefixes: + * UB960_SR_* = Shared register + * UB960_RR_* = FPD-Link RX, per-port paged register + * UB960_TR_* = CSI-2 TX, per-port paged register + * UB960_XR_* = Reserved register + * UB960_IR_* = Indirect register + */ + +#define UB960_SR_I2C_DEV_ID 0x00 +#define UB960_SR_RESET 0x01 +#define UB960_SR_RESET_DIGITAL_RESET1 BIT(1) +#define UB960_SR_RESET_DIGITAL_RESET0 BIT(0) +#define UB960_SR_RESET_GPIO_LOCK_RELEASE BIT(5) + +#define UB960_SR_GEN_CONFIG 0x02 +#define UB960_SR_REV_MASK 0x03 +#define UB960_SR_DEVICE_STS 0x04 +#define UB960_SR_PAR_ERR_THOLD_HI 0x05 +#define UB960_SR_PAR_ERR_THOLD_LO 0x06 +#define UB960_SR_BCC_WDOG_CTL 0x07 +#define UB960_SR_I2C_CTL1 0x08 +#define UB960_SR_I2C_CTL2 0x09 +#define UB960_SR_SCL_HIGH_TIME 0x0A +#define UB960_SR_SCL_LOW_TIME 0x0B +#define UB960_SR_RX_PORT_CTL 0x0C +#define UB960_SR_IO_CTL 0x0D +#define UB960_SR_GPIO_PIN_STS 0x0E +#define UB960_SR_GPIO_INPUT_CTL 0x0F +#define UB960_SR_GPIO_PIN_CTL(n) (0x10 + (n)) /* n < UB960_NUM_GPIOS */ +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SEL 5 +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SRC_SHIFT 2 +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_EN BIT(0) + +#define UB960_SR_FS_CTL 0x18 +#define UB960_SR_FS_HIGH_TIME_1 0x19 +#define UB960_SR_FS_HIGH_TIME_0 0x1A +#define UB960_SR_FS_LOW_TIME_1 0x1B +#define UB960_SR_FS_LOW_TIME_0 0x1C +#define UB960_SR_MAX_FRM_HI 0x1D +#define UB960_SR_MAX_FRM_LO 0x1E +#define UB960_SR_CSI_PLL_CTL 0x1F + +#define UB960_SR_FWD_CTL1 0x20 +#define UB960_SR_FWD_CTL1_PORT_DIS(n) BIT((n) + 4) + +#define UB960_SR_FWD_CTL2 0x21 +#define UB960_SR_FWD_STS 0x22 + +#define UB960_SR_INTERRUPT_CTL 0x23 +#define UB960_SR_INTERRUPT_CTL_INT_EN BIT(7) +#define UB960_SR_INTERRUPT_CTL_IE_CSI_TX0 BIT(4) +#define UB960_SR_INTERRUPT_CTL_IE_RX(n) BIT((n)) /* rxport[n] IRQ */ +#define UB960_SR_INTERRUPT_CTL_ALL 0x83 /* TODO 0x93 to enable CSI */ + +#define UB960_SR_INTERRUPT_STS 0x24 +#define UB960_SR_INTERRUPT_STS_INT BIT(7) +#define UB960_SR_INTERRUPT_STS_IS_CSI_TX(n) BIT(4 + (n)) /* txport[n] IRQ */ +#define UB960_SR_INTERRUPT_STS_IS_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define UB960_SR_TS_CONFIG 0x25 +#define UB960_SR_TS_CONTROL 0x26 +#define UB960_SR_TS_LINE_HI 0x27 +#define UB960_SR_TS_LINE_LO 0x28 +#define UB960_SR_TS_STATUS 0x29 +#define UB960_SR_TIMESTAMP_P0_HI 0x2A +#define UB960_SR_TIMESTAMP_P0_LO 0x2B +#define UB960_SR_TIMESTAMP_P1_HI 0x2C +#define UB960_SR_TIMESTAMP_P1_LO 0x2D + +#define UB960_SR_CSI_PORT_SEL 0x32 + +#define UB960_TR_CSI_CTL 0x33 +#define UB960_TR_CSI_CTL_CSI_CAL_EN BIT(6) +#define UB960_TR_CSI_CTL_CSI_ENABLE BIT(0) + +#define UB960_TR_CSI_CTL2 0x34 +#define UB960_TR_CSI_STS 0x35 +#define UB960_TR_CSI_TX_ICR 0x36 + +#define UB960_TR_CSI_TX_ISR 0x37 +#define UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR BIT(3) +#define UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR BIT(1) + +#define UB960_TR_CSI_TEST_CTL 0x38 +#define UB960_TR_CSI_TEST_PATT_HI 0x39 +#define UB960_TR_CSI_TEST_PATT_LO 0x3A + +#define UB960_XR_SFILTER_CFG 0x41 +#define UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT 4 +#define UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT 0 + +#define UB960_XR_AEQ_CTL1 0x42 +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_FPD_CLK BIT(6) +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_ENCODING BIT(5) +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_PARITY BIT(4) +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_MASK \ + (UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_FPD_CLK | \ + UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_ENCODING | \ + UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_PARITY) +#define UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN BIT(0) + +#define UB960_XR_AEQ_ERR_THOLD 0x43 + +#define UB960_RR_BCC_ERR_CTL 0x46 +#define UB960_RR_BCC_STATUS 0x47 +#define UB960_RR_BCC_STATUS_SEQ_ERROR BIT(5) +#define UB960_RR_BCC_STATUS_MASTER_ERR BIT(4) +#define UB960_RR_BCC_STATUS_MASTER_TO BIT(3) +#define UB960_RR_BCC_STATUS_SLAVE_ERR BIT(2) +#define UB960_RR_BCC_STATUS_SLAVE_TO BIT(1) +#define UB960_RR_BCC_STATUS_RESP_ERR BIT(0) +#define UB960_RR_BCC_STATUS_ERROR_MASK \ + (UB960_RR_BCC_STATUS_SEQ_ERROR | UB960_RR_BCC_STATUS_MASTER_ERR | \ + UB960_RR_BCC_STATUS_MASTER_TO | UB960_RR_BCC_STATUS_SLAVE_ERR | \ + UB960_RR_BCC_STATUS_SLAVE_TO | UB960_RR_BCC_STATUS_RESP_ERR) + +#define UB960_RR_FPD3_CAP 0x4A +#define UB960_RR_RAW_EMBED_DTYPE 0x4B +#define UB960_RR_RAW_EMBED_DTYPE_LINES_SHIFT 6 + +#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_STS1_ERROR_MASK \ + (UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR | \ + UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR | \ + UB960_RR_RX_PORT_STS1_PARITY_ERROR) + +#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_PORT_STS2_ERROR_MASK \ + UB960_RR_RX_PORT_STS2_BUFFER_ERROR + +#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_BCC_CONFIG_BC_FREQ_SEL_MASK GENMASK(2, 0) + +#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_PORT_CONFIG_FPD3_MODE_MASK GENMASK(1, 0) + +#define UB960_RR_BC_GPIO_CTL(n) (0x6E + (n)) /* n < 2 */ +#define UB960_RR_RAW10_ID 0x70 +#define UB960_RR_RAW10_ID_VC_SHIFT 6 +#define UB960_RR_RAW10_ID_DT_SHIFT 0 + +#define UB960_RR_RAW12_ID 0x71 +#define UB960_RR_CSI_VC_MAP 0x72 +#define UB960_RR_CSI_VC_MAP_SHIFT(x) ((x) * 2) + +#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_RX_STS_ERROR_MASK \ + (UB960_RR_CSI_RX_STS_LENGTH_ERR | UB960_RR_CSI_RX_STS_CKSUM_ERR | \ + UB960_RR_CSI_RX_STS_ECC2_ERR | UB960_RR_CSI_RX_STS_ECC1_ERR) + +#define UB960_RR_CSI_ERR_COUNTER 0x7B +#define UB960_RR_PORT_CONFIG2 0x7C +#define UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_MASK GENMASK(7, 6) +#define UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_SHIFT 6 + +#define UB960_RR_PORT_CONFIG2_LV_POL_LOW BIT(1) +#define UB960_RR_PORT_CONFIG2_FV_POL_LOW BIT(0) + +#define UB960_RR_PORT_PASS_CTL 0x7D +#define UB960_RR_SEN_INT_RISE_CTL 0x7E +#define UB960_RR_SEN_INT_FALL_CTL 0x7F + +#define UB960_SR_CSI_FRAME_COUNT_HI(n) (0x90 + 8 * (n)) +#define UB960_SR_CSI_FRAME_COUNT_LO(n) (0x91 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_HI(n) (0x92 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_LO(n) (0x93 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_HI(n) (0x94 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_LO(n) (0x95 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_HI(n) (0x96 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_LO(n) (0x97 + 8 * (n)) + +#define UB960_XR_REFCLK_FREQ 0xA5 /* UB960 */ + +#define UB960_RR_VC_ID_MAP(x) (0xa0 + (x)) /* UB9702 */ + +#define UB960_SR_IND_ACC_CTL 0xB0 +#define UB960_SR_IND_ACC_CTL_IA_AUTO_INC BIT(1) + +#define UB960_SR_IND_ACC_ADDR 0xB1 +#define UB960_SR_IND_ACC_DATA 0xB2 +#define UB960_SR_BIST_CONTROL 0xB3 +#define UB960_SR_MODE_IDX_STS 0xB8 +#define UB960_SR_LINK_ERROR_COUNT 0xB9 +#define UB960_SR_FPD3_ENC_CTL 0xBA +#define UB960_SR_FV_MIN_TIME 0xBC +#define UB960_SR_GPIO_PD_CTL 0xBE + +#define UB960_SR_FPD_RATE_CFG 0xc2 /* UB9702 */ +#define UB960_SR_CSI_PLL_DIV 0xc9 /* UB9702 */ + +#define UB960_RR_PORT_DEBUG 0xD0 +#define UB960_RR_AEQ_CTL2 0xD2 +#define UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR BIT(2) + +#define UB960_RR_AEQ_STATUS 0xD3 +#define UB960_RR_AEQ_STATUS_STATUS_2 GENMASK(5, 3) +#define UB960_RR_AEQ_STATUS_STATUS_1 GENMASK(2, 0) + +#define UB960_RR_AEQ_BYPASS 0xD4 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT 5 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK GENMASK(7, 5) +#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT 1 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK GENMASK(3, 1) +#define UB960_RR_AEQ_BYPASS_ENABLE BIT(0) + +#define UB960_RR_AEQ_MIN_MAX 0xD5 +#define UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT 4 +#define UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT 0 + +#define UB960_RR_SFILTER_STS_0 0xD6 +#define UB960_RR_SFILTER_STS_1 0xD7 +#define UB960_RR_PORT_ICR_HI 0xD8 +#define UB960_RR_PORT_ICR_LO 0xD9 +#define UB960_RR_PORT_ISR_HI 0xDA +#define UB960_RR_PORT_ISR_LO 0xDB +#define UB960_RR_FC_GPIO_STS 0xDC +#define UB960_RR_FC_GPIO_ICR 0xDD +#define UB960_RR_SEN_INT_RISE_STS 0xDE +#define UB960_RR_SEN_INT_FALL_STS 0xDF + +#define UB960_RR_CHANNEL_MODE 0xe4 /* UB9702 */ + +#define UB960_SR_FPD3_RX_ID(n) (0xF0 + (n)) + +#define UB960_SR_I2C_RX_ID(n) (0xF8 + (n)) /* < UB960_FPD_RX_NPORTS */ + +/* Indirect register blocks */ +#define UB960_IND_TARGET_PAT_GEN 0x00 +#define UB960_IND_TARGET_RX_ANA(n) (0x01 + (n)) +#define UB960_IND_TARGET_CSI_CSIPLL_REG_1 0x92 /* UB9702 */ +#define UB960_IND_TARGET_CSI_ANA 0x07 + +/* UB960_IR_PGEN_*: Indirect Registers for Test Pattern Generator */ + +#define UB960_IR_PGEN_CTL 0x01 +#define UB960_IR_PGEN_CTL_PGEN_ENABLE BIT(0) + +#define UB960_IR_PGEN_CFG 0x02 +#define UB960_IR_PGEN_CSI_DI 0x03 +#define UB960_IR_PGEN_LINE_SIZE1 0x04 +#define UB960_IR_PGEN_LINE_SIZE0 0x05 +#define UB960_IR_PGEN_BAR_SIZE1 0x06 +#define UB960_IR_PGEN_BAR_SIZE0 0x07 +#define UB960_IR_PGEN_ACT_LPF1 0x08 +#define UB960_IR_PGEN_ACT_LPF0 0x09 +#define UB960_IR_PGEN_TOT_LPF1 0x0A +#define UB960_IR_PGEN_TOT_LPF0 0x0B +#define UB960_IR_PGEN_LINE_PD1 0x0C +#define UB960_IR_PGEN_LINE_PD0 0x0D +#define UB960_IR_PGEN_VBP 0x0E +#define UB960_IR_PGEN_VFP 0x0F +#define UB960_IR_PGEN_COLOR(n) (0x10 + (n)) /* n < 15 */ + +#define UB960_IR_RX_ANA_STROBE_SET_CLK 0x08 +#define UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY BIT(3) +#define UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK GENMASK(2, 0) + +#define UB960_IR_RX_ANA_STROBE_SET_DATA 0x09 +#define UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(3) +#define UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK GENMASK(2, 0) + +/* EQ related */ + +#define UB960_MIN_AEQ_STROBE_POS -7 +#define UB960_MAX_AEQ_STROBE_POS 7 + +#define UB960_MANUAL_STROBE_EXTRA_DELAY 6 + +#define UB960_MIN_MANUAL_STROBE_POS -(7 + UB960_MANUAL_STROBE_EXTRA_DELAY) +#define UB960_MAX_MANUAL_STROBE_POS (7 + UB960_MANUAL_STROBE_EXTRA_DELAY) +#define UB960_NUM_MANUAL_STROBE_POS (UB960_MAX_MANUAL_STROBE_POS - UB960_MIN_MANUAL_STROBE_POS + 1) + +#define UB960_MIN_EQ_LEVEL 0 +#define UB960_MAX_EQ_LEVEL 14 +#define UB960_NUM_EQ_LEVELS (UB960_MAX_EQ_LEVEL - UB960_MIN_EQ_LEVEL + 1) + +struct ub960_hw_data { + const char *model; + u8 num_rxports; + u8 num_txports; + bool is_ub9702; + bool is_fpdlink4; +}; + +enum ub960_rxport_mode { + RXPORT_MODE_RAW10 = 0, + RXPORT_MODE_RAW12_HF = 1, + RXPORT_MODE_RAW12_LF = 2, + RXPORT_MODE_CSI2_SYNC = 3, + RXPORT_MODE_CSI2_ASYNC = 4, + RXPORT_MODE_LAST = RXPORT_MODE_CSI2_ASYNC, +}; + +enum ub960_rxport_cdr { + RXPORT_CDR_FPD3 = 0, + RXPORT_CDR_FPD4 = 1, + RXPORT_CDR_LAST = RXPORT_CDR_FPD4, +}; + +struct ub960_rxport { + struct ub960_data *priv; + u8 nport; /* RX port number, and index in priv->rxport[] */ + + struct v4l2_subdev *source_sd; /* Connected subdev */ + u16 source_sd_pad; + struct fwnode_handle *source_ep_fwnode; + + enum ub960_rxport_mode rx_mode; + enum ub960_rxport_cdr cdr_mode; + + struct fwnode_handle *remote_fwnode; /* 'serializer' fwnode */ + struct i2c_client *ser_client; /* Serializer */ + unsigned short ser_alias; /* Serializer i2c alias (lower 7 bits) */ + + u8 lv_fv_pol; /* LV and FV polarities */ + + struct regulator *vpoc; + + /* EQ settings */ + struct { + bool manual_eq; + + s8 strobe_pos; + + union { + struct { + u8 eq_level_min; + u8 eq_level_max; + } aeq; + + struct { + u8 eq_level; + } manual; + }; + } eq; + + struct ds90ub9xx_platform_data ser_platform_data; +}; + +struct ub960_asd { + struct v4l2_async_subdev base; + struct ub960_rxport *rxport; +}; + +static inline struct ub960_asd *to_ub960_asd(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct ub960_asd, base); +} + +struct ub960_txport { + struct ub960_data *priv; + u8 nport; /* TX port number, and index in priv->txport[] */ + + u32 num_data_lanes; +}; + +struct atr_alias_table_entry { + u16 alias_id; /* Alias ID from DT */ + + bool reserved; + u8 nport; + u8 slave_id; /* i2c client's local i2c address */ + u8 port_reg_idx; +}; + +struct atr_alias_table { + /* Protects fields in this struct */ + struct mutex lock; + + size_t num_entries; + struct atr_alias_table_entry *entries; +}; + +struct ub960_data { + const struct ub960_hw_data *hw_data; + struct i2c_client *client; /* for shared local registers */ + struct regmap *regmap; + + /* lock for register access */ + struct mutex reg_lock; + + struct clk *refclk; + + struct regulator *vddio; + + struct gpio_desc *pd_gpio; + struct delayed_work poll_work; + struct i2c_atr *atr; + struct ub960_rxport *rxports[UB960_MAX_RX_NPORTS]; + struct ub960_txport *txports[UB960_MAX_TX_NPORTS]; + + struct v4l2_subdev sd; + struct media_pad pads[UB960_MAX_NPORTS]; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_async_notifier notifier; + + u32 tx_data_rate; /* Nominal data rate (Gb/s) */ + s64 tx_link_freq[1]; + + struct atr_alias_table atr_alias_table; + + u8 current_read_rxport; + u8 current_write_rxport_mask; + + u8 current_read_csiport; + u8 current_write_csiport_mask; + + u8 current_indirect_target; + + bool streaming; + + u8 stored_fwd_ctl; + + u64 stream_enable_mask[UB960_MAX_NPORTS]; + + /* These are common to all ports */ + struct { + bool manual; + + s8 min; + s8 max; + } strobe; +}; + +static void ub960_reset(struct ub960_data *priv, bool reset_regs); + +static inline struct ub960_data *sd_to_ub960(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub960_data, sd); +} + +enum { + TEST_PATTERN_DISABLED = 0, + TEST_PATTERN_V_COLOR_BARS_1, + TEST_PATTERN_V_COLOR_BARS_2, + TEST_PATTERN_V_COLOR_BARS_4, + TEST_PATTERN_V_COLOR_BARS_8, +}; + +static const char *const ub960_tpg_qmenu[] = { + "Disabled", + "1 vertical color bar", + "2 vertical color bars", + "4 vertical color bars", + "8 vertical color bars", +}; + +static inline bool ub960_pad_is_sink(struct ub960_data *priv, u32 pad) +{ + return pad < priv->hw_data->num_rxports; +} + +static inline bool ub960_pad_is_source(struct ub960_data *priv, u32 pad) +{ + return pad >= priv->hw_data->num_rxports && + pad < (priv->hw_data->num_rxports + priv->hw_data->num_txports); +} + +static inline unsigned int ub960_pad_to_port(struct ub960_data *priv, u32 pad) +{ + if (ub960_pad_is_sink(priv, pad)) + return pad; + else + return pad - priv->hw_data->num_rxports; +} + +struct ub960_format_info { + u32 code; + u32 bpp; + u8 datatype; + bool meta; +}; + +static const struct ub960_format_info ub960_formats[] = { + { .code = MEDIA_BUS_FMT_YUYV8_1X16, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_VYUY8_1X16, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_YVYU8_1X16, .bpp = 16, .datatype = 0x1e, }, + + /* Legacy */ + { .code = MEDIA_BUS_FMT_YUYV8_2X8, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_UYVY8_2X8, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_VYUY8_2X8, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_YVYU8_2X8, .bpp = 16, .datatype = 0x1e, }, + + /* RAW */ + { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, .datatype = 0x2c, }, + { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, .datatype = 0x2c, }, +}; + +static const struct ub960_format_info *ub960_find_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ub960_formats); ++i) { + if (ub960_formats[i].code == code) + return &ub960_formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Basic device access + */ + +static int ub960_read(struct ub960_data *priv, u8 reg, u8 *val) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write(struct ub960_data *priv, u8 reg, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_update_bits(struct ub960_data *priv, u8 reg, u8 mask, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub960_rxport_select(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_read_rxport == nport && + priv->current_write_rxport_mask == BIT(nport)) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_FPD3_PORT_SEL, + (nport << 4) | BIT(nport)); + if (ret) { + dev_err(dev, "%s: cannot select rxport %d (%d)!\n", __func__, + nport, ret); + return ret; + } + + priv->current_read_rxport = nport; + priv->current_write_rxport_mask = BIT(nport); + + return 0; +} + +static int ub960_rxport_read(struct ub960_data *priv, u8 nport, u8 reg, u8 *val) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_rxport_write(struct ub960_data *priv, u8 nport, u8 reg, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_rxport_update_bits(struct ub960_data *priv, u8 nport, u8 reg, + u8 mask, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub960_csiport_select(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_read_csiport == nport && + priv->current_write_csiport_mask == BIT(nport)) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_CSI_PORT_SEL, + (nport << 4) | BIT(nport)); + if (ret) { + dev_err(dev, "%s: cannot select csi port %d (%d)!\n", __func__, + nport, ret); + return ret; + } + + priv->current_read_csiport = nport; + priv->current_write_csiport_mask = BIT(nport); + + return 0; +} + +static int ub960_csiport_read(struct ub960_data *priv, u8 nport, u8 reg, + u8 *val) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_csiport_write(struct ub960_data *priv, u8 nport, u8 reg, + u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_csiport_update_bits(struct ub960_data *priv, u8 nport, u8 reg, + u8 mask, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub960_select_ind_reg_block(struct ub960_data *priv, u8 block) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_indirect_target == block) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_CTL, block << 2); + if (ret) { + dev_err(dev, "%s: cannot select indirect target %u (%d)!\n", + __func__, block, ret); + return ret; + } + + priv->current_indirect_target = block; + + return 0; +} + +static int ub960_read_ind(struct ub960_data *priv, u8 block, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write_ind(struct ub960_data *priv, u8 block, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n", + block, reg, ret); + goto out_unlock; + } + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write_ind16(struct ub960_data *priv, u8 block, u8 reg, u16 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val >> 8); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg + 1); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val & 0xff); + if (ret) + goto out_unlock; + +out_unlock: + 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; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when updating %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_update_bits(priv->regmap, UB960_SR_IND_ACC_DATA, mask, + val); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when updating %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * I2C-ATR (address translator) + */ + +static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client, + u16 *alias_id) +{ + struct ub960_data *priv = i2c_atr_get_driver_data(atr); + struct ub960_rxport *rxport = priv->rxports[chan_id]; + struct device *dev = &priv->client->dev; + struct atr_alias_table_entry *entry = NULL; + unsigned int reg_idx; + unsigned int pool_idx; + u16 alias; + int ret = 0; + u8 port_reg_idx_mask = 0; + + dev_dbg(dev, "rx%u: %s\n", chan_id, __func__); + + mutex_lock(&priv->atr_alias_table.lock); + + /* + * Go through the alias table and: + * 1. Look for an unreserved entry + * 2. Construct a bitmask of port's used alias entries + */ + + for (pool_idx = 0; pool_idx < priv->atr_alias_table.num_entries; pool_idx++) { + struct atr_alias_table_entry *e; + + e = &priv->atr_alias_table.entries[pool_idx]; + + if (!entry && !e->reserved) + entry = e; + + if (e->reserved && e->nport == rxport->nport) + port_reg_idx_mask |= BIT(e->port_reg_idx); + } + + if (!entry) { + dev_err(dev, "rx%u: alias pool exhausted\n", rxport->nport); + ret = -EADDRNOTAVAIL; + goto out_unlock; + } + + if (port_reg_idx_mask == GENMASK(UB960_MAX_PORT_ALIASES - 1, 0)) { + dev_err(dev, "rx%u: all aliases in use\n", rxport->nport); + ret = -EADDRNOTAVAIL; + goto out_unlock; + } + + alias = entry->alias_id; + + reg_idx = ffz(port_reg_idx_mask); + + entry->reserved = true; + entry->nport = rxport->nport; + entry->slave_id = client->addr; + entry->port_reg_idx = reg_idx; + + /* Map alias to slave */ + + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ID(reg_idx), + client->addr << 1); + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), + alias << 1); + + *alias_id = alias; /* tell the atr which alias we chose */ + + dev_dbg(dev, "rx%u: client 0x%02x mapped at alias 0x%02x (%s)\n", + rxport->nport, client->addr, alias, client->name); + +out_unlock: + mutex_unlock(&priv->atr_alias_table.lock); + return ret; +} + +static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client) +{ + struct ub960_data *priv = i2c_atr_get_driver_data(atr); + struct ub960_rxport *rxport = priv->rxports[chan_id]; + struct device *dev = &priv->client->dev; + struct atr_alias_table_entry *entry; + unsigned int reg_idx; + unsigned int pool_idx; + u16 alias = 0; + + dev_dbg(dev, "rx%u: %s\n", chan_id, __func__); + + mutex_lock(&priv->atr_alias_table.lock); + + /* Find alias mapped to this client */ + + for (pool_idx = 0; pool_idx < priv->atr_alias_table.num_entries; pool_idx++) { + entry = &priv->atr_alias_table.entries[pool_idx]; + + if (entry->reserved && entry->nport == rxport->nport && + entry->slave_id == client->addr) + break; + } + + if (pool_idx == priv->atr_alias_table.num_entries) { + dev_err(dev, "rx%u: client 0x%02x is not mapped!\n", + rxport->nport, client->addr); + goto out_unlock; + } + + alias = entry->alias_id; + + reg_idx = entry->port_reg_idx; + + /* Unmap */ + + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), 0); + + entry->reserved = false; + + dev_dbg(dev, "rx%u: client 0x%02x unmapped from alias 0x%02x (%s)\n", + rxport->nport, client->addr, alias, client->name); + +out_unlock: + mutex_unlock(&priv->atr_alias_table.lock); +} + +static const struct i2c_atr_ops ub960_atr_ops = { + .attach_client = ub960_atr_attach_client, + .detach_client = ub960_atr_detach_client, +}; + +/* ----------------------------------------------------------------------------- + * CSI ports + */ + +static int ub960_parse_dt_txport(struct ub960_data *priv, + const struct fwnode_handle *ep_fwnode, + u8 nport) +{ + struct device *dev = &priv->client->dev; + struct ub960_txport *txport; + int ret; + u64 freq; + + txport = kzalloc(sizeof(*txport), GFP_KERNEL); + if (!txport) + return -ENOMEM; + + txport->priv = priv; + txport->nport = nport; + + priv->txports[nport] = txport; + + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes"); + if (ret < 0) { + dev_err(dev, "tx%u: failed to parse 'data-lanes': %d\n", nport, + ret); + goto err_free_txport; + } + + txport->num_data_lanes = ret; + + ret = fwnode_property_read_u64(ep_fwnode, "link-frequencies", &freq); + if (ret) { + dev_err(dev, "tx%u: failed to read 'link-frequencies': %d\n", + nport, ret); + goto err_free_txport; + } + + priv->tx_link_freq[0] = freq; + priv->tx_data_rate = freq * 2; + + if (priv->tx_data_rate != MHZ(1600) && + priv->tx_data_rate != MHZ(1200) && + priv->tx_data_rate != MHZ(800) && + priv->tx_data_rate != MHZ(400)) { + dev_err(dev, "tx%u: invalid 'link-frequencies' value\n", nport); + return -EINVAL; + } + + dev_dbg(dev, "tx%u: nominal data rate: %u", nport, priv->tx_data_rate); + + return 0; + +err_free_txport: + kfree(txport); + + return ret; +} + +static void ub960_csi_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + u8 csi_tx_isr; + int ret; + + ret = ub960_csiport_read(priv, nport, UB960_TR_CSI_TX_ISR, &csi_tx_isr); + if (ret) + return; + + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR) + dev_warn(dev, "TX%u: CSI_SYNC_ERROR\n", nport); + + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR) + dev_warn(dev, "TX%u: CSI_PASS_ERROR\n", nport); +} + +/* ----------------------------------------------------------------------------- + * RX ports + */ + +static int ub960_rxport_enable_vpocs(struct ub960_data *priv) +{ + unsigned int nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport || !rxport->vpoc) + continue; + + ret = regulator_enable(rxport->vpoc); + if (ret) + goto err_disable_vpocs; + } + + return 0; + +err_disable_vpocs: + while (nport--) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport || !rxport->vpoc) + continue; + + regulator_disable(rxport->vpoc); + } + + return ret; +} + +static void ub960_rxport_disable_vpocs(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport || !rxport->vpoc) + continue; + + regulator_disable(rxport->vpoc); + } +} + +static void ub960_rxport_clear_errors(struct ub960_data *priv, + unsigned int nport) +{ + u8 v; + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v); + ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &v); + ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &v); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v); +} + +static void ub960_clear_rx_errors(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) + ub960_rxport_clear_errors(priv, nport); +} + +static int ub960_rxport_get_strobe_pos(struct ub960_data *priv, + unsigned int nport, s8 *strobe_pos) +{ + u8 v; + u8 clk_delay, data_delay; + int ret; + + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, &v); + + clk_delay = (v & UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY) ? + 0 : + UB960_MANUAL_STROBE_EXTRA_DELAY; + + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, &v); + + data_delay = (v & UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY) ? + 0 : + UB960_MANUAL_STROBE_EXTRA_DELAY; + + ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v); + if (ret) + return ret; + + clk_delay += v & UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK; + + ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_1, &v); + if (ret) + return ret; + + data_delay += v & UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK; + + *strobe_pos = data_delay - clk_delay; + + return 0; +} + +static void ub960_rxport_set_strobe_pos(struct ub960_data *priv, + unsigned int nport, s8 strobe_pos) +{ + u8 clk_delay, data_delay; + + clk_delay = UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + data_delay = UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + if (strobe_pos < UB960_MIN_AEQ_STROBE_POS) + clk_delay = abs(strobe_pos) - UB960_MANUAL_STROBE_EXTRA_DELAY; + else if (strobe_pos > UB960_MAX_AEQ_STROBE_POS) + data_delay = strobe_pos - UB960_MANUAL_STROBE_EXTRA_DELAY; + else if (strobe_pos < 0) + clk_delay = abs(strobe_pos) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + else if (strobe_pos > 0) + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay); +} + +static void ub960_rxport_set_strobe_range(struct ub960_data *priv, + s8 strobe_min, s8 strobe_max) +{ + /* Convert the signed strobe pos to positive zero based value */ + strobe_min -= UB960_MIN_AEQ_STROBE_POS; + strobe_max -= UB960_MIN_AEQ_STROBE_POS; + + ub960_write(priv, UB960_XR_SFILTER_CFG, + ((u8)strobe_min << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) | + ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT)); +} + +static int ub960_rxport_get_eq_level(struct ub960_data *priv, + unsigned int nport, u8 *eq_level) +{ + int ret; + u8 v; + + ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_STATUS, &v); + if (ret) + return ret; + + *eq_level = (v & UB960_RR_AEQ_STATUS_STATUS_1) + + (v & UB960_RR_AEQ_STATUS_STATUS_2); + + 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; + const unsigned int eq_stage_max = 7; + u8 v; + + if (eq_level <= eq_stage_max) { + eq_stage_1_select_value = eq_level; + eq_stage_2_select_value = 0; + } else { + eq_stage_1_select_value = eq_stage_max; + eq_stage_2_select_value = eq_level - eq_stage_max; + } + + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v); + + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK | + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK); + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT; + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT; + v |= UB960_RR_AEQ_BYPASS_ENABLE; + + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v); +} + +static void ub960_rxport_set_eq_range(struct ub960_data *priv, + unsigned int nport, u8 eq_min, u8 eq_max) +{ + ub960_rxport_write(priv, nport, UB960_RR_AEQ_MIN_MAX, + (eq_min << UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) | + (eq_max << UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT)); + + /* Enable AEQ min setting */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_CTL2, + UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR, + UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR); +} + +static void ub960_rxport_config_eq(struct ub960_data *priv, unsigned int nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + + /* We also set common settings here. Should be moved elsewhere. */ + + if (priv->strobe.manual) { + /* Disable AEQ_SFILTER_EN */ + ub960_update_bits(priv, UB960_XR_AEQ_CTL1, + UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN, 0); + } else { + /* Enable SFILTER and error control */ + ub960_write(priv, UB960_XR_AEQ_CTL1, + UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_MASK | + UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN); + + /* Set AEQ strobe range */ + ub960_rxport_set_strobe_range(priv, priv->strobe.min, + priv->strobe.max); + } + + /* The rest are port specific */ + + if (priv->strobe.manual) + ub960_rxport_set_strobe_pos(priv, nport, rxport->eq.strobe_pos); + else + ub960_rxport_set_strobe_pos(priv, nport, 0); + + if (rxport->eq.manual_eq) { + ub960_rxport_set_eq_level(priv, nport, + rxport->eq.manual.eq_level); + + /* Enable AEQ Bypass */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + UB960_RR_AEQ_BYPASS_ENABLE, + UB960_RR_AEQ_BYPASS_ENABLE); + } else { + ub960_rxport_set_eq_range(priv, nport, + rxport->eq.aeq.eq_level_min, + rxport->eq.aeq.eq_level_max); + + /* Disable AEQ Bypass */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + UB960_RR_AEQ_BYPASS_ENABLE, 0); + } +} + +static int ub960_rxport_link_ok(struct ub960_data *priv, unsigned int nport, + bool *ok) +{ + u8 rx_port_sts1, rx_port_sts2; + unsigned int parity_errors; + u8 csi_rx_sts; + u8 csi_err_cnt; + u8 v1, v2; + u8 bcc_sts; + int ret; + bool errors; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &rx_port_sts1); + if (ret) + return ret; + + if (!(rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS)) { + *ok = false; + return 0; + } + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &csi_rx_sts); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, + &csi_err_cnt); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &bcc_sts); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + if (ret) + return ret; + + parity_errors = (v1 << 8) | v2; + + errors = (rx_port_sts1 & UB960_RR_RX_PORT_STS1_ERROR_MASK) || + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_ERROR_MASK) || + (bcc_sts & UB960_RR_BCC_STATUS_ERROR_MASK) || + (csi_rx_sts & UB960_RR_CSI_RX_STS_ERROR_MASK) || csi_err_cnt || + parity_errors; + + *ok = !errors; + + return 0; +} + +/* + * Wait for the RX ports to lock, have no errors and have stable strobe position + * and EQ level. + */ +static int ub960_rxport_wait_locks(struct ub960_data *priv, + unsigned long port_mask, + unsigned int *lock_mask) +{ + struct device *dev = &priv->client->dev; + unsigned long timeout; + unsigned int link_ok_mask; + unsigned int missing; + unsigned int loops; + u8 nport; + int ret; + + if (port_mask == 0) + return 0; + + if (port_mask >= BIT(priv->hw_data->num_rxports)) + return -EINVAL; + + timeout = jiffies + msecs_to_jiffies(1000); + loops = 0; + link_ok_mask = 0; + + while (time_before(jiffies, timeout)) { + missing = 0; + + for_each_set_bit(nport, &port_mask, + priv->hw_data->num_rxports) { + struct ub960_rxport *rxport = priv->rxports[nport]; + bool ok; + + if (!rxport) + continue; + + ret = ub960_rxport_link_ok(priv, nport, &ok); + if (ret) + return ret; + + if (!ok || !(link_ok_mask & BIT(nport))) + missing++; + + if (ok) + link_ok_mask |= BIT(nport); + else + link_ok_mask &= ~BIT(nport); + } + + loops++; + + if (missing == 0) + break; + + msleep(50); + } + + if (lock_mask) + *lock_mask = link_ok_mask; + + dev_dbg(dev, "Wait locks done in %u loops\n", loops); + for_each_set_bit(nport, &port_mask, priv->hw_data->num_rxports) { + struct ub960_rxport *rxport = priv->rxports[nport]; + s8 strobe_pos, eq_level; + u8 v1, v2; + + if (!rxport) + continue; + + if (!(link_ok_mask & BIT(nport))) { + dev_dbg(dev, "\trx%u: not locked\n", nport); + continue; + } + + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2); + + ret = ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos); + if (ret) + return ret; + + ret = ub960_rxport_get_eq_level(priv, nport, &eq_level); + if (ret) + return ret; + + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n", + nport, strobe_pos, eq_level, + v1 * 1000000 + v2 * 1000000 / 256); + } + + return 0; +} + +static int ub960_init_atr(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + struct i2c_adapter *parent_adap = priv->client->adapter; + + priv->atr = i2c_atr_new(parent_adap, dev, &ub960_atr_ops, + priv->hw_data->num_rxports); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_driver_data(priv->atr, priv); + + return 0; +} + +static void ub960_uninit_atr(struct ub960_data *priv) +{ + i2c_atr_delete(priv->atr); + priv->atr = NULL; +} + +static unsigned long ub960_calc_bc_clk_rate_ub960(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int mult; + unsigned int div; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + mult = 1; + div = 10; + break; + + case RXPORT_MODE_CSI2_SYNC: + mult = 2; + div = 1; + break; + + case RXPORT_MODE_CSI2_ASYNC: + mult = 2; + div = 5; + break; + + default: + 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: + return 0; + } +} + +static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + struct device *dev = &priv->client->dev; + struct ds90ub9xx_platform_data *ser_pdata = &rxport->ser_platform_data; + struct i2c_board_info ser_info = { + .of_node = to_of_node(rxport->remote_fwnode), + .fwnode = rxport->remote_fwnode, + .platform_data = ser_pdata, + }; + + ser_pdata->port = nport; + ser_pdata->atr = priv->atr; + if (priv->hw_data->is_ub9702) + ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub9702(priv, rxport); + else + ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub960(priv, rxport); + + /* + * Adding the serializer under rxport->adap would be cleaner, but it + * would need tweaks to bypass the alias table. Adding to the + * upstream adapter is way simpler. + */ + ser_info.addr = rxport->ser_alias; + rxport->ser_client = + i2c_new_client_device(priv->client->adapter, &ser_info); + if (!rxport->ser_client) { + dev_err(dev, "rx%u: cannot add %s i2c device", nport, + ser_info.type); + return -EIO; + } + + dev_dbg(dev, "rx%u: remote serializer at alias 0x%02x (%u-%04x)\n", + nport, rxport->ser_client->addr, + rxport->ser_client->adapter->nr, rxport->ser_client->addr); + + return 0; +} + +static void ub960_rxport_remove_serializer(struct ub960_data *priv, u8 nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + + i2c_unregister_device(rxport->ser_client); + rxport->ser_client = NULL; +} + +/* Add serializer i2c devices for all initialized ports */ +static int ub960_rxport_add_serializers(struct ub960_data *priv) +{ + unsigned int nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + ret = ub960_rxport_add_serializer(priv, nport); + if (ret) + goto err_remove_sers; + } + + return 0; + +err_remove_sers: + while (nport--) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + rxport = priv->rxports[nport]; + if (!rxport) + continue; + + ub960_rxport_remove_serializer(priv, nport); + } + + return ret; +} + +static void ub960_rxport_remove_serializers(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + ub960_rxport_remove_serializer(priv, nport); + } +} + +static void ub960_init_tx_port(struct ub960_data *priv, + struct ub960_txport *txport) +{ + unsigned int nport = txport->nport; + u8 csi_ctl = 0; + + /* + * From the datasheet: "initial CSI Skew-Calibration + * sequence [...] should be set when operating at 1.6 Gbps" + */ + if (priv->tx_data_rate == MHZ(1600)) + csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN; + + csi_ctl |= (4 - txport->num_data_lanes) << 4; + + ub960_csiport_write(priv, nport, UB960_TR_CSI_CTL, csi_ctl); +} + +static int ub960_init_tx_ports(struct ub960_data *priv) +{ + unsigned int nport; + u8 speed_select; + u8 pll_div; + + /* TX ports */ + + switch (priv->tx_data_rate) { + case MHZ(1600): + default: + speed_select = 0; + pll_div = 0x10; + break; + case MHZ(1200): + speed_select = 1; + break; + case MHZ(800): + speed_select = 2; + pll_div = 0x10; + break; + case MHZ(400): + speed_select = 3; + pll_div = 0x10; + break; + } + + ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select); + + if (priv->hw_data->is_ub9702) { + ub960_write(priv, UB960_SR_CSI_PLL_DIV, pll_div); + + switch (priv->tx_data_rate) { + case MHZ(1600): + 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 MHZ(800): + 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 MHZ(400): + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0xA0); + break; + } + } + + for (nport = 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + if (!txport) + continue; + + ub960_init_tx_port(priv, txport); + } + + return 0; +} + +static void ub960_init_rx_port_ub960(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u32 bc_freq_val; + + /* + * Back channel frequency select. + * Override FREQ_SELECT from the strap. + * 0 - 2.5 Mbps (DS90UB913A-Q1 / DS90UB933-Q1) + * 2 - 10 Mbps + * 6 - 50 Mbps (DS90UB953-Q1) + * + * Note that changing this setting will result in some errors on the back + * channel for a short period of time. + */ + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val = 2; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + break; + + default: + return; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_BC_FREQ_SEL_MASK, + bc_freq_val); + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + /* FPD3_MODE = RAW10 Mode (DS90UB913A-Q1 / DS90UB933-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, + UB960_RR_PORT_CONFIG_FPD3_MODE_MASK, + 0x3); + + /* + * RAW10_8BIT_CTL = 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, + UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_MASK, + 0x2 << UB960_RR_PORT_CONFIG2_RAW10_8BIT_CTL_SHIFT); + + break; + + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + /* Not implemented */ + return; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + /* CSI-2 Mode (DS90UB953-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3, + 0x0); + + break; + } + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, + rxport->lv_fv_pol); + + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07); + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH); + + /* Enable I2C communication to the serializer via the alias addr */ + ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID, + rxport->ser_alias << 1); + + /* Configure EQ related settings */ + ub960_rxport_config_eq(priv, nport); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport)); +} + +static void ub960_init_rx_port_ub9702_fpd3(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u8 bc_freq_val; + u8 fpd_func_mode; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + bc_freq_val = 0; + fpd_func_mode = 5; + break; + + case RXPORT_MODE_RAW12_HF: + bc_freq_val = 0; + fpd_func_mode = 4; + break; + + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + fpd_func_mode = 6; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + fpd_func_mode = 2; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val = 2; + fpd_func_mode = 2; + break; + + default: + return; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, fpd_func_mode); + + /* set serdes_eq_mode = 1 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xA8, 0x80); + + /* enable serdes driver */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x0D, 0x7F); + + /* set serdes_eq_offset=4 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04); + + /* init default serdes_eq_max in 0xA9 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xA9, 0x23); + + /* init serdes_eq_min in 0xAA */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xAA, 0); + + /* serdes_driver_ctl2 control: DS90UB953-Q1/DS90UB933-Q1/DS90UB913A-Q1 */ + ub960_ind_update_bits(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1b, + BIT(3), BIT(3)); + + /* RX port to half-rate */ + ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2), + BIT(nport * 2)); +} + +static void ub960_init_rx_port_ub9702_fpd4_aeq(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + bool first_time_power_up = true; + + if (first_time_power_up) { + u8 v; + + /* AEQ init */ + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2C, &v); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, v); + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, v + 1); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x00); + } + + /* enable serdes_eq_ctl2 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x9E, 0x00); + + /* enable serdes_eq_ctl1 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x90, 0x40); + + /* enable serdes_eq_en */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2E, 0x40); + + /* disable serdes_eq_override */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xF0, 0x00); + + /* disable serdes_gain_override */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x71, 0x00); +} + +static void ub960_init_rx_port_ub9702_fpd4(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u8 bc_freq_val; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + bc_freq_val = 0; + break; + + case RXPORT_MODE_RAW12_HF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val = 2; + break; + + default: + return; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + + /* FPD4 Sync Mode */ + ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, 0); + + /* add serdes_eq_offset of 4 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04); + + /* FPD4 serdes_start_eq in 0x27: assign default */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, 0x0); + /* FPD4 serdes_end_eq in 0x28: assign default */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, 0x23); + + /* set serdes_driver_mode into FPD IV mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x04, 0x00); + /* set FPD PBC drv into FPD IV mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1B, 0x00); + + /* set serdes_system_init to 0x2f */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x21, 0x2f); + /* set serdes_system_rst in reset mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0xC1); + + /* RX port to 7.55G mode */ + ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2), + 0 << (nport * 2)); + + ub960_init_rx_port_ub9702_fpd4_aeq(priv, rxport); +} + +static void ub960_init_rx_port_ub9702(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + + if (rxport->cdr_mode == RXPORT_CDR_FPD3) + ub960_init_rx_port_ub9702_fpd3(priv, rxport); + else /* RXPORT_CDR_FPD4 */ + ub960_init_rx_port_ub9702_fpd4(priv, rxport); + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + /* + * RAW10_8BIT_CTL = 0b11 : 8-bit processing using lower 8 bits + * 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, + 0x3 << 6, 0x2 << 6); + + break; + + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + /* Not implemented */ + return; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + + break; + } + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, 0x1); + + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07); + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH); + + /* Enable I2C communication to the serializer via the alias addr */ + ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID, + rxport->ser_alias << 1); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport)); + + if (rxport->cdr_mode == RXPORT_CDR_FPD4) { + /* unreset 960 AEQ */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0x41); + } +} + +static int ub960_init_rx_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; nport++) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + if (priv->hw_data->is_ub9702) + ub960_init_rx_port_ub9702(priv, rxport); + else + ub960_init_rx_port_ub960(priv, rxport); + } + + return 0; +} + +static void ub960_rxport_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + u8 rx_port_sts1; + u8 rx_port_sts2; + u8 csi_rx_sts; + u8 bcc_sts; + int ret = 0; + + /* Read interrupts (also clears most of them) */ + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &rx_port_sts1); + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2); + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, + &csi_rx_sts); + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, + &bcc_sts); + + if (ret) + return; + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PARITY_ERROR) { + u8 v1, v2; + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + dev_err(dev, "rx%u parity errors: %u\n", nport, (v1 << 8) | v2); + } + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR) + dev_err(dev, "rx%u BCC CRC error\n", nport); + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR) + dev_err(dev, "rx%u BCC SEQ error\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE) + dev_err(dev, "rx%u line length unstable\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR) + dev_err(dev, "rx%u FPD3 encode error\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_BUFFER_ERROR) + dev_err(dev, "rx%u buffer error\n", nport); + + if (csi_rx_sts) + dev_err(dev, "rx%u CSI error: %#02x\n", nport, csi_rx_sts); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC1_ERR) + dev_err(dev, "rx%u CSI ECC1 error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC2_ERR) + dev_err(dev, "rx%u CSI ECC2 error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_CKSUM_ERR) + dev_err(dev, "rx%u CSI checksum error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_LENGTH_ERR) + dev_err(dev, "rx%u CSI length error\n", nport); + + if (bcc_sts) + dev_err(dev, "rx%u BCC error: %#02x\n", nport, bcc_sts); + + if (bcc_sts & UB960_RR_BCC_STATUS_RESP_ERR) + dev_err(dev, "rx%u BCC response error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_TO) + dev_err(dev, "rx%u BCC slave timeout", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_ERR) + dev_err(dev, "rx%u BCC slave error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_TO) + dev_err(dev, "rx%u BCC master timeout", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_ERR) + dev_err(dev, "rx%u BCC master error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SEQ_ERROR) + dev_err(dev, "rx%u BCC sequence error", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_CHG) { + u8 v1, v2; + + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2); + dev_dbg(dev, "rx%u line len changed: %u\n", nport, + (v1 << 8) | v2); + } + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_CNT_CHG) { + u8 v1, v2; + + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2); + dev_dbg(dev, "rx%u line count changed: %u\n", nport, + (v1 << 8) | v2); + } + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS_CHG) { + dev_dbg(dev, "rx%u: %s, %s, %s, %s\n", nport, + (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS) ? + "locked" : + "unlocked", + (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PORT_PASS) ? + "passed" : + "not passed", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_CABLE_FAULT) ? + "no clock" : + "clock ok", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FREQ_STABLE) ? + "stable freq" : + "unstable freq"); + } +} + +/* ----------------------------------------------------------------------------- + * V4L2 + */ + +static void ub960_get_vc_maps(struct ub960_data *priv, + struct v4l2_subdev_state *state, u8 *vc) +{ + const struct v4l2_subdev_krouting *routing = &state->routing; + u8 cur_vc[UB960_MAX_TX_NPORTS] = { }; + u8 handled_mask = 0; + unsigned int i; + + for (i = 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route = &routing->routes[i]; + unsigned int rx, tx; + + rx = ub960_pad_to_port(priv, route->sink_pad); + if (BIT(rx) & handled_mask) + continue; + + tx = ub960_pad_to_port(priv, route->source_pad); + + vc[rx] = cur_vc[tx]++; + handled_mask |= BIT(rx); + } +} + +static int ub960_enable_tx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "enable TX port %u\n", nport); + + ub960_csiport_update_bits(priv, nport, UB960_TR_CSI_CTL, + UB960_TR_CSI_CTL_CSI_ENABLE, + UB960_TR_CSI_CTL_CSI_ENABLE); + + return 0; +} + +static void ub960_disable_tx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "disable TX port %u\n", nport); + + ub960_csiport_update_bits(priv, nport, UB960_TR_CSI_CTL, + UB960_TR_CSI_CTL_CSI_ENABLE, 0); +} + +static int ub960_enable_rx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "enable RX port %u\n", nport); + + /* Enable forwarding */ + ub960_update_bits(priv, UB960_SR_FWD_CTL1, BIT(4 + nport), 0); + + return 0; +} + +static void ub960_disable_rx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "disable RX port %u\n", nport); + + /* Disable forwarding */ + ub960_update_bits(priv, UB960_SR_FWD_CTL1, BIT(4 + nport), + BIT(4 + nport)); +} + +static int ub960_configure_ports_for_streaming(struct ub960_data *priv, + struct v4l2_subdev_state *state) +{ + const struct v4l2_subdev_krouting *routing = &state->routing; + u8 fwd_ctl; + struct { + u32 num_streams; + u8 pixel_dt; + u8 meta_dt; + u32 meta_lines; + u32 tx_port; + } rx_data[UB960_MAX_RX_NPORTS] = { }; + u8 vc_map[UB960_MAX_RX_NPORTS] = { }; + + ub960_get_vc_maps(priv, state, vc_map); + + for (unsigned int i = 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route = &routing->routes[i]; + struct ub960_rxport *rxport; + struct ub960_txport *txport; + struct v4l2_mbus_framefmt *fmt; + const struct ub960_format_info *ub960_fmt; + unsigned int nport; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + rxport = priv->rxports[nport]; + if (!rxport) + return -EINVAL; + + txport = priv->txports[ub960_pad_to_port(priv, route->source_pad)]; + if (!txport) + return -EINVAL; + + rx_data[nport].tx_port = ub960_pad_to_port(priv, route->source_pad); + + rx_data[nport].num_streams++; + + /* For the rest, we are only interested in parallel busses */ + if (rxport->rx_mode == RXPORT_MODE_CSI2_SYNC || + rxport->rx_mode == RXPORT_MODE_CSI2_ASYNC) + continue; + + if (rx_data[nport].num_streams > 2) + return -EPIPE; + + fmt = v4l2_subdev_state_get_stream_format(state, + route->sink_pad, + route->sink_stream); + if (!fmt) + return -EPIPE; + + ub960_fmt = ub960_find_format(fmt->code); + if (!ub960_fmt) + return -EPIPE; + + if (ub960_fmt->meta) { + if (fmt->height > 3) { + dev_err(&priv->client->dev, + "rx%u: unsupported metadata height %u\n", + nport, fmt->height); + return -EPIPE; + } + + rx_data[nport].meta_dt = ub960_fmt->datatype; + rx_data[nport].meta_lines = fmt->height; + } else { + rx_data[nport].pixel_dt = ub960_fmt->datatype; + } + } + + /* Configure RX ports */ + + fwd_ctl = 0; + + for (unsigned int nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + u8 vc = vc_map[nport]; + + if (rx_data[nport].num_streams == 0) + continue; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + ub960_rxport_write(priv, nport, UB960_RR_RAW10_ID, + rx_data[nport].pixel_dt | (vc << UB960_RR_RAW10_ID_VC_SHIFT)); + + ub960_rxport_write(priv, rxport->nport, + UB960_RR_RAW_EMBED_DTYPE, + (rx_data[nport].meta_lines << UB960_RR_RAW_EMBED_DTYPE_LINES_SHIFT) | + rx_data[nport].meta_dt); + + break; + + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + /* Not implemented */ + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + if (!priv->hw_data->is_ub9702) { + /* Map all VCs from this port to the same VC */ + ub960_rxport_write(priv, nport, UB960_RR_CSI_VC_MAP, + (vc << UB960_RR_CSI_VC_MAP_SHIFT(3)) | + (vc << UB960_RR_CSI_VC_MAP_SHIFT(2)) | + (vc << UB960_RR_CSI_VC_MAP_SHIFT(1)) | + (vc << UB960_RR_CSI_VC_MAP_SHIFT(0))); + } else { + unsigned int i; + + /* Map all VCs from this port to VC(nport) */ + for (i = 0; i < 8; ++i) + ub960_rxport_write(priv, nport, + UB960_RR_VC_ID_MAP(i), + nport); + } + + break; + } + + /* Forwarding */ + + fwd_ctl |= BIT(4 + nport); /* forward disable */ + + if (rx_data[nport].tx_port == 1) + fwd_ctl |= BIT(nport); /* forward to TX1 */ + else + fwd_ctl &= ~BIT(nport); /* forward to TX0 */ + } + + ub960_write(priv, UB960_SR_FWD_CTL1, fwd_ctl); + + return 0; +} + +static void ub960_update_streaming_status(struct ub960_data *priv) +{ + unsigned int i; + + for (i = 0; i < UB960_MAX_NPORTS; ++i) { + if (priv->stream_enable_mask[i]) + break; + } + + priv->streaming = i < UB960_MAX_NPORTS; +} + +static int ub960_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 source_pad, + u64 source_streams_mask) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + const struct v4l2_subdev_krouting *routing; + unsigned int source_stream; + int ret; + u64 sink_streams[UB960_MAX_RX_NPORTS] = { }; + unsigned int nport; + unsigned int failed_port; + + dev_dbg(dev, "Enable streams %u:%#llx\n", source_pad, + source_streams_mask); + + if (priv->stream_enable_mask[source_pad] & source_streams_mask) { + dev_err(dev, + "cannot enable already enabled streams on pad %u mask %#llx\n", + source_pad, source_streams_mask); + return -EBUSY; + } + + routing = &state->routing; + + if (!priv->streaming) { + dev_dbg(dev, "Prepare for streaming\n"); + ret = ub960_configure_ports_for_streaming(priv, state); + if (ret) + return ret; + } + + /* Enable TX port if not yet enabled */ + if (!priv->stream_enable_mask[source_pad]) { + ret = ub960_enable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + if (ret) + return ret; + } + + priv->stream_enable_mask[source_pad] |= source_streams_mask; + + /* Collect sink streams per pad which we need to enable */ + for (source_stream = 0; source_stream < sizeof(source_streams_mask) * 8; + ++source_stream) { + struct v4l2_subdev_route *route; + + if (!(source_streams_mask & BIT_ULL(source_stream))) + continue; + + for_each_active_route(routing, route) { + if (!(route->source_pad == source_pad) || + !(route->source_stream == source_stream)) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |= BIT_ULL(route->sink_stream); + } + } + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + if (!sink_streams[nport]) + continue; + + /* Enable the RX port if not yet enabled */ + if (!priv->stream_enable_mask[nport]) { + ret = ub960_enable_rx_port(priv, nport); + if (ret) { + failed_port = nport; + goto err; + } + } + + priv->stream_enable_mask[nport] |= sink_streams[nport]; + + dev_dbg(dev, "Enable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + ret = v4l2_subdev_enable_streams( + priv->rxports[nport]->source_sd, + priv->rxports[nport]->source_sd_pad, + sink_streams[nport]); + if (ret) { + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + + failed_port = nport; + goto err; + } + } + + priv->streaming = true; + + return 0; + +err: + for (nport = 0; nport < failed_port; ++nport) { + if (!sink_streams[nport]) + continue; + + dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + ret = v4l2_subdev_disable_streams( + priv->rxports[nport]->source_sd, + priv->rxports[nport]->source_sd_pad, + sink_streams[nport]); + if (ret) + dev_err(dev, "Failed to disable streams: %d\n", ret); + + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + /* Disable RX port if no active streams */ + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + } + + priv->stream_enable_mask[source_pad] &= ~source_streams_mask; + + if (!priv->stream_enable_mask[source_pad]) + ub960_disable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + + ub960_update_streaming_status(priv); + + return ret; +} + +static int ub960_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 source_pad, u64 source_streams_mask) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + const struct v4l2_subdev_krouting *routing; + int ret; + unsigned int source_stream; + u64 sink_streams[UB960_MAX_RX_NPORTS] = { }; + unsigned int nport; + + dev_dbg(dev, "Disable streams %u:%#llx\n", source_pad, + source_streams_mask); + + if ((priv->stream_enable_mask[source_pad] & source_streams_mask) != source_streams_mask) { + dev_err(dev, + "cannot disable already disabled streams on pad %u mask %#llx\n", + source_pad, source_streams_mask); + return -EBUSY; + } + + routing = &state->routing; + + /* Collect sink streams per pad which we need to disable */ + for (source_stream = 0; source_stream < sizeof(source_streams_mask) * 8; + ++source_stream) { + struct v4l2_subdev_route *route; + + if (!(source_streams_mask & BIT_ULL(source_stream))) + continue; + + for_each_active_route(routing, route) { + if (!(route->source_pad == source_pad) || + !(route->source_stream == source_stream)) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |= BIT_ULL(route->sink_stream); + } + } + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + if (!sink_streams[nport]) + continue; + + dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + ret = v4l2_subdev_disable_streams( + priv->rxports[nport]->source_sd, + priv->rxports[nport]->source_sd_pad, + sink_streams[nport]); + if (ret) + dev_err(dev, "Failed to disable streams: %d\n", ret); + + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + /* Disable RX port if no active streams */ + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + } + + /* Disable TX port if no active streams */ + + priv->stream_enable_mask[source_pad] &= ~source_streams_mask; + + if (!priv->stream_enable_mask[source_pad]) + ub960_disable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + + ub960_update_streaming_status(priv); + + return 0; +} + +static int ub960_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ub960_data *priv = sd_to_ub960(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_state *state; + struct v4l2_subdev_route *route; + u64 pad_stream_masks[UB960_MAX_TX_NPORTS] = { }; + unsigned int nport; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + routing = &state->routing; + + for_each_active_route(routing, route) + pad_stream_masks[ub960_pad_to_port(priv, route->source_pad)] |= + BIT_ULL(route->source_stream); + + if (enable) { + for (nport = 0; nport < UB960_MAX_TX_NPORTS; ++nport) { + if (pad_stream_masks[nport] == 0) + continue; + + ret = ub960_enable_streams( + sd, state, priv->hw_data->num_rxports + nport, + pad_stream_masks[nport]); + + if (ret) { + while (nport--) { + if (pad_stream_masks[nport] == 0) + continue; + + ub960_disable_streams( + sd, state, + priv->hw_data->num_rxports + + nport, + pad_stream_masks[nport]); + } + + break; + } + } + } else { + for (nport = 0; nport < UB960_MAX_TX_NPORTS; ++nport) { + if (pad_stream_masks[nport] == 0) + continue; + + ub960_disable_streams(sd, state, + priv->hw_data->num_rxports + nport, + pad_stream_masks[nport]); + } + } + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static const struct v4l2_subdev_video_ops ub960_video_ops = { + .s_stream = ub960_s_stream, +}; + +static int _ub960_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + /* + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. + */ + + if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -E2BIG; + + /* + * TODO: We need a new flag to validate that all streams from a sink pad + * go to a single source pad. + */ + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int ub960_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub960_data *priv = sd_to_ub960(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + return _ub960_set_routing(sd, state, routing); +} + +static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub960_data *priv = sd_to_ub960(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret = 0; + struct device *dev = &priv->client->dev; + u8 vc_map[UB960_MAX_RX_NPORTS] = { }; + + if (!ub960_pad_is_source(priv, pad)) + return -EINVAL; + + memset(fd, 0, sizeof(*fd)); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ub960_get_vc_maps(priv, state, vc_map); + + routing = &state->routing; + + for_each_active_route(routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + struct v4l2_mbus_frame_desc source_fd; + unsigned int nport; + unsigned int i; + + if (route->source_pad != pad) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + ret = v4l2_subdev_call(priv->rxports[nport]->source_sd, pad, + get_frame_desc, + priv->rxports[nport]->source_sd_pad, + &source_fd); + if (ret) { + dev_err(dev, + "Failed to get source frame desc for pad %u\n", + route->sink_pad); + goto out_unlock; + } + + for (i = 0; i < source_fd.num_entries; ++i) + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + + if (!source_entry) { + dev_err(dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out_unlock; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_entry->flags; + fd->entry[fd->num_entries].length = source_entry->length; + fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode; + + fd->entry[fd->num_entries].bus.csi2.vc = vc_map[nport]; + + if (source_fd.type == V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + fd->entry[fd->num_entries].bus.csi2.dt = + source_entry->bus.csi2.dt; + } else { + const struct ub960_format_info *ub960_fmt; + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_stream_format(state, pad, + route->source_stream); + + if (!fmt) { + ret = -EINVAL; + goto out_unlock; + } + + ub960_fmt = ub960_find_format(fmt->code); + if (!ub960_fmt) { + dev_err(dev, "Unable to find format\n"); + ret = -EINVAL; + goto out_unlock; + } + + fd->entry[fd->num_entries].bus.csi2.dt = + ub960_fmt->datatype; + } + + fd->num_entries++; + } + +out_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub960_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (ub960_pad_is_source(priv, format->pad)) + return v4l2_subdev_get_fmt(sd, state, format); + + /* TODO: implement fmt validation */ + + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub960_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct ub960_data *priv = sd_to_ub960(sd); + + struct v4l2_subdev_route routes[] = { + { + .sink_pad = 0, + .sink_stream = 0, + .source_pad = priv->hw_data->num_rxports, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub960_set_routing(sd, state, &routing); +} + +static const struct v4l2_subdev_pad_ops ub960_pad_ops = { + .enable_streams = ub960_enable_streams, + .disable_streams = ub960_disable_streams, + + .set_routing = ub960_set_routing, + .get_frame_desc = ub960_get_frame_desc, + + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub960_set_fmt, + + .init_cfg = ub960_init_cfg, +}; + +static int ub960_log_status(struct v4l2_subdev *sd) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + struct v4l2_subdev_state *state; + unsigned int nport; + u8 v = 0, v1 = 0, v2 = 0; + u8 id[7]; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + for (unsigned int i = 0; i < 6; ++i) + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]); + id[6] = 0; + + dev_info(dev, "ID '%s'\n", id); + + for (nport = 0; nport < priv->hw_data->num_txports; ++nport) { + struct ub960_txport *txport = priv->txports[nport]; + + dev_info(dev, "TX %u\n", nport); + + if (!txport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ub960_csiport_read(priv, nport, UB960_TR_CSI_STS, &v); + dev_info(dev, "\tsync %u, pass %u\n", v & (u8)BIT(1), + v & (u8)BIT(0)); + + ub960_read(priv, UB960_SR_CSI_FRAME_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_FRAME_COUNT_LO(nport), &v2); + dev_info(dev, "\tframe counter %u\n", (v1 << 8) | v2); + + ub960_read(priv, UB960_SR_CSI_FRAME_ERR_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_FRAME_ERR_COUNT_LO(nport), &v2); + dev_info(dev, "\tframe error counter %u\n", (v1 << 8) | v2); + + ub960_read(priv, UB960_SR_CSI_LINE_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_LINE_COUNT_LO(nport), &v2); + dev_info(dev, "\tline counter %u\n", (v1 << 8) | v2); + + ub960_read(priv, UB960_SR_CSI_LINE_ERR_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_LINE_ERR_COUNT_LO(nport), &v2); + dev_info(dev, "\tline error counter %u\n", (v1 << 8) | v2); + } + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + u8 eq_level; + s8 strobe_pos; + unsigned int i; + + dev_info(dev, "RX %u\n", nport); + + if (!rxport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v); + + if (v & UB960_RR_RX_PORT_STS1_LOCK_STS) + dev_info(dev, "\tLocked\n"); + else + dev_info(dev, "\tNot locked\n"); + + dev_info(dev, "\trx_port_sts1 %#02x\n", v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v); + dev_info(dev, "\trx_port_sts2 %#02x\n", v); + + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2); + dev_info(dev, "\tlink freq %u MHz\n", + v1 * 1000000 + v2 * 1000000 / 256); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + dev_info(dev, "\tparity errors %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2); + dev_info(dev, "\tlines per frame %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2); + dev_info(dev, "\tbytes per line %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v); + dev_info(dev, "\tcsi_err_counter %u\n", v); + + /* Strobe */ + + ub960_read(priv, UB960_XR_AEQ_CTL1, &v); + + dev_info(dev, "\t%s strobe\n", + (v & UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN) ? "Adaptive" : + "Manual"); + + if (v & UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN) { + ub960_read(priv, UB960_XR_SFILTER_CFG, &v); + + dev_info(dev, "\tStrobe range [%d, %d]\n", + ((v >> UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) & 0xf) - 7, + ((v >> UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT) & 0xf) - 7); + } + + ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos); + + dev_info(dev, "\tStrobe pos %d\n", strobe_pos); + + /* EQ */ + + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v); + + dev_info(dev, "\t%s EQ\n", + (v & UB960_RR_AEQ_BYPASS_ENABLE) ? "Manual" : + "Adaptive"); + + if (!(v & UB960_RR_AEQ_BYPASS_ENABLE)) { + ub960_rxport_read(priv, nport, UB960_RR_AEQ_MIN_MAX, &v); + + dev_info(dev, "\tEQ range [%u, %u]\n", + (v >> UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) & 0xf, + (v >> UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT) & 0xf); + } + + if (ub960_rxport_get_eq_level(priv, nport, &eq_level) == 0) + dev_info(dev, "\tEQ level %u\n", eq_level); + + /* GPIOs */ + for (i = 0; i < UB960_NUM_BC_GPIOS; ++i) { + u8 ctl_reg; + u8 ctl_shift; + + ctl_reg = UB960_RR_BC_GPIO_CTL(i / 2); + ctl_shift = (i % 2) * 4; + + ub960_rxport_read(priv, nport, ctl_reg, &v); + + dev_info(dev, "\tGPIO%u: mode %u\n", i, + (v >> ctl_shift) & 0xf); + } + } + + v4l2_subdev_unlock_state(state); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub960_subdev_core_ops = { + .log_status = ub960_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ub960_subdev_ops = { + .core = &ub960_subdev_core_ops, + .video = &ub960_video_ops, + .pad = &ub960_pad_ops, +}; + +static const struct media_entity_operations ub960_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, +}; + +static void ub960_enable_tpg(struct ub960_data *priv, int tpg_num) +{ + /* + * Note: no need to write UB960_REG_IND_ACC_CTL: the only indirect + * registers target we use is "CSI-2 Pattern Generator & Timing + * Registers", which is the default + */ + + /* + * TPG can only provide a single stream per CSI TX port. If + * multiple streams are currently enabled, only the first + * one will use the TPG, other streams will be halted. + */ + + struct v4l2_mbus_framefmt *fmt; + u8 vbp, vfp; + u16 blank_lines; + u16 width; + u16 height; + + u16 bytespp = 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */ + u8 cbars_idx = tpg_num - TEST_PATTERN_V_COLOR_BARS_1; + u8 num_cbars = 1 << cbars_idx; + + u16 line_size; /* Line size [bytes] */ + u16 bar_size; /* cbar size [bytes] */ + u16 act_lpf; /* active lines/frame */ + u16 tot_lpf; /* tot lines/frame */ + u16 line_pd; /* Line period in 10-ns units */ + + struct v4l2_subdev_state *state; + + state = v4l2_subdev_get_locked_active_state(&priv->sd); + + vbp = 33; + vfp = 10; + blank_lines = vbp + vfp + 2; /* total blanking lines */ + + fmt = v4l2_subdev_state_get_stream_format(state, 4, 0); + if (!fmt) { + dev_err(&priv->client->dev, "failed to enable TPG\n"); + return; + } + + width = fmt->width; + height = fmt->height; + + line_size = width * bytespp; + bar_size = line_size / num_cbars; + act_lpf = height; + tot_lpf = act_lpf + blank_lines; + line_pd = 100000000 / 60 / tot_lpf; + + /* Disable forwarding from FPD-3 RX ports */ + ub960_read(priv, UB960_SR_FWD_CTL1, &priv->stored_fwd_ctl); + ub960_write(priv, UB960_SR_FWD_CTL1, 0xf << 4); + + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CTL, + UB960_IR_PGEN_CTL_PGEN_ENABLE); + + /* YUV422 8bit: 2 bytes/block, CSI-2 data type 0x1e */ + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CFG, + cbars_idx << 4 | 0x2); + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CSI_DI, + 0x1e); + + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_LINE_SIZE1, line_size); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_BAR_SIZE1, bar_size); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_ACT_LPF1, act_lpf); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_TOT_LPF1, tot_lpf); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_LINE_PD1, line_pd); + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_VBP, vbp); + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_VFP, vfp); +} + +static void ub960_disable_tpg(struct ub960_data *priv) +{ + /* TPG off, enable forwarding from FPD-3 RX ports */ + ub960_write(priv, UB960_SR_FWD_CTL1, priv->stored_fwd_ctl); + + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CTL, 0x00); +} + +static int ub960_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ub960_data *priv = + container_of(ctrl->handler, struct ub960_data, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + if (ctrl->val == 0) + ub960_disable_tpg(priv); + else + ub960_enable_tpg(priv, ctrl->val); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops ub960_ctrl_ops = { + .s_ctrl = ub960_s_ctrl, +}; + +/* ----------------------------------------------------------------------------- + * Core + */ + +static irqreturn_t ub960_handle_events(int irq, void *arg) +{ + struct ub960_data *priv = arg; + unsigned int i; + u8 int_sts; + u8 fwd_sts; + int ret; + + ret = ub960_read(priv, UB960_SR_INTERRUPT_STS, &int_sts); + if (ret || !int_sts) + return IRQ_NONE; + + dev_dbg(&priv->client->dev, "INTERRUPT_STS %x\n", int_sts); + + ub960_read(priv, UB960_SR_FWD_STS, &fwd_sts); + + dev_dbg(&priv->client->dev, "FWD_STS %#02x\n", fwd_sts); + + for (i = 0; i < priv->hw_data->num_txports; ++i) { + if (int_sts & UB960_SR_INTERRUPT_STS_IS_CSI_TX(i)) + ub960_csi_handle_events(priv, i); + } + + for (i = 0; i < priv->hw_data->num_rxports; ++i) { + if (!priv->rxports[i]) + continue; + + if (int_sts & UB960_SR_INTERRUPT_STS_IS_RX(i)) + ub960_rxport_handle_events(priv, i); + } + + return IRQ_HANDLED; +} + +static void ub960_handler_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ub960_data *priv = + container_of(dwork, struct ub960_data, poll_work); + + ub960_handle_events(0, priv); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); +} + +static void ub960_txport_free_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + if (!txport) + continue; + + kfree(txport); + priv->txports[nport] = NULL; + } +} + +static void ub960_rxport_free_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; nport++) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + fwnode_handle_put(rxport->source_ep_fwnode); + fwnode_handle_put(rxport->remote_fwnode); + + kfree(rxport); + priv->rxports[nport] = NULL; + } +} + +static int ub960_parse_dt_base(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + size_t table_size; + u16 *aliases; + int ret; + + ret = fwnode_property_count_u16(dev_fwnode(dev), "i2c-alias-pool"); + if (ret < 0) { + dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n", + ret); + return ret; + } + + table_size = ret; + priv->atr_alias_table.num_entries = ret; + + if (!table_size) + return 0; + + priv->atr_alias_table.entries = + devm_kcalloc(dev, table_size, + sizeof(struct atr_alias_table_entry), GFP_KERNEL); + if (!priv->atr_alias_table.entries) + return -ENOMEM; + + aliases = kcalloc(table_size, sizeof(u16), GFP_KERNEL); + if (!aliases) + return -ENOMEM; + + ret = fwnode_property_read_u16_array(dev_fwnode(dev), "i2c-alias-pool", + aliases, table_size); + if (ret < 0) { + dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n", + ret); + kfree(aliases); + return ret; + } + + for (unsigned int i = 0; i < table_size; ++i) + priv->atr_alias_table.entries[i].alias_id = aliases[i]; + + kfree(aliases); + + dev_dbg(dev, "i2c-alias-pool has %zu aliases", table_size); + + return 0; +} + +static int +ub960_parse_dt_rxport_link_properties(struct ub960_data *priv, + struct fwnode_handle *link_fwnode, + struct ub960_rxport *rxport) +{ + struct device *dev = &priv->client->dev; + unsigned int nport = rxport->nport; + u32 rx_mode; + u32 cdr_mode; + s32 strobe_pos; + u32 eq_level; + u32 ser_i2c_alias; + int ret; + + ret = fwnode_property_read_u32(link_fwnode, "ti,cdr-mode", &cdr_mode); + if (ret == -EINVAL) { + cdr_mode = RXPORT_CDR_FPD3; + } else if (ret < 0) { + dev_err(dev, "rx%u: failed to read 'ti,cdr-mode': %d\n", nport, + ret); + return ret; + } + + if (cdr_mode > RXPORT_CDR_LAST) { + dev_err(dev, "rx%u: bad 'ti,cdr-mode' %u\n", nport, cdr_mode); + return -EINVAL; + } + + if (!priv->hw_data->is_fpdlink4 && cdr_mode == RXPORT_CDR_FPD4) { + dev_err(dev, "rx%u: FPD-Link 4 CDR not supported\n", nport); + return -EINVAL; + } + + rxport->cdr_mode = cdr_mode; + + ret = fwnode_property_read_u32(link_fwnode, "ti,rx-mode", &rx_mode); + if (ret < 0) { + dev_err(dev, "rx%u: failed to read 'ti,rx-mode': %d\n", nport, + ret); + return ret; + } + + if (rx_mode > RXPORT_MODE_LAST) { + dev_err(dev, "rx%u: bad 'ti,rx-mode' %u\n", nport, rx_mode); + return -EINVAL; + } + + rxport->rx_mode = rx_mode; + + /* EQ & Strobe related */ + + /* Defaults */ + rxport->eq.manual_eq = false; + rxport->eq.aeq.eq_level_min = UB960_MIN_EQ_LEVEL; + rxport->eq.aeq.eq_level_max = UB960_MAX_EQ_LEVEL; + + ret = fwnode_property_read_u32(link_fwnode, "ti,strobe-pos", + &strobe_pos); + if (ret) { + if (ret != -EINVAL) { + dev_err(dev, + "rx%u: failed to read 'ti,strobe-pos': %d\n", + nport, ret); + return ret; + } + } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS || + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) { + dev_err(dev, "rx%u: illegal 'strobe-pos' value: %d\n", nport, + strobe_pos); + } else { + /* NOTE: ignored unless global manual strobe pos is set */ + rxport->eq.strobe_pos = strobe_pos; + if (!priv->strobe.manual) + dev_warn(dev, + "rx%u: 'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n", + nport); + } + + ret = fwnode_property_read_u32(link_fwnode, "ti,eq-level", &eq_level); + if (ret) { + if (ret != -EINVAL) { + dev_err(dev, "rx%u: failed to read 'ti,eq-level': %d\n", + nport, ret); + return ret; + } + } else if (eq_level > UB960_MAX_EQ_LEVEL) { + dev_err(dev, "rx%u: illegal 'ti,eq-level' value: %d\n", nport, + eq_level); + } else { + rxport->eq.manual_eq = true; + rxport->eq.manual.eq_level = eq_level; + } + + ret = fwnode_property_read_u32(link_fwnode, "i2c-alias", + &ser_i2c_alias); + if (ret) { + dev_err(dev, "rx%u: failed to read 'i2c-alias': %d\n", nport, + ret); + return ret; + } + rxport->ser_alias = ser_i2c_alias; + + rxport->remote_fwnode = fwnode_get_named_child_node(link_fwnode, "serializer"); + if (!rxport->remote_fwnode) { + dev_err(dev, "rx%u: missing 'serializer' node\n", nport); + return -EINVAL; + } + + return 0; +} + +static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv, + struct fwnode_handle *ep_fwnode, + struct ub960_rxport *rxport) +{ + struct device *dev = &priv->client->dev; + unsigned int nport = rxport->nport; + int ret; + u32 v; + + rxport->source_ep_fwnode = fwnode_graph_get_remote_endpoint(ep_fwnode); + if (!rxport->source_ep_fwnode) { + dev_err(dev, "rx%u: no remote endpoint\n", nport); + return -ENODEV; + } + + /* We currently have properties only for RAW modes */ + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + break; + default: + return 0; + } + + ret = fwnode_property_read_u32(ep_fwnode, "hsync-active", &v); + if (ret) { + dev_err(dev, "rx%u: failed to parse 'hsync-active': %d\n", + nport, ret); + goto err_put_source_ep_fwnode; + } + + rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_LV_POL_LOW : 0; + + ret = fwnode_property_read_u32(ep_fwnode, "vsync-active", &v); + if (ret) { + dev_err(dev, "rx%u: failed to parse 'vsync-active': %d\n", + nport, ret); + goto err_put_source_ep_fwnode; + } + + rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_FV_POL_LOW : 0; + + return 0; + +err_put_source_ep_fwnode: + fwnode_handle_put(rxport->source_ep_fwnode); + return ret; +} + +static int ub960_parse_dt_rxport(struct ub960_data *priv, unsigned int nport, + struct fwnode_handle *link_fwnode, + struct fwnode_handle *ep_fwnode) +{ + const char *vpoc_names[UB960_MAX_RX_NPORTS] = { "vpoc0", "vpoc1", + "vpoc2", "vpoc3" }; + struct device *dev = &priv->client->dev; + struct ub960_rxport *rxport; + int ret; + + rxport = kzalloc(sizeof(*rxport), GFP_KERNEL); + if (!rxport) + return -ENOMEM; + + priv->rxports[nport] = rxport; + + rxport->nport = nport; + rxport->priv = priv; + + ret = ub960_parse_dt_rxport_link_properties(priv, link_fwnode, rxport); + if (ret) + goto err_free_rxport; + + rxport->vpoc = devm_regulator_get_optional(dev, vpoc_names[nport]); + if (IS_ERR(rxport->vpoc)) { + ret = PTR_ERR(rxport->vpoc); + if (ret == -ENODEV) { + rxport->vpoc = NULL; + } else { + dev_err(dev, "rx%u: failed to get VPOC supply: %d\n", + nport, ret); + goto err_put_remote_fwnode; + } + } + + ret = ub960_parse_dt_rxport_ep_properties(priv, ep_fwnode, rxport); + if (ret) + goto err_put_remote_fwnode; + + return 0; + +err_put_remote_fwnode: + fwnode_handle_put(rxport->remote_fwnode); +err_free_rxport: + priv->rxports[nport] = NULL; + kfree(rxport); + return ret; +} + +static struct fwnode_handle * +ub960_fwnode_get_link_by_regs(struct fwnode_handle *links_fwnode, + unsigned int nport) +{ + struct fwnode_handle *link_fwnode; + int ret; + + fwnode_for_each_child_node(links_fwnode, link_fwnode) { + u32 link_num; + + if (!str_has_prefix(fwnode_get_name(link_fwnode), "link@")) + continue; + + ret = fwnode_property_read_u32(link_fwnode, "reg", &link_num); + if (ret) { + fwnode_handle_put(link_fwnode); + return NULL; + } + + if (nport == link_num) { + fwnode_handle_put(link_fwnode); + return link_fwnode; + } + } + + return NULL; +} + +static int ub960_parse_dt_rxports(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *links_fwnode; + unsigned int nport; + int ret; + + links_fwnode = fwnode_get_named_child_node(dev_fwnode(dev), "links"); + if (!links_fwnode) { + dev_err(dev, "'links' node missing\n"); + return -ENODEV; + } + + /* Defaults, recommended by TI */ + priv->strobe.min = 2; + priv->strobe.max = 3; + + priv->strobe.manual = fwnode_property_read_bool(links_fwnode, "ti,manual-strobe"); + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct fwnode_handle *link_fwnode; + struct fwnode_handle *ep_fwnode; + + link_fwnode = ub960_fwnode_get_link_by_regs(links_fwnode, nport); + if (!link_fwnode) + continue; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + nport, 0, 0); + if (!ep_fwnode) { + fwnode_handle_put(link_fwnode); + continue; + } + + ret = ub960_parse_dt_rxport(priv, nport, link_fwnode, + ep_fwnode); + + fwnode_handle_put(link_fwnode); + fwnode_handle_put(ep_fwnode); + + if (ret) { + dev_err(dev, "rx%u: failed to parse RX port\n", nport); + goto err_put_links; + } + } + + fwnode_handle_put(links_fwnode); + + return 0; + +err_put_links: + fwnode_handle_put(links_fwnode); + + return ret; +} + +static int ub960_parse_dt_txports(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + u32 nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_txports; ++nport) { + struct fwnode_handle *ep_fwnode; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + nport + priv->hw_data->num_rxports, 0, 0); + if (!ep_fwnode) + continue; + + ret = ub960_parse_dt_txport(priv, ep_fwnode, nport); + + fwnode_handle_put(ep_fwnode); + + if (ret) + break; + } + + return 0; +} + +static int ub960_parse_dt(struct ub960_data *priv) +{ + int ret; + + ret = ub960_parse_dt_base(priv); + if (ret) + return ret; + + ret = ub960_parse_dt_rxports(priv); + if (ret) + return ret; + + ret = ub960_parse_dt_txports(priv); + if (ret) + goto err_free_rxports; + + return 0; + +err_free_rxports: + ub960_rxport_free_ports(priv); + + return ret; +} + +static int ub960_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct ub960_data *priv = sd_to_ub960(notifier->sd); + struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport; + struct device *dev = &priv->client->dev; + u8 nport = rxport->nport; + unsigned int i; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, + rxport->source_ep_fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + rxport->source_sd = subdev; + rxport->source_sd_pad = ret; + + ret = media_create_pad_link(&rxport->source_sd->entity, + rxport->source_sd_pad, &priv->sd.entity, + nport, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:%u\n", + rxport->source_sd->name, rxport->source_sd_pad, + priv->sd.name, nport); + return ret; + } + + dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name, + rxport->source_sd_pad, nport); + + for (i = 0; i < priv->hw_data->num_rxports; ++i) { + if (priv->rxports[i] && !priv->rxports[i]->source_sd) { + dev_dbg(dev, "Waiting for more subdevs to be bound\n"); + return 0; + } + } + + dev_dbg(dev, "All subdevs bound\n"); + + return 0; +} + +static void ub960_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport; + + rxport->source_sd = NULL; +} + +static const struct v4l2_async_notifier_operations ub960_notify_ops = { + .bound = ub960_notify_bound, + .unbind = ub960_notify_unbind, +}; + +static int ub960_v4l2_notifier_register(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int i; + int ret; + + v4l2_async_nf_init(&priv->notifier); + + for (i = 0; i < priv->hw_data->num_rxports; ++i) { + struct ub960_rxport *rxport = priv->rxports[i]; + struct ub960_asd *asd; + + if (!rxport) + continue; + + asd = v4l2_async_nf_add_fwnode(&priv->notifier, + rxport->source_ep_fwnode, + struct ub960_asd); + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev for source %u: %pe", + i, asd); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + asd->rxport = rxport; + } + + priv->notifier.ops = &ub960_notify_ops; + + ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub960_v4l2_notifier_unregister(struct ub960_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub960_create_subdev(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int i; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub960_subdev_ops); + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub960_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ub960_tpg_qmenu) - 1, 0, 0, + ub960_tpg_qmenu); + + v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(priv->tx_link_freq) - 1, 0, + priv->tx_link_freq); + + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub960_entity_ops; + + for (i = 0; i < priv->hw_data->num_rxports + priv->hw_data->num_txports; i++) { + priv->pads[i].flags = ub960_pad_is_sink(priv, i) ? + MEDIA_PAD_FL_SINK : + MEDIA_PAD_FL_SOURCE; + } + + ret = media_entity_pads_init(&priv->sd.entity, + priv->hw_data->num_rxports + + priv->hw_data->num_txports, + priv->pads); + if (ret) + goto err_free_ctrl; + + priv->sd.state_lock = priv->sd.ctrl_handler->lock; + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub960_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub960_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); +err_free_ctrl: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +} + +static void ub960_destroy_subdev(struct ub960_data *priv) +{ + ub960_v4l2_notifier_unregister(priv); + v4l2_async_unregister_subdev(&priv->sd); + + v4l2_subdev_cleanup(&priv->sd); + + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static const struct regmap_config ub960_regmap_config = { + .name = "ds90ub960", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + + /* + * We do locking in the driver to cover the TX/RX port selection and the + * indirect register access. + */ + .disable_locking = true, +}; + +static void ub960_reset(struct ub960_data *priv, bool reset_regs) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + u8 bit; + + bit = reset_regs ? UB960_SR_RESET_DIGITAL_RESET1 : + UB960_SR_RESET_DIGITAL_RESET0; + + ub960_write(priv, UB960_SR_RESET, bit); + + mutex_lock(&priv->reg_lock); + + ret = regmap_read_poll_timeout(priv->regmap, UB960_SR_RESET, v, + (v & bit) == 0, 2000, 100000); + + mutex_unlock(&priv->reg_lock); + + if (ret) + dev_err(dev, "reset failed: %d\n", ret); +} + +static int ub960_get_hw_resources(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + + priv->regmap = devm_regmap_init_i2c(priv->client, &ub960_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->vddio = devm_regulator_get(dev, "vddio"); + if (IS_ERR(priv->vddio)) + return dev_err_probe(dev, PTR_ERR(priv->vddio), + "cannot get VDDIO regulator\n"); + + /* get power-down pin from DT */ + priv->pd_gpio = + devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(priv->pd_gpio)) + return dev_err_probe(dev, PTR_ERR(priv->pd_gpio), + "Cannot get powerdown GPIO\n"); + + priv->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) + return dev_err_probe(dev, PTR_ERR(priv->refclk), + "Cannot get REFCLK\n"); + + return 0; +} + +static int ub960_enable_core_hw(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + u8 rev_mask; + int ret; + u8 dev_sts; + u8 refclk_freq; + + ret = regulator_enable(priv->vddio); + if (ret) + return dev_err_probe(dev, ret, + "failed to enable VDDIO regulator\n"); + + ret = clk_prepare_enable(priv->refclk); + if (ret) { + dev_err_probe(dev, ret, "Failed to enable refclk\n"); + goto err_disable_vddio; + } + + if (priv->pd_gpio) { + gpiod_set_value_cansleep(priv->pd_gpio, 1); + /* wait min 2 ms for reset to complete */ + usleep_range(2000, 5000); + gpiod_set_value_cansleep(priv->pd_gpio, 0); + /* wait min 2 ms for power up to finish */ + usleep_range(2000, 5000); + } + + ub960_reset(priv, true); + + /* Runtime check register accessibility */ + ret = ub960_read(priv, UB960_SR_REV_MASK, &rev_mask); + if (ret) { + dev_err_probe(dev, ret, "Cannot read first register, abort\n"); + goto err_pd_gpio; + } + + dev_dbg(dev, "Found %s (rev/mask %#04x)\n", priv->hw_data->model, + rev_mask); + + ret = ub960_read(priv, UB960_SR_DEVICE_STS, &dev_sts); + if (ret) + goto err_pd_gpio; + + ret = ub960_read(priv, UB960_XR_REFCLK_FREQ, &refclk_freq); + if (ret) + goto err_pd_gpio; + + dev_dbg(dev, "refclk valid %u freq %u MHz (clk fw freq %lu MHz)\n", + !!(dev_sts & BIT(4)), refclk_freq, + clk_get_rate(priv->refclk) / 1000000); + + /* Disable all RX ports by default */ + ub960_write(priv, UB960_SR_RX_PORT_CTL, 0); + + return 0; + +err_pd_gpio: + 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) +{ + gpiod_set_value_cansleep(priv->pd_gpio, 1); + clk_disable_unprepare(priv->refclk); + regulator_disable(priv->vddio); +} + +static int ub960_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub960_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->hw_data = device_get_match_data(dev); + + mutex_init(&priv->reg_lock); + mutex_init(&priv->atr_alias_table.lock); + + INIT_DELAYED_WORK(&priv->poll_work, ub960_handler_work); + + /* + * Initialize these to invalid values so that the first reg writes will + * configure the target. + */ + priv->current_indirect_target = 0xff; + priv->current_read_rxport = 0xff; + priv->current_write_rxport_mask = 0xff; + priv->current_read_csiport = 0xff; + priv->current_write_csiport_mask = 0xff; + + ret = ub960_get_hw_resources(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub960_enable_core_hw(priv); + if (ret) + goto err_mutex_destroy; + + /* release GPIO lock */ + if (priv->hw_data->is_ub9702) + ub960_update_bits(priv, UB960_SR_RESET, + UB960_SR_RESET_GPIO_LOCK_RELEASE, + UB960_SR_RESET_GPIO_LOCK_RELEASE); + + ret = ub960_parse_dt(priv); + if (ret) + goto err_disable_core_hw; + + ret = ub960_init_tx_ports(priv); + if (ret) + goto err_free_ports; + + ret = ub960_rxport_enable_vpocs(priv); + if (ret) + goto err_free_ports; + + ret = ub960_init_rx_ports(priv); + if (ret) + goto err_disable_vpocs; + + ub960_reset(priv, false); + + ub960_rxport_wait_locks(priv, GENMASK(3, 0), NULL); + + /* + * Clear any errors caused by switching the RX port settings while + * probing. + */ + ub960_clear_rx_errors(priv); + + ret = ub960_init_atr(priv); + if (ret) + goto err_disable_vpocs; + + ret = ub960_rxport_add_serializers(priv); + if (ret) + goto err_uninit_atr; + + ret = ub960_create_subdev(priv); + if (ret) + goto err_free_sers; + + if (client->irq) + dev_warn(dev, "irq support not implemented, using polling\n"); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); + + return 0; + +err_free_sers: + ub960_rxport_remove_serializers(priv); +err_uninit_atr: + ub960_uninit_atr(priv); +err_disable_vpocs: + ub960_rxport_disable_vpocs(priv); +err_free_ports: + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); +err_disable_core_hw: + ub960_disable_core_hw(priv); +err_mutex_destroy: + mutex_destroy(&priv->atr_alias_table.lock); + mutex_destroy(&priv->reg_lock); + return ret; +} + +static void ub960_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub960_data *priv = sd_to_ub960(sd); + + cancel_delayed_work_sync(&priv->poll_work); + + ub960_destroy_subdev(priv); + ub960_rxport_remove_serializers(priv); + ub960_uninit_atr(priv); + ub960_rxport_disable_vpocs(priv); + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); + ub960_disable_core_hw(priv); + mutex_destroy(&priv->atr_alias_table.lock); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub960_hw_data ds90ub960_hw = { + .model = "ub960", + .num_rxports = 4, + .num_txports = 2, +}; + +static const struct ub960_hw_data ds90ub9702_hw = { + .model = "ub9702", + .num_rxports = 4, + .num_txports = 2, + .is_ub9702 = true, + .is_fpdlink4 = true, +}; + +static const struct i2c_device_id ub960_id[] = { + { "ds90ub960-q1", (kernel_ulong_t)&ds90ub960_hw }, + { "ds90ub9702-q1", (kernel_ulong_t)&ds90ub9702_hw }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ub960_id); + +static const struct of_device_id ub960_dt_ids[] = { + { .compatible = "ti,ds90ub960-q1", .data = &ds90ub960_hw }, + { .compatible = "ti,ds90ub9702-q1", .data = &ds90ub9702_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, ub960_dt_ids); + +static struct i2c_driver ds90ub960_driver = { + .probe_new = ub960_probe, + .remove = ub960_remove, + .id_table = ub960_id, + .driver = { + .name = "ds90ub960", + .of_match_table = ub960_dt_ids, + }, +}; +module_i2c_driver(ds90ub960_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV Deserializers Driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS(I2C_ATR); diff --git a/include/media/i2c/ds90ub9xx.h b/include/media/i2c/ds90ub9xx.h new file mode 100644 index 000000000000..42d47d732c03 --- /dev/null +++ b/include/media/i2c/ds90ub9xx.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __MEDIA_I2C_DS90UB9XX_H__ +#define __MEDIA_I2C_DS90UB9XX_H__ + +#include + +struct i2c_atr; + +struct ds90ub9xx_platform_data { + u32 port; + struct i2c_atr *atr; + unsigned long bc_rate; +}; + +#endif /* __MEDIA_I2C_DS90UB9XX_H__ */ From patchwork Fri Jan 20 15:34:16 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46428 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp269955wrn; Fri, 20 Jan 2023 07:43:42 -0800 (PST) X-Google-Smtp-Source: AMrXdXv29oTzs0K4AN0LIc6QVoA6/J7JrS6gUUK9yvpypED+e9p4/WN6pWG6Wo9WSQbfiP3Dj7+Z X-Received: by 2002:aa7:de8f:0:b0:498:e0be:318b with SMTP id j15-20020aa7de8f000000b00498e0be318bmr15462673edv.38.1674229422762; Fri, 20 Jan 2023 07:43:42 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229422; cv=none; d=google.com; s=arc-20160816; b=RvEh6ilXe0X3CGoyt+eD7iuZlxG5uECy1GmZI00gStAKfhPIzt6QJvtpC5qcxEeg0o b27bO7XVh+WnaSYz1cXH/NQycbeytHQXXtBDhRxbPqPjZTI1+mdXXmXKmI0LfPetDueL 9rhpUr0bYf2rUyZbuqQpWR0pbAyCfP7mo2MMGbwoXPULtuY1yEfyNeoMSObZmowskG5p qURM1GpCWb1bAiWxwlobjMUSgiEgt3Je2MszC8Ar8imhpwKLTtYtrXTnsEVQ6/bB53aS 5x1AnKAyjg3n4zEKN7Hp2Qju015nfIwv4MDwH+LDQK1OPU8u5NF5bzM2YEoHIjoyO+F2 Uzyw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=MSGK0c62zp1XfCZvelpsPID+d7bT84cUpaJis/AKCo8=; b=gHyDMtnB9uwYPw1Zi7ZsGlix/eHaZtf/C/J6vlJ58PaCQXXMjelwVrSU0NX1YZaCmP C1/KJ00ZTjalYAgvydYMDOri8LJ0Hst46h3gvAmiXtRDqrC36rZKqNM73hPuRyOrkiX4 /KgC66eS/BBz09yVMu8BqogY8VO4GzPeDjmTuy24lHCRiuLuFms450DxzOoE4dRelr/E YOik40hLRnSB2o8aHwiAde3583+BM71Ojp7lZnA8FVl9w5x2O343eCepqH17ma6geSBz ztof3PdoZqidvRt/IBdjWyInMyNQvg0v10KCFTAull5Re+vJt7W7XtMeysoz1v4jrOXd yQtA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=li7hQOvB; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id ec18-20020a0564020d5200b0046b0205ab58si38010789edb.580.2023.01.20.07.43.18; Fri, 20 Jan 2023 07:43:42 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=li7hQOvB; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231232AbjATPfH (ORCPT + 99 others); Fri, 20 Jan 2023 10:35:07 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37452 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231551AbjATPez (ORCPT ); Fri, 20 Jan 2023 10:34:55 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 27EDAC9263; Fri, 20 Jan 2023 07:34:48 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 8CA73514; Fri, 20 Jan 2023 16:34:42 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228883; bh=HINmh/U5m0Ad8GKTaSR+fHfMWAhREHqOis0XZaBtZuo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=li7hQOvBX3f6R17iIoJ6WDkQ7rAqWLOdjQXa/lq0tLXJn6KyxilMMfMNglIquSpNN xIqtfGFoqSCFrimCZXYqwUWDjjAnwcjxHf0jmDwdTleORwD404EZuDNXZyiX7Bt7dc P1Z3K6jRUGMwONtEVactuD0FuZDrxCt26LgskbF0= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Tomi Valkeinen , Laurent Pinchart Subject: [PATCH v8 6/7] media: i2c: add DS90UB913 driver Date: Fri, 20 Jan 2023 17:34:16 +0200 Message-Id: <20230120153417.1156207-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556791555578145?= X-GMAIL-MSGID: =?utf-8?q?1755556791555578145?= Add driver for TI DS90UB913 FPD-Link III Serializer. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ds90ub913.c | 851 ++++++++++++++++++++++++++++++++++ 3 files changed, 865 insertions(+) create mode 100644 drivers/media/i2c/ds90ub913.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index dc1c7c80dc1c..e0a1c2a5f3bf 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1601,6 +1601,19 @@ endmenu menu "Video serializers and deserializers" +config VIDEO_DS90UB913 + tristate "TI DS90UB913 FPD-Link III Serializer" + depends on OF && I2C && VIDEO_DEV + select I2C_ATR + select MEDIA_CONTROLLER + select OF_GPIO + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Texas Instruments DS90UB913 + FPD-Link III Serializer. + config VIDEO_DS90UB960 tristate "TI FPD-Link III/IV Deserializers" depends on OF && I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 4cd680f3b953..c4875ec8c3b9 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -142,4 +142,5 @@ obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_WM8739) += wm8739.o obj-$(CONFIG_VIDEO_WM8775) += wm8775.o +obj-$(CONFIG_VIDEO_DS90UB913) += ds90ub913.o obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c new file mode 100644 index 000000000000..60a07b5bace3 --- /dev/null +++ b/drivers/media/i2c/ds90ub913.c @@ -0,0 +1,851 @@ +// 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) 2023 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 oscillator + * mode. Thus we only support 2 gpios for now. + */ +#define UB913_NUM_GPIOS 2 + +#define UB913_REG_RESET_CTL 0x01 +#define UB913_REG_RESET_CTL_DIGITAL_RESET_1 BIT(1) +#define UB913_REG_RESET_CTL_DIGITAL_RESET_0 BIT(0) + +#define UB913_REG_GENERAL_CFG 0x03 +#define UB913_REG_GENERAL_CFG_CRC_ERR_RESET BIT(5) +#define UB913_REG_GENERAL_CFG_PCLK_RISING BIT(0) + +#define UB913_REG_MODE_SEL 0x05 +#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5) +#define UB913_REG_MODE_SEL_MODE_UP_TO_DATE BIT(4) +#define UB913_REG_MODE_SEL_MODE_MASK GENMASK(3, 0) + +#define UB913_REG_CRC_ERRORS_LSB 0x0a +#define UB913_REG_CRC_ERRORS_MSB 0x0b + +#define UB913_REG_GENERAL_STATUS 0x0c + +#define UB913_REG_GPIO_CFG(n) (0x0d + (n)) +#define UB913_REG_GPIO_CFG_ENABLE(n) BIT(0 + (n) * 4) +#define UB913_REG_GPIO_CFG_DIR_INPUT(n) BIT(1 + (n) * 4) +#define UB913_REG_GPIO_CFG_REMOTE_EN(n) BIT(2 + (n) * 4) +#define UB913_REG_GPIO_CFG_OUT_VAL(n) BIT(3 + (n) * 4) +#define UB913_REG_GPIO_CFG_MASK(n) (0xf << ((n) * 4)) + +#define UB913_REG_SCL_HIGH_TIME 0x11 +#define UB913_REG_SCL_LOW_TIME 0x12 + +#define UB913_REG_PLL_OVR 0x35 + +struct ub913_data { + struct i2c_client *client; + struct regmap *regmap; + struct clk *clkin; + + struct gpio_chip gpio_chip; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + u16 source_sd_pad; + + u64 enabled_source_streams; + + struct clk_hw *clkout_clk_hw; + + struct ds90ub9xx_platform_data *plat_data; + + u32 pclk_polarity; +}; + +static inline struct ub913_data *sd_to_ub913(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub913_data, sd); +} + +static int ub913_read(const struct ub913_data *priv, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + ret = regmap_read(priv->regmap, reg, &v); + if (ret < 0) { + dev_err(&priv->client->dev, + "Cannot read register 0x%02x: %d!\n", reg, ret); + return ret; + } + + *val = v; + return 0; +} + +static int ub913_write(const struct ub913_data *priv, u8 reg, u8 val) +{ + int ret; + + ret = regmap_write(priv->regmap, reg, val); + if (ret < 0) + dev_err(&priv->client->dev, + "Cannot write register 0x%02x: %d!\n", reg, ret); + + return ret; +} + +/* + * GPIO chip + */ +static int ub913_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + return GPIO_LINE_DIRECTION_OUT; +} + +static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ub913_data *priv = gpiochip_get_data(gc); + unsigned int reg_idx = offset / 2; + unsigned int field_idx = offset % 2; + + return regmap_update_bits(priv->regmap, UB913_REG_GPIO_CFG(reg_idx), + UB913_REG_GPIO_CFG_MASK(field_idx), + UB913_REG_GPIO_CFG_ENABLE(field_idx) | + (value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : + 0)); +} + +static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + ub913_gpio_direction_out(gc, offset, value); +} + +static int ub913_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub913_gpiochip_probe(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret; + + /* Initialize GPIOs 0 and 1 to local control, tri-state */ + ub913_write(priv, UB913_REG_GPIO_CFG(0), 0); + + gc->label = dev_name(dev); + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->can_sleep = true; + gc->ngpio = UB913_NUM_GPIOS; + gc->get_direction = ub913_gpio_get_direction; + gc->direction_output = ub913_gpio_direction_out; + gc->set = ub913_gpio_set; + gc->of_xlate = ub913_gpio_of_xlate; + gc->of_gpio_n_cells = 2; + + ret = gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub913_gpiochip_remove(struct ub913_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +static const struct regmap_config ub913_regmap_config = { + .name = "ds90ub913", + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_DEFAULT, + .val_format_endian = REGMAP_ENDIAN_DEFAULT, +}; + +/* + * V4L2 + */ + +static int ub913_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub913_data *priv = sd_to_ub913(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE, + UB913_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |= streams_mask; + + return 0; +} + +static int ub913_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub913_data *priv = sd_to_ub913(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB913_PAD_SOURCE, + UB913_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + return 0; +} + +static int _ub913_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + /* + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. + */ + + if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -EINVAL; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int ub913_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub913_data *priv = sd_to_ub913(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) + return -EBUSY; + + return _ub913_set_routing(sd, state, routing); +} + +static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub913_data *priv = sd_to_ub913(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret; + + if (pad != UB913_PAD_SOURCE) + return -EINVAL; + + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, + priv->source_sd_pad, &source_fd); + if (ret) + return ret; + + memset(fd, 0, sizeof(*fd)); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + routing = &state->routing; + + for_each_active_route(routing, route) { + unsigned int i; + + if (route->source_pad != pad) + continue; + + for (i = 0; i < source_fd.num_entries; ++i) { + if (source_fd.entry[i].stream == route->sink_stream) + break; + } + + if (i == source_fd.num_entries) { + dev_err(&priv->client->dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out_unlock; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_fd.entry[i].flags; + fd->entry[fd->num_entries].length = source_fd.entry[i].length; + fd->entry[fd->num_entries].pixelcode = + source_fd.entry[i].pixelcode; + + fd->num_entries++; + } + +out_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub913_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == UB913_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub913_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = UB913_PAD_SINK, + .sink_stream = 0, + .source_pad = UB913_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub913_set_routing(sd, state, &routing); +} + +static int ub913_log_status(struct v4l2_subdev *sd) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct device *dev = &priv->client->dev; + u8 v, v1, v2; + + ub913_read(priv, UB913_REG_MODE_SEL, &v); + dev_info(dev, "MODE_SEL %#02x\n", v); + + ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1); + ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2); + dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8)); + + /* clear CRC errors */ + ub913_read(priv, UB913_REG_GENERAL_CFG, &v); + ub913_write(priv, UB913_REG_GENERAL_CFG, + v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET); + ub913_write(priv, UB913_REG_GENERAL_CFG, v); + + ub913_read(priv, UB913_REG_GENERAL_STATUS, &v); + dev_info(dev, "GENERAL_STATUS %#02x\n", v); + + ub913_read(priv, UB913_REG_PLL_OVR, &v); + dev_info(dev, "PLL_OVR %#02x\n", v); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub913_subdev_core_ops = { + .log_status = ub913_log_status, +}; + +static const struct v4l2_subdev_pad_ops ub913_pad_ops = { + .enable_streams = ub913_enable_streams, + .disable_streams = ub913_disable_streams, + .set_routing = ub913_set_routing, + .get_frame_desc = ub913_get_frame_desc, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub913_set_fmt, + .init_cfg = ub913_init_cfg, +}; + +static const struct v4l2_subdev_ops ub913_subdev_ops = { + .core = &ub913_subdev_core_ops, + .pad = &ub913_pad_ops, +}; + +static const struct media_entity_operations ub913_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int ub913_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_subdev *asd) +{ + struct ub913_data *priv = sd_to_ub913(notifier->sd); + struct device *dev = &priv->client->dev; + int ret; + + ret = media_entity_get_fwnode_pad(&source_subdev->entity, + source_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", + source_subdev->name); + return ret; + } + + priv->source_sd = source_subdev; + priv->source_sd_pad = ret; + + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad, + &priv->sd.entity, UB913_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, priv->source_sd_pad, + priv->sd.name); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations ub913_notify_ops = { + .bound = ub913_notify_bound, +}; + +static int ub913_v4l2_notifier_register(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB913_PAD_SINK, 0, 0); + if (!ep_fwnode) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_nf_init(&priv->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode, + struct v4l2_async_subdev); + + fwnode_handle_put(ep_fwnode); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &ub913_notify_ops; + + ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub913_v4l2_nf_unregister(struct ub913_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub913_register_clkout(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + const char *name; + int ret; + + name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev)); + if (!name) + return -ENOMEM; + + priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name, + __clk_get_name(priv->clkin), 0, 1, 2); + + kfree(name); + + if (IS_ERR(priv->clkout_clk_hw)) + return dev_err_probe(dev, PTR_ERR(priv->clkout_clk_hw), + "Cannot register clkout hw\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + priv->clkout_clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; +} + +static int ub913_i2c_master_init(struct ub913_data *priv) +{ + /* i2c fast mode */ + u32 scl_high = 600 + 300; /* high period + rise time, ns */ + u32 scl_low = 1300 + 300; /* low period + fall time, ns */ + unsigned long ref; + int ret; + + ref = clk_get_rate(priv->clkin) / 2; + + scl_high = div64_u64((u64)scl_high * ref, 1000000000); + scl_low = div64_u64((u64)scl_low * ref, 1000000000); + + ret = ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high); + if (ret) + return ret; + + ret = ub913_write(priv, UB913_REG_SCL_LOW_TIME, scl_low); + if (ret) + return ret; + + return 0; +} + +static int ub913_add_i2c_adapter(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle = device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port, + i2c_handle); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + return 0; +} + +static int ub913_parse_dt(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB913_PAD_SINK, 0, 0); + if (!ep_fwnode) { + dev_err_probe(dev, -ENOENT, "No sink endpoint\n"); + return -ENOENT; + } + + ret = fwnode_property_read_u32(ep_fwnode, "pclk-sample", + &priv->pclk_polarity); + + fwnode_handle_put(ep_fwnode); + + if (ret) { + dev_err_probe(dev, ret, "failed to parse 'pclk-sample'\n"); + return ret; + } + + return 0; +} + +static int ub913_hw_init(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + bool mode_override; + u8 mode; + int ret; + u8 v; + + ret = ub913_read(priv, UB913_REG_MODE_SEL, &v); + if (ret) + return ret; + + if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE)) + return dev_err_probe(dev, -ENODEV, + "Mode value not stabilized\n"); + + mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE; + mode = v & UB913_REG_MODE_SEL_MODE_MASK; + + dev_dbg(dev, "mode from %s: %#x\n", + mode_override ? "reg" : "deserializer", mode); + + ret = ub913_i2c_master_init(priv); + if (ret) + return dev_err_probe(dev, ret, "i2c master init failed\n"); + + ub913_read(priv, UB913_REG_GENERAL_CFG, &v); + v &= ~UB913_REG_GENERAL_CFG_PCLK_RISING; + v |= priv->pclk_polarity ? UB913_REG_GENERAL_CFG_PCLK_RISING : 0; + ub913_write(priv, UB913_REG_GENERAL_CFG, v); + + return 0; +} + +static int ub913_subdev_init(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops); + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub913_entity_ops; + + priv->pads[0].flags = MEDIA_PAD_FL_SINK; + priv->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) + return dev_err_probe(dev, ret, "Failed to init pads\n"); + + priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB913_PAD_SOURCE, 0, + 0); + + if (!priv->sd.fwnode) { + ret = -ENODEV; + dev_err_probe(dev, ret, "Missing TX endpoint\n"); + goto err_entity_cleanup; + } + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_fwnode_put; + + ret = ub913_v4l2_notifier_register(priv); + if (ret) { + dev_err_probe(dev, ret, + "v4l2 subdev notifier register failed\n"); + goto err_subdev_cleanup; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n"); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub913_v4l2_nf_unregister(priv); +err_subdev_cleanup: + v4l2_subdev_cleanup(&priv->sd); +err_fwnode_put: + fwnode_handle_put(priv->sd.fwnode); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); + + return ret; +} + +static void ub913_subdev_uninit(struct ub913_data *priv) +{ + v4l2_async_unregister_subdev(&priv->sd); + ub913_v4l2_nf_unregister(priv); + v4l2_subdev_cleanup(&priv->sd); + fwnode_handle_put(priv->sd.fwnode); + media_entity_cleanup(&priv->sd.entity); +} + +static int ub913_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub913_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->plat_data = dev_get_platdata(&client->dev); + if (!priv->plat_data) + return dev_err_probe(dev, -ENODEV, "Platform data missing\n"); + + priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config); + if (IS_ERR(priv->regmap)) + return dev_err_probe(dev, PTR_ERR(priv->regmap), + "Failed to init regmap\n"); + + /* + * ub913 can also work without ext clock, but that is not supported by + * the driver yet. + */ + priv->clkin = devm_clk_get(dev, "clkin"); + if (IS_ERR(priv->clkin)) + return dev_err_probe(dev, PTR_ERR(priv->clkin), + "Cannot get CLKIN\n"); + + ret = ub913_parse_dt(priv); + if (ret) + return ret; + + ret = ub913_hw_init(priv); + if (ret) + return ret; + + ret = ub913_gpiochip_probe(priv); + if (ret) + return dev_err_probe(dev, ret, "Failed to init gpiochip\n"); + + ret = ub913_register_clkout(priv); + if (ret) { + dev_err_probe(dev, ret, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + ret = ub913_subdev_init(priv); + if (ret) + goto err_gpiochip_remove; + + ret = ub913_add_i2c_adapter(priv); + if (ret) { + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n"); + goto err_subdev_uninit; + } + + return 0; + +err_subdev_uninit: + ub913_subdev_uninit(priv); +err_gpiochip_remove: + ub913_gpiochip_remove(priv); + + return ret; +} + +static void ub913_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub913_data *priv = sd_to_ub913(sd); + + i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port); + + ub913_subdev_uninit(priv); + + ub913_gpiochip_remove(priv); +} + +static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} }; +MODULE_DEVICE_TABLE(i2c, ub913_id); + +static const struct of_device_id ub913_dt_ids[] = { + { .compatible = "ti,ds90ub913a-q1" }, + {} +}; +MODULE_DEVICE_TABLE(of, ub913_dt_ids); + +static struct i2c_driver ds90ub913_driver = { + .probe_new = ub913_probe, + .remove = ub913_remove, + .id_table = ub913_id, + .driver = { + .name = "ds90ub913a", + .of_match_table = ub913_dt_ids, + }, +}; +module_i2c_driver(ds90ub913_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB913 FPD-Link III Serializer Driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS(I2C_ATR); From patchwork Fri Jan 20 15:34:17 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 46429 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp270123wrn; Fri, 20 Jan 2023 07:44:04 -0800 (PST) X-Google-Smtp-Source: AMrXdXsSSaTHnWIvzw+VT7FOJD4WTqIp7gNipX6w9kUjZdgtW+mm7QQGrWmLNs2ffAkr9dptpKHr X-Received: by 2002:a17:906:4094:b0:854:956:1438 with SMTP id u20-20020a170906409400b0085409561438mr18005842ejj.25.1674229433417; Fri, 20 Jan 2023 07:43:53 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1674229433; cv=none; d=google.com; s=arc-20160816; b=sShf475/StTYqYS8YwQh+Tp/Iqgh36IKKqESzIl4nE+zplTHN54lbQSmIjTj6w1ZTw 4zdKy0+FG5P/u9zKBjUj7SV5kOkmpyBZ1rAgJCCW7v9IOhb31X2td37fo7jgZF+QbEHl bSM49torl4gQMBtTHwdzsUhU+HiEnKhjwsiduepwP52j51PIPiQPk8hdo8NP2234JTFo pdMwkWK0Kt270JBd3XHFtG1ee/lkUo6Ipggqg10YwzK+4pmGrBYXlxOO2B6vF3t7fdSh PQRdgYSxoGv7Ta1XqDwdB0qg+CPYVzVnemQnxR8f4hGTuM3MvfzZFPdQFcKnsGxJehZZ x0tg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :references:in-reply-to:message-id:date:subject:cc:to:from :dkim-signature; bh=AJ7WWM0ZmBmyC7rN6eJ9eHBabdPZVZxY6O5rUBasV8E=; b=zc5yuQP0MAJF3q1yaLFZZQnrDII3AgAR468K62nVorZm+YArdROiYknPn+sqpVPeS0 CMDYc7FUI+KLhHtwT5qYeaubwb/cqaJiR0QIf/ORq+0a9ma11mWlWePykPbkVLN2Xa/s ASN7P13aOgkFH/K9Y29aO4BkkM3BVUlbYffhHAaVpgAYuS9tLwc86nQRYP7DW/O7axUG CyAljVGHArkPW5S75u7MAKZGXvP0ebARYpi01N/A4e9r4pKvQ7aSvIVt/LfhcXRQo7bW vzzrUYimPhEWLKQzOsP/cpHMhAKvI/Kr+8+pfwqQzunl8x+N9OvsjN09yf7RFdZoeKLj /ezw== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=HWEZpxMT; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id xc13-20020a170907074d00b00877a58772fesi1906465ejb.90.2023.01.20.07.43.29; Fri, 20 Jan 2023 07:43:53 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=HWEZpxMT; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231584AbjATPf0 (ORCPT + 99 others); Fri, 20 Jan 2023 10:35:26 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38214 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231235AbjATPfP (ORCPT ); Fri, 20 Jan 2023 10:35:15 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 7E54ACC5EE; Fri, 20 Jan 2023 07:34:52 -0800 (PST) Received: from desky.lan (91-154-32-225.elisa-laajakaista.fi [91.154.32.225]) by perceval.ideasonboard.com (Postfix) with ESMTPSA id 23BA91356; Fri, 20 Jan 2023 16:34:44 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1674228885; bh=k98JpWarlihqaft19dq8MUcBXB0kGb+FX04Ej8fn4d8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HWEZpxMTVT8lDKYyFkYGNXXmSerLL+eOcGaXKgHDNLNLaSUIzB4Ho67yFGMlZIDev IW7RpsfgRcOnJmzK+WXKjDk4m5Uy2JWNQcG4hVFIEHtCbi7wzBVe5kqLf0ZGL0kD3t Us1JLuD+NCP1RfCFcQyoeH4lZSg0bU4p0RtMXA60= From: Tomi Valkeinen To: linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-i2c@vger.kernel.org, Rob Herring , Krzysztof Kozlowski , Wolfram Sang , Luca Ceresoli , Andy Shevchenko , Matti Vaittinen , Laurent Pinchart Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Satish Nagireddy , Tomi Valkeinen , Laurent Pinchart Subject: [PATCH v8 7/7] media: i2c: add DS90UB953 driver Date: Fri, 20 Jan 2023 17:34:17 +0200 Message-Id: <20230120153417.1156207-8-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> References: <20230120153417.1156207-1-tomi.valkeinen@ideasonboard.com> MIME-Version: 1.0 X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,SPF_HELO_PASS,SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1755556802743647860?= X-GMAIL-MSGID: =?utf-8?q?1755556802743647860?= Add driver for TI DS90UB953 FPD-Link III Serializer. Signed-off-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart --- drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ds90ub953.c | 1572 +++++++++++++++++++++++++++++++++ 3 files changed, 1586 insertions(+) create mode 100644 drivers/media/i2c/ds90ub953.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index e0a1c2a5f3bf..0590312ec751 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1614,6 +1614,19 @@ config VIDEO_DS90UB913 Device driver for the Texas Instruments DS90UB913 FPD-Link III Serializer. +config VIDEO_DS90UB953 + tristate "TI FPD-Link III/IV CSI-2 Serializers" + depends on OF && I2C && VIDEO_DEV + select I2C_ATR + select MEDIA_CONTROLLER + select OF_GPIO + select REGMAP_I2C + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Device driver for the Texas Instruments DS90UB953 + FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer. + config VIDEO_DS90UB960 tristate "TI FPD-Link III/IV Deserializers" depends on OF && I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index c4875ec8c3b9..efd5f717a5f7 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -143,4 +143,5 @@ obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_WM8739) += wm8739.o obj-$(CONFIG_VIDEO_WM8775) += wm8775.o obj-$(CONFIG_VIDEO_DS90UB913) += ds90ub913.o +obj-$(CONFIG_VIDEO_DS90UB953) += ds90ub953.o obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c new file mode 100644 index 000000000000..738652f2294b --- /dev/null +++ b/drivers/media/i2c/ds90ub953.c @@ -0,0 +1,1572 @@ +// 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) 2023 Tomi Valkeinen + */ + +#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_GENERAL_CFG_CONT_CLK BIT(6) +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT 4 +#define UB953_REG_GENERAL_CFG_CSI_LANE_SEL_MASK GENMASK(5, 4) +#define UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE BIT(1) +#define UB953_REG_GENERAL_CFG_I2C_STRAP_MODE BIT(0) + +#define UB953_REG_MODE_SEL 0x03 +#define UB953_REG_MODE_SEL_MODE_DONE BIT(3) +#define UB953_REG_MODE_SEL_MODE_OVERRIDE BIT(4) +#define UB953_REG_MODE_SEL_MODE_MASK GENMASK(2, 0) + +#define UB953_REG_CLKOUT_CTRL0 0x06 +#define UB953_REG_CLKOUT_CTRL1 0x07 + +#define UB953_REG_SCL_HIGH_TIME 0x0b +#define UB953_REG_SCL_LOW_TIME 0x0c + +#define UB953_REG_LOCAL_GPIO_DATA 0x0d +#define UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(n) BIT(4 + (n)) +#define UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(n) BIT(0 + (n)) + +#define UB953_REG_GPIO_INPUT_CTRL 0x0e +#define UB953_REG_GPIO_INPUT_CTRL_OUT_EN(n) BIT(4 + (n)) +#define UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(n) BIT(0 + (n)) + +#define UB953_REG_REV_MASK_ID 0x50 +#define UB953_REG_GENERAL_STATUS 0x52 + +#define UB953_REG_GPIO_PIN_STS 0x53 +#define UB953_REG_GPIO_PIN_STS_GPIO_STS(n) BIT(0 + (n)) + +#define UB953_REG_BIST_ERR_CNT 0x54 +#define UB953_REG_CRC_ERR_CNT1 0x55 +#define UB953_REG_CRC_ERR_CNT2 0x56 + +#define UB953_REG_CSI_ERR_CNT 0x5c +#define UB953_REG_CSI_ERR_STATUS 0x5d +#define UB953_REG_CSI_ERR_DLANE01 0x5e +#define UB953_REG_CSI_ERR_DLANE23 0x5f +#define UB953_REG_CSI_ERR_CLK_LANE 0x60 +#define UB953_REG_CSI_PKT_HDR_VC_ID 0x61 +#define UB953_REG_PKT_HDR_WC_LSB 0x62 +#define UB953_REG_PKT_HDR_WC_MSB 0x63 +#define UB953_REG_CSI_ECC 0x64 + +#define UB953_REG_IND_ACC_CTL 0xb0 +#define UB953_REG_IND_ACC_ADDR 0xb1 +#define UB953_REG_IND_ACC_DATA 0xb2 + +#define UB953_REG_FPD3_RX_ID(n) (0xf0 + (n)) + +/* Indirect register blocks */ +#define UB953_IND_TARGET_PAT_GEN 0x00 +#define UB953_IND_TARGET_FPD3_TX 0x01 +#define UB953_IND_TARGET_DIE_ID 0x02 + +#define UB953_IND_PGEN_CTL 0x01 +#define UB953_IND_PGEN_CTL_PGEN_ENABLE BIT(0) +#define UB953_IND_PGEN_CFG 0x02 +#define UB953_IND_PGEN_CSI_DI 0x03 +#define UB953_IND_PGEN_LINE_SIZE1 0x04 +#define UB953_IND_PGEN_LINE_SIZE0 0x05 +#define UB953_IND_PGEN_BAR_SIZE1 0x06 +#define UB953_IND_PGEN_BAR_SIZE0 0x07 +#define UB953_IND_PGEN_ACT_LPF1 0x08 +#define UB953_IND_PGEN_ACT_LPF0 0x09 +#define UB953_IND_PGEN_TOT_LPF1 0x0a +#define UB953_IND_PGEN_TOT_LPF0 0x0b +#define UB953_IND_PGEN_LINE_PD1 0x0c +#define UB953_IND_PGEN_LINE_PD0 0x0d +#define UB953_IND_PGEN_VBP 0x0e +#define UB953_IND_PGEN_VFP 0x0f +#define UB953_IND_PGEN_COLOR(n) (0x10 + (n)) /* n <= 15 */ + +/* Note: Only sync mode supported for now */ +enum ub953_mode { + /* FPD-Link III CSI-2 synchronous mode */ + UB953_MODE_SYNC, + /* FPD-Link III CSI-2 non-synchronous mode, external ref clock */ + UB953_MODE_NONSYNC_EXT, + /* FPD-Link III CSI-2 non-synchronous mode, internal ref clock */ + UB953_MODE_NONSYNC_INT, + /* FPD-Link III DVP mode */ + UB953_MODE_DVP, +}; + +struct ub953_hw_data { + const char *model; + bool is_ub971; +}; + +struct ub953_data { + const struct ub953_hw_data *hw_data; + + struct i2c_client *client; + struct regmap *regmap; + + u32 num_data_lanes; + + struct gpio_chip gpio_chip; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + u16 source_sd_pad; + + struct v4l2_ctrl_handler ctrl_handler; + + u64 enabled_source_streams; + + /* lock for register access */ + struct mutex reg_lock; + + u8 current_indirect_target; + + struct clk_hw clkout_clk_hw; + + enum ub953_mode mode; + + const struct ds90ub9xx_platform_data *plat_data; +}; + +static inline struct ub953_data *sd_to_ub953(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub953_data, sd); +} + +/* + * HW Access + */ + +static int ub953_read(struct ub953_data *priv, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) { + dev_err(&priv->client->dev, "Cannot read register 0x%02x: %d\n", + reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write(struct ub953_data *priv, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(&priv->client->dev, + "Cannot write register 0x%02x: %d\n", reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_select_ind_reg_block(struct ub953_data *priv, u8 block) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_indirect_target == block) + return 0; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_CTL, block << 2); + if (ret) { + dev_err(dev, "%s: cannot select indirect target %u (%d)\n", + __func__, block, ret); + return ret; + } + + priv->current_indirect_target = block; + + return 0; +} + +__maybe_unused static int ub953_read_ind(struct ub953_data *priv, u8 block, + u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = ub953_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when reading %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when reading %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + *val = v; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write_ind(struct ub953_data *priv, u8 block, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = ub953_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_ADDR failed when writing %u:%x02x: %d\n", + block, reg, ret); + goto out_unlock; + } + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val); + if (ret) { + dev_err(&priv->client->dev, + "Write to IND_ACC_DATA failed when writing %u:%x02x\n: %d\n", + block, reg, ret); + } + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write_ind16(struct ub953_data *priv, u8 block, u8 reg, u16 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = ub953_select_ind_reg_block(priv, block); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val >> 8); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg + 1); + if (ret) + goto out_unlock; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val & 0xff); + if (ret) + goto out_unlock; + +out_unlock: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +/* + * GPIO chip + */ +static int ub953_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &v); + if (ret) + return ret; + + if (v & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset)) + return GPIO_LINE_DIRECTION_IN; + else + return GPIO_LINE_DIRECTION_OUT; +} + +static int ub953_gpio_direction_in(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + + return regmap_update_bits(priv->regmap, UB953_REG_GPIO_INPUT_CTRL, + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) | + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset), + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset)); +} + +static int ub953_gpio_direction_out(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + + ret = regmap_update_bits(priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : + 0); + + if (ret) + return ret; + + return regmap_update_bits(priv->regmap, UB953_REG_GPIO_INPUT_CTRL, + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) | + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset), + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset)); +} + +static int ub953_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_GPIO_PIN_STS, &v); + if (ret) + return ret; + + return !!(v & UB953_REG_GPIO_PIN_STS_GPIO_STS(offset)); +} + +static void ub953_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + + regmap_update_bits(priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : + 0); +} + +static int ub953_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub953_gpiochip_probe(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret; + + /* Set all GPIOs to local input mode */ + ub953_write(priv, UB953_REG_LOCAL_GPIO_DATA, 0); + ub953_write(priv, UB953_REG_GPIO_INPUT_CTRL, 0xf); + + gc->label = dev_name(dev); + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->can_sleep = true; + gc->ngpio = UB953_NUM_GPIOS; + gc->get_direction = ub953_gpio_get_direction; + gc->direction_input = ub953_gpio_direction_in; + gc->direction_output = ub953_gpio_direction_out; + gc->get = ub953_gpio_get; + gc->set = ub953_gpio_set; + gc->of_xlate = ub953_gpio_of_xlate; + gc->of_gpio_n_cells = 2; + + ret = gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub953_gpiochip_remove(struct ub953_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +/* + * V4L2 + */ + +static int _ub953_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + /* + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. + */ + + if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -EINVAL; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int ub953_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub953_data *priv = sd_to_ub953(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->enabled_source_streams) + return -EBUSY; + + return _ub953_set_routing(sd, state, routing); +} + +static int ub953_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret; + + if (pad != UB953_PAD_SOURCE) + return -EINVAL; + + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, + priv->source_sd_pad, &source_fd); + if (ret) + return ret; + + memset(fd, 0, sizeof(*fd)); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + for_each_active_route(&state->routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + unsigned int i; + + if (route->source_pad != pad) + continue; + + for (i = 0; i < source_fd.num_entries; ++i) { + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + } + + if (!source_entry) { + dev_err(&priv->client->dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out_unlock; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_entry->flags; + fd->entry[fd->num_entries].length = source_entry->length; + fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode; + fd->entry[fd->num_entries].bus.csi2.vc = + source_entry->bus.csi2.vc; + fd->entry[fd->num_entries].bus.csi2.dt = + source_entry->bus.csi2.dt; + + fd->num_entries++; + } + +out_unlock: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub953_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == UB953_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub953_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = UB953_PAD_SINK, + .sink_stream = 0, + .source_pad = UB953_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub953_set_routing(sd, state, &routing); +} + +static int ub953_log_status(struct v4l2_subdev *sd) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct device *dev = &priv->client->dev; + u8 v = 0, v1 = 0, v2 = 0; + unsigned int i; + char id[7]; + u8 gpio_local_data; + u8 gpio_input_ctrl; + u8 gpio_pin_sts; + + for (i = 0; i < 6; ++i) + ub953_read(priv, UB953_REG_FPD3_RX_ID(i), &id[i]); + id[6] = 0; + + dev_info(dev, "ID '%s'\n", id); + + ub953_read(priv, UB953_REG_GENERAL_STATUS, &v); + dev_info(dev, "GENERAL_STATUS %#02x\n", v); + + ub953_read(priv, UB953_REG_CRC_ERR_CNT1, &v1); + ub953_read(priv, UB953_REG_CRC_ERR_CNT2, &v2); + dev_info(dev, "CRC error count %u\n", v1 | (v2 << 8)); + + ub953_read(priv, UB953_REG_CSI_ERR_CNT, &v); + dev_info(dev, "CSI error count %u\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_STATUS, &v); + dev_info(dev, "CSI_ERR_STATUS %#02x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v); + dev_info(dev, "CSI_ERR_DLANE01 %#02x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v); + dev_info(dev, "CSI_ERR_DLANE23 %#02x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v); + dev_info(dev, "CSI_ERR_CLK_LANE %#02x\n", v); + + ub953_read(priv, UB953_REG_CSI_PKT_HDR_VC_ID, &v); + dev_info(dev, "CSI packet header VC %u ID %u\n", v >> 6, v & 0x3f); + + ub953_read(priv, UB953_REG_PKT_HDR_WC_LSB, &v1); + ub953_read(priv, UB953_REG_PKT_HDR_WC_MSB, &v2); + dev_info(dev, "CSI packet header WC %u\n", (v2 << 8) | v1); + + ub953_read(priv, UB953_REG_CSI_ECC, &v); + dev_info(dev, "CSI ECC %#02x\n", v); + + ub953_read(priv, UB953_REG_LOCAL_GPIO_DATA, &gpio_local_data); + ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &gpio_input_ctrl); + ub953_read(priv, UB953_REG_GPIO_PIN_STS, &gpio_pin_sts); + + for (i = 0; i < UB953_NUM_GPIOS; ++i) { + dev_info(dev, + "GPIO%u: remote: %u is_input: %u is_output: %u val: %u sts: %u\n", + i, + !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(i)), + !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(i)), + !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_OUT_EN(i)), + !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(i)), + !!(gpio_pin_sts & UB953_REG_GPIO_PIN_STS_GPIO_STS(i))); + } + + return 0; +} + +static int ub953_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub953_data *priv = sd_to_ub953(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE, + UB953_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_enable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |= streams_mask; + + return 0; +} + +static int ub953_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub953_data *priv = sd_to_ub953(sd); + u64 sink_streams; + int ret; + + sink_streams = v4l2_subdev_state_xlate_streams(state, UB953_PAD_SOURCE, + UB953_PAD_SINK, + &streams_mask); + + ret = v4l2_subdev_disable_streams(priv->source_sd, priv->source_sd_pad, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ub953_pad_ops = { + .enable_streams = ub953_enable_streams, + .disable_streams = ub953_disable_streams, + .set_routing = ub953_set_routing, + .get_frame_desc = ub953_get_frame_desc, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub953_set_fmt, + .init_cfg = ub953_init_cfg, +}; + +static const struct v4l2_subdev_core_ops ub953_subdev_core_ops = { + .log_status = ub953_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ub953_subdev_ops = { + .core = &ub953_subdev_core_ops, + .pad = &ub953_pad_ops, +}; + +static const struct media_entity_operations ub953_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +enum { + TEST_PATTERN_DISABLED = 0, + TEST_PATTERN_V_COLOR_BARS_1, + TEST_PATTERN_V_COLOR_BARS_2, + TEST_PATTERN_V_COLOR_BARS_4, + TEST_PATTERN_V_COLOR_BARS_8, +}; + +static const char *const ub953_tpg_qmenu[] = { + "Disabled", + "1 vertical color bar", + "2 vertical color bars", + "4 vertical color bars", + "8 vertical color bars", +}; + +static int ub953_enable_tpg(struct ub953_data *priv, int tpg_num) +{ + struct v4l2_subdev *sd = &priv->sd; + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + u8 vbp, vfp; + u16 blank_lines; + u16 width; + u16 height; + + u16 bytespp = 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */ + u8 cbars_idx = tpg_num - TEST_PATTERN_V_COLOR_BARS_1; + u8 num_cbars = 1 << cbars_idx; + + u16 line_size; /* Line size [bytes] */ + u16 bar_size; /* cbar size [bytes] */ + u16 act_lpf; /* active lines/frame */ + u16 tot_lpf; /* tot lines/frame */ + u16 line_pd; /* Line period in 10-ns units */ + + u16 fps = 30; + + vbp = 33; + vfp = 10; + blank_lines = vbp + vfp + 2; /* total blanking lines */ + + state = v4l2_subdev_get_locked_active_state(sd); + + if (state->routing.num_routes != 1) + return -EINVAL; + + fmt = v4l2_subdev_state_get_stream_format(state, UB953_PAD_SOURCE, 0); + if (!fmt) + return -EINVAL; + + if (fmt->code != MEDIA_BUS_FMT_UYVY8_1X16) + return -EINVAL; + + width = fmt->width; + height = fmt->height; + + line_size = width * bytespp; + bar_size = line_size / num_cbars; + act_lpf = height; + tot_lpf = act_lpf + blank_lines; + line_pd = 100000000 / fps / tot_lpf; + + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CTL, + UB953_IND_PGEN_CTL_PGEN_ENABLE); + + /* YUV422 8bit: 2 bytes/block, CSI-2 data type 0x1e */ + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CFG, + cbars_idx << 4 | 0x2); + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CSI_DI, + 0x1e); + + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_LINE_SIZE1, line_size); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_BAR_SIZE1, bar_size); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_ACT_LPF1, act_lpf); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_TOT_LPF1, tot_lpf); + ub953_write_ind16(priv, UB953_IND_TARGET_PAT_GEN, + UB953_IND_PGEN_LINE_PD1, line_pd); + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_VBP, + vbp); + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_VFP, + vfp); + + return 0; +} + +static void ub953_disable_tpg(struct ub953_data *priv) +{ + ub953_write_ind(priv, UB953_IND_TARGET_PAT_GEN, UB953_IND_PGEN_CTL, + 0x0); +} + +static int ub953_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ub953_data *priv = + container_of(ctrl->handler, struct ub953_data, ctrl_handler); + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + if (ctrl->val == 0) + ub953_disable_tpg(priv); + else + ret = ub953_enable_tpg(priv, ctrl->val); + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops ub953_ctrl_ops = { + .s_ctrl = ub953_s_ctrl, +}; + +static int ub953_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_subdev *asd) +{ + struct ub953_data *priv = sd_to_ub953(notifier->sd); + struct device *dev = &priv->client->dev; + int ret; + + ret = media_entity_get_fwnode_pad(&source_subdev->entity, + source_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", + source_subdev->name); + return ret; + } + + priv->source_sd = source_subdev; + priv->source_sd_pad = ret; + + ret = media_create_pad_link(&source_subdev->entity, priv->source_sd_pad, + &priv->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, priv->source_sd_pad, + priv->sd.name); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations ub953_notify_ops = { + .bound = ub953_notify_bound, +}; + +static int ub953_v4l2_notifier_register(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB953_PAD_SINK, 0, 0); + if (!ep_fwnode) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_nf_init(&priv->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, ep_fwnode, + struct v4l2_async_subdev); + + fwnode_handle_put(ep_fwnode); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &ub953_notify_ops; + + ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub953_v4l2_notifier_unregister(struct ub953_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +/* + * Probing + */ + +static int ub953_i2c_master_init(struct ub953_data *priv) +{ + /* i2c fast mode */ + u32 ref = 26250000; + u32 scl_high = 915; /* ns */ + u32 scl_low = 1641; /* ns */ + int ret; + + scl_high = div64_u64((u64)scl_high * ref, 1000000000) - 5; + scl_low = div64_u64((u64)scl_low * ref, 1000000000) - 5; + + ret = ub953_write(priv, UB953_REG_SCL_HIGH_TIME, scl_high); + if (ret) + return ret; + + ret = ub953_write(priv, UB953_REG_SCL_LOW_TIME, scl_low); + if (ret) + return ret; + + return 0; +} + +static u64 ub953_get_fc_rate(struct ub953_data *priv) +{ + if (priv->mode != UB953_MODE_SYNC) { + /* Not supported */ + return 0; + } + + if (priv->hw_data->is_ub971) + return priv->plat_data->bc_rate * 160ull; + else + return priv->plat_data->bc_rate / 2 * 160ull; +} + +static unsigned long ub953_calc_clkout_ub953(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *hs_div, u8 *m, u8 *n) +{ + /* + * We always use 4 as a pre-divider (HS_CLK_DIV = 2). + * + * According to the datasheet: + * - "HS_CLK_DIV typically should be set to either 16, 8, or 4 (default)." + * - "if it is not possible to have an integer ratio of N/M, it is best to + * select a smaller value for HS_CLK_DIV. + * + * For above reasons the default HS_CLK_DIV seems the best in the average + * case. Use always that value to keep the code simple. + */ + static const unsigned long hs_clk_div = 4; + + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout = fc / hs_clk_div * m / n */ + + fc_divided = div_u64(fc, hs_clk_div); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res = div_u64(fc_divided * mul, div); + + *hs_div = hs_clk_div; + *m = mul; + *n = div; + + return res; +} + +static unsigned long ub953_calc_clkout_ub971(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *m, u8 *n) +{ + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout = fc * m / (8 * n) */ + + fc_divided = div_u64(fc, 8); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res = div_u64(fc_divided * mul, div); + + *m = mul; + *n = div; + + return res; +} + +static unsigned long ub953_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct device *dev = &priv->client->dev; + u8 ctrl0, ctrl1; + u32 mul, div; + u64 fc_rate; + u32 hs_clk_div; + u64 rate; + int ret; + + ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL0, &ctrl0); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL0: %d\n", ret); + return 0; + } + + ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL1, &ctrl1); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL1: %d\n", ret); + return 0; + } + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->is_ub971) { + mul = ctrl0 & 0x1f; + div = ctrl1; + + if (div == 0) + return 0; + + rate = div_u64(fc_rate * mul, 8 * div); + + dev_dbg(dev, "clkout: fc rate %llu, mul %u, div %u = %llu\n", + fc_rate, mul, div, rate); + } else { + mul = ctrl0 & 0x1f; + hs_clk_div = 1 << (ctrl0 >> 5); + div = ctrl1; + + if (div == 0) + return 0; + + rate = div_u64(div_u64(fc_rate, hs_clk_div) * mul, div); + + dev_dbg(dev, + "clkout: fc rate %llu, hs_clk_div %u, mul %u, div %u = %llu\n", + fc_rate, hs_clk_div, mul, div, rate); + } + + return rate; +} + +static long ub953_clkout_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct device *dev = &priv->client->dev; + unsigned long res; + u64 fc_rate; + u8 hs_div, m, n; + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->is_ub971) { + res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n); + + dev_dbg(dev, "%s %llu * %u / (8 * %u) = %lu (requested %lu)", + __func__, fc_rate, m, n, res, rate); + } else { + res = ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n); + + dev_dbg(dev, "%s %llu / %u * %u / %u = %lu (requested %lu)", + __func__, fc_rate, hs_div, m, n, res, rate); + } + + return res; +} + +static int ub953_clkout_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + u64 fc_rate; + u8 hs_div, m, n; + unsigned long res; + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->is_ub971) { + res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); + } else { + res = ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, (__ffs(hs_div) << 5) | m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); + } + + dev_dbg(&priv->client->dev, "%s %lu (requested %lu)\n", __func__, res, + rate); + + return 0; +} + +static const struct clk_ops ub953_clkout_ops = { + .recalc_rate = ub953_clkout_recalc_rate, + .round_rate = ub953_clkout_round_rate, + .set_rate = ub953_clkout_set_rate, +}; + +static void ub953_init_clkout_ub953(struct ub953_data *priv) +{ + u64 fc_rate; + u8 hs_div, m, n; + + fc_rate = ub953_get_fc_rate(priv); + + ub953_calc_clkout_ub953(priv, 25000000, fc_rate, &hs_div, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, (__ffs(hs_div) << 5) | m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); +} + +static void ub953_init_clkout_ub971(struct ub953_data *priv) +{ + u64 fc_rate; + u8 m, n; + + fc_rate = ub953_get_fc_rate(priv); + + ub953_calc_clkout_ub971(priv, 25000000, fc_rate, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); +} + +static int ub953_register_clkout(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + const struct clk_init_data init = { + .name = kasprintf(GFP_KERNEL, "ds90%s.%s.clk_out", + priv->hw_data->model, dev_name(dev)), + .ops = &ub953_clkout_ops, + }; + int ret; + + if (!init.name) + return -ENOMEM; + + /* Initialize clkout to 25MHz by default */ + if (priv->hw_data->is_ub971) + ub953_init_clkout_ub971(priv); + else + ub953_init_clkout_ub953(priv); + + priv->clkout_clk_hw.init = &init; + + ret = devm_clk_hw_register(dev, &priv->clkout_clk_hw); + kfree(init.name); + if (ret) + return dev_err_probe(dev, ret, "Cannot register clock HW\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &priv->clkout_clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; +} + +static int ub953_add_i2c_adapter(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle = device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port, + i2c_handle); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + return 0; +} + +static const struct regmap_config ub953_regmap_config = { + .name = "ds90ub953", + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_DEFAULT, + .val_format_endian = REGMAP_ENDIAN_DEFAULT, +}; + +static int ub953_parse_dt(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *ep_fwnode; + int ret; + + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB953_PAD_SINK, 0, 0); + if (!ep_fwnode) + return dev_err_probe(dev, -ENOENT, "no endpoint found\n"); + + ret = fwnode_property_count_u32(ep_fwnode, "data-lanes"); + + fwnode_handle_put(ep_fwnode); + + if (ret < 0) + return dev_err_probe(dev, ret, + "failed to parse property 'data-lanes'\n"); + + if (ret != 1 && ret != 2 && ret != 4) + return dev_err_probe(dev, -EINVAL, + "bad number of data-lanes: %d\n", ret); + + priv->num_data_lanes = ret; + + return 0; +} + +static int ub953_hw_init(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + bool mode_override; + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_MODE_SEL, &v); + if (ret) + return ret; + + if (!(v & UB953_REG_MODE_SEL_MODE_DONE)) + return dev_err_probe(dev, -EIO, "Mode value not stabilized\n"); + + mode_override = v & UB953_REG_MODE_SEL_MODE_OVERRIDE; + + switch (v & UB953_REG_MODE_SEL_MODE_MASK) { + case 0: + priv->mode = UB953_MODE_SYNC; + break; + case 2: + priv->mode = UB953_MODE_NONSYNC_EXT; + break; + case 3: + priv->mode = UB953_MODE_NONSYNC_INT; + break; + case 5: + priv->mode = UB953_MODE_DVP; + break; + default: + return dev_err_probe(dev, -EIO, + "Invalid mode in mode register\n"); + } + + dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap", + priv->mode); + + if (priv->mode != UB953_MODE_SYNC) + return dev_err_probe(dev, -ENODEV, + "Only synchronous mode supported\n"); + + ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v); + if (ret) + return dev_err_probe(dev, ret, "Failed to read revision"); + + dev_info(dev, "Found %s rev/mask %#04x\n", priv->hw_data->model, v); + + ret = ub953_read(priv, UB953_REG_GENERAL_CFG, &v); + if (ret) + return ret; + + dev_dbg(dev, "i2c strap setting %s V\n", + (v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "1.8" : "3.3"); + + ret = ub953_i2c_master_init(priv); + if (ret) + return dev_err_probe(dev, ret, "i2c init failed\n"); + + ub953_write(priv, UB953_REG_GENERAL_CFG, + UB953_REG_GENERAL_CFG_CONT_CLK | + ((priv->num_data_lanes - 1) << UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT) | + UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE); + + return 0; +} + +static int ub953_subdev_init(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops); + + v4l2_ctrl_handler_init(&priv->ctrl_handler, 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub953_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ub953_tpg_qmenu) - 1, 0, 0, + ub953_tpg_qmenu); + + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + dev_err_probe(dev, ret, "Failed to set up v4l2 controls\n"); + goto err_remove_ctrls; + } + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub953_entity_ops; + + priv->pads[0].flags = MEDIA_PAD_FL_SINK; + priv->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) { + dev_err_probe(dev, ret, "Failed to init pads\n"); + goto err_remove_ctrls; + } + + priv->sd.fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + UB953_PAD_SOURCE, 0, + 0); + if (!priv->sd.fwnode) { + ret = -ENODEV; + dev_err_probe(dev, ret, "Missing TX endpoint\n"); + goto err_entity_cleanup; + } + + priv->sd.state_lock = priv->sd.ctrl_handler->lock; + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_fwnode_put; + + ret = ub953_v4l2_notifier_register(priv); + if (ret) { + dev_err_probe(dev, ret, + "v4l2 subdev notifier register failed\n"); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err_probe(dev, ret, "v4l2_async_register_subdev error\n"); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub953_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_fwnode_put: + fwnode_handle_put(priv->sd.fwnode); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); +err_remove_ctrls: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +} + +static void ub953_subdev_uninit(struct ub953_data *priv) +{ + v4l2_async_unregister_subdev(&priv->sd); + ub953_v4l2_notifier_unregister(priv); + v4l2_subdev_cleanup(&priv->sd); + fwnode_handle_put(priv->sd.fwnode); + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static int ub953_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub953_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->hw_data = device_get_match_data(dev); + + priv->plat_data = dev_get_platdata(&client->dev); + if (!priv->plat_data) + return dev_err_probe(dev, -ENODEV, "Platform data missing\n"); + + mutex_init(&priv->reg_lock); + + /* + * Initialize to invalid values so that the first reg writes will + * configure the target. + */ + priv->current_indirect_target = 0xff; + + priv->regmap = devm_regmap_init_i2c(client, &ub953_regmap_config); + if (IS_ERR(priv->regmap)) { + ret = PTR_ERR(priv->regmap); + dev_err_probe(dev, ret, "Failed to init regmap\n"); + goto err_mutex_destroy; + } + + ret = ub953_parse_dt(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub953_hw_init(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub953_gpiochip_probe(priv); + if (ret) { + dev_err_probe(dev, ret, "Failed to init gpiochip\n"); + goto err_mutex_destroy; + } + + ret = ub953_register_clkout(priv); + if (ret) { + dev_err_probe(dev, ret, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + ret = ub953_subdev_init(priv); + if (ret) + goto err_gpiochip_remove; + + ret = ub953_add_i2c_adapter(priv); + if (ret) { + dev_err_probe(dev, ret, "failed to add remote i2c adapter\n"); + goto err_subdev_uninit; + } + + return 0; + +err_subdev_uninit: + ub953_subdev_uninit(priv); +err_gpiochip_remove: + ub953_gpiochip_remove(priv); +err_mutex_destroy: + mutex_destroy(&priv->reg_lock); + + return ret; +} + +static void ub953_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub953_data *priv = sd_to_ub953(sd); + + i2c_atr_del_adapter(priv->plat_data->atr, priv->plat_data->port); + + ub953_subdev_uninit(priv); + + ub953_gpiochip_remove(priv); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub953_hw_data ds90ub953_hw = { + .model = "ub953", +}; + +static const struct ub953_hw_data ds90ub971_hw = { + .model = "ub971", + .is_ub971 = true, +}; + +static const struct i2c_device_id ub953_id[] = { + { "ds90ub953-q1", (kernel_ulong_t)&ds90ub953_hw }, + { "ds90ub971-q1", (kernel_ulong_t)&ds90ub971_hw }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ub953_id); + +static const struct of_device_id ub953_dt_ids[] = { + { .compatible = "ti,ds90ub953-q1", .data = &ds90ub953_hw }, + { .compatible = "ti,ds90ub971-q1", .data = &ds90ub971_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, ub953_dt_ids); + +static struct i2c_driver ds90ub953_driver = { + .probe_new = ub953_probe, + .remove = ub953_remove, + .id_table = ub953_id, + .driver = { + .name = "ds90ub953", + .owner = THIS_MODULE, + .of_match_table = ub953_dt_ids, + }, +}; +module_i2c_driver(ds90ub953_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments FPD-Link III/IV CSI-2 Serializers Driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS(I2C_ATR);