From patchwork Thu Jan 5 14:03:00 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39599 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp315309wrt; Thu, 5 Jan 2023 06:04:40 -0800 (PST) X-Google-Smtp-Source: AMrXdXsxdtK+yqxgEPSLlipFejUNpSoXGGE9IHC1LtTJbJZrSvyPs41UuNFznvQO/iGzMmaFjqU1 X-Received: by 2002:a05:6a20:d04a:b0:ad:3ada:c712 with SMTP id hv10-20020a056a20d04a00b000ad3adac712mr63971531pzb.14.1672927479812; Thu, 05 Jan 2023 06:04:39 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927479; cv=none; d=google.com; s=arc-20160816; b=WP8i9BXIK/SBMCPKtlqaKFySkhgMKqNrNsP9HKCM41O0EIHfeB//HRmcov0A9QSGAJ zLCp0d9duz+oAIIthpxmJZvySAeitUf4KjVACRUPRtFI2d5vAcXXiN0uqwE5fsDjSyst Zia/HNY63KuZlMHdmnxV14bi3xq36eeyii5buZd+xWwoLKayOpFfhHwAjclsWziOpGJw fF/8KoCvJIVkdRBGE1Cu6jimq/ejyNrlMvD8lMC+NDst1PdMWtNEsg1a658cDodBUqvM CH2TUtVjfBFMomN0p7Di8Pgc10XmKpNyRQw0OIMZ6yYaw3mCE0dpUoSJrLsDUh8GTBoo W2UA== 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=ltqW+nOq/Q47vRdS96rC3m64HE4PdJN4YX7L1DfEV6A=; b=S/T2sNt1XEPG2Rv6XjSbXiSr1fV2l+AkHtOZuuAk7Zu/vBHdLTF8OIQUBIctWwg9eZ Xg5NLdUGTqpjsOOKHyIBg0t6C3+QnwRpSfVOtE93TkK8ArzGaFwOxFV6Fjt96NX5H6UD OpXgXgkyz8SZkgAaXI3FMkEnTBXyrs09UN6ze+6dxMvyJMtZ/NUJuPngIs+rRJ1KbwgV GJ6gz7pheBS+R4njt+66pqSE3+EgviYSRBqfaVc4vXKciNlL/YXcU9e86qoB/I8LGftE cepW8VfDUsbeNDy6D3HSf0ae8k5sWlVdP5sBNogJ0sXlH1a7Og3uHUJC49jV6kRClEOW xv1Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=kp+L0TlA; 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 j12-20020a056a00174c00b00574e84f7ee7si1002733pfc.174.2023.01.05.06.04.25; Thu, 05 Jan 2023 06:04:39 -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=kp+L0TlA; 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 S234608AbjAEOEJ (ORCPT + 99 others); Thu, 5 Jan 2023 09:04:09 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49408 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S233792AbjAEOD2 (ORCPT ); Thu, 5 Jan 2023 09:03:28 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DC3F840863; Thu, 5 Jan 2023 06:03:26 -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 E26454AE; Thu, 5 Jan 2023 15:03:23 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927405; bh=nzYI/InFnBKeKA+u3WNviBVDcVXlhGM1JMPGchLnOb0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=kp+L0TlAtqg51LR37LO/3z6UT+3EhZWHv3Zozz7hDP8oUNXvN2G7BRqfJybPJHao6 YgY8qNfi4xgE4Bv/OlWkl+z5+VvK+LdFuoqutG2F90+ckalIkHeIpUE0APU8vKrW+r 5mgHxBYFRqWgIqZCUMxLmtPyE5c96m1UfspFXDmc= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Luca Ceresoli , Tomi Valkeinen Subject: [PATCH v6 1/8] i2c: core: let adapters be notified of client attach/detach Date: Thu, 5 Jan 2023 16:03:00 +0200 Message-Id: <20230105140307.272052-2-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191604599191424?= X-GMAIL-MSGID: =?utf-8?q?1754191604599191424?= From: Luca Ceresoli An adapter might need to know when a new device is about to be added. This will soon bee needed to implement an "I2C address translator" (ATR for short), a device that propagates I2C transactions with a different slave address (an "alias" address). An ATR driver needs to know when a slave is being added to find a suitable alias and program the device translation map. Add an attach/detach callback pair to allow adapter drivers to be notified of clients being added and removed. Signed-off-by: Luca Ceresoli Signed-off-by: Tomi Valkeinen --- drivers/i2c/i2c-core-base.c | 21 ++++++++++++++++++++- include/linux/i2c.h | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/i2c/i2c-core-base.c b/drivers/i2c/i2c-core-base.c index 7539b0740351..8a0888ba89e5 100644 --- a/drivers/i2c/i2c-core-base.c +++ b/drivers/i2c/i2c-core-base.c @@ -916,6 +916,7 @@ int i2c_dev_irq_from_resources(const struct resource *resources, struct i2c_client * i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info) { + const struct i2c_attach_operations *attach_ops; struct i2c_client *client; int status; @@ -967,15 +968,24 @@ i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *inf } } + attach_ops = adap->attach_ops; + + if (attach_ops && attach_ops->attach_client && + attach_ops->attach_client(adap, info, client)) + goto out_remove_swnode; + status = device_register(&client->dev); if (status) - goto out_remove_swnode; + goto out_detach_client; dev_dbg(&adap->dev, "client [%s] registered with bus id %s\n", client->name, dev_name(&client->dev)); return client; +out_detach_client: + if (attach_ops && attach_ops->detach_client) + attach_ops->detach_client(adap, client); out_remove_swnode: device_remove_software_node(&client->dev); out_err_put_of_node: @@ -997,9 +1007,18 @@ EXPORT_SYMBOL_GPL(i2c_new_client_device); */ void i2c_unregister_device(struct i2c_client *client) { + const struct i2c_attach_operations *attach_ops; + struct i2c_adapter *adap; + if (IS_ERR_OR_NULL(client)) return; + adap = client->adapter; + attach_ops = adap->attach_ops; + + if (attach_ops && attach_ops->detach_client) + attach_ops->detach_client(adap, client); + if (client->dev.of_node) { of_node_clear_flag(client->dev.of_node, OF_POPULATED); of_node_put(client->dev.of_node); diff --git a/include/linux/i2c.h b/include/linux/i2c.h index f7c49bbdb8a1..9a385b6de388 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -584,6 +584,21 @@ struct i2c_lock_operations { void (*unlock_bus)(struct i2c_adapter *adapter, unsigned int flags); }; +/** + * struct i2c_attach_operations - callbacks to notify client attach/detach + * @attach_client: Notify of new client being attached + * @detach_client: Notify of new client being detached + * + * Both ops are optional. + */ +struct i2c_attach_operations { + int (*attach_client)(struct i2c_adapter *adapter, + const struct i2c_board_info *info, + const struct i2c_client *client); + void (*detach_client)(struct i2c_adapter *adapter, + const struct i2c_client *client); +}; + /** * struct i2c_timings - I2C timing information * @bus_freq_hz: the bus frequency in Hz @@ -726,6 +741,7 @@ struct i2c_adapter { /* data fields that are valid for all devices */ const struct i2c_lock_operations *lock_ops; + const struct i2c_attach_operations *attach_ops; struct rt_mutex bus_lock; struct rt_mutex mux_lock; From patchwork Thu Jan 5 14:03:01 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39596 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp314916wrt; Thu, 5 Jan 2023 06:04:04 -0800 (PST) X-Google-Smtp-Source: AMrXdXsEKxoxXZfxR5Q6ZDkfLrlDnAZzGZpLA1I0YLPKznXJrRuBLsP5zeFzzEc2watK4E356KvV X-Received: by 2002:a05:6a20:e61a:b0:ac:a4fd:f5bf with SMTP id my26-20020a056a20e61a00b000aca4fdf5bfmr65535863pzb.50.1672927444357; Thu, 05 Jan 2023 06:04:04 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927444; cv=none; d=google.com; s=arc-20160816; b=0lHGoUpatua0LoWIexH2RuQAWsnjWrwFQMklz5N+XlT7OKE/+UtkumganL+q6/V7kd kG6tuUfbIgoI1K20KuuVwIbge2qzwhkkW9tHkLnHu2ZO7SEIMlGYV7HwicY9QFJW6c2K GDEJ8aZjT+c951Z+Ll9koQu1SjNdZVZQgC381qouVW1mJ+6AQe1jdsFhQaJv8RgKTj3z hN0OAGt4amvdsrDH4PmQIAWJ7kyjcncSFEFMDQbOuR1NTSjF5stRxNnPdYp6nxSw7EWE xnxB/Om+gKTymSU0oxONxwGeXpSviKpHU+k+ZKLKhzo5OvmcKVdnk2BAR5YDPrBMkAjl qO6g== 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=HkjMCmxT3RmXA6TUD2IXdkw03db8hUH5iTK8jiSs1Cc=; b=u5AmplNU84dfAI5pYbTgLw4kMzQ2joOXd0wX0RNuL7HFfMOjNQLvaa8pIttWX1Ix4D 4Zt0kwK99Rl/TblWlyv+C9/T6eYuAd44lyktI6ylYD13tJweRdIvrCw305oBGIau8dBc 5fAt8PxV+y1Ofv+0pJDiizaa5NYGzqiYPK3m46Jf2Ln86iYJSGe4C7bA0WHjaiVLinP6 vU9T95V+RgHTwvXPWlxzxMpGU6zh6jz+04gyn25RpuuOY2A10rFvTn8zKQHkdVSsA81F PDV4Fk1p4ajzyy16hCxEIBCRXr5cMI6qGp15mu22zSXkWyfizGroANfvTQzCW50OViu1 7LHA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=puI+vNpl; 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 k2-20020a654642000000b00476c8632d24si40504112pgr.686.2023.01.05.06.03.51; Thu, 05 Jan 2023 06:04: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=puI+vNpl; 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 S234199AbjAEODq (ORCPT + 99 others); Thu, 5 Jan 2023 09:03:46 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49426 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234212AbjAEODc (ORCPT ); Thu, 5 Jan 2023 09:03:32 -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 F25D441D57; Thu, 5 Jan 2023 06:03:29 -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 5A2D06F0; Thu, 5 Jan 2023 15:03:25 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927406; bh=WDrkqUpnV83LO3mFX0X8aOb9mG1NXgDn9KpmOcw96Pw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=puI+vNpl8dhEKBA03kBF3OmIdsr134DKFCjuXXbKyAUoyudYHO1p9oNcG3srn8RIF NQuwdg3exQeWiAB4FcSyoTbFurG0NKtjpl021hee+PZ6hRdgf9/AaDiVP12DajnwH+ U3jsG8sJb0OhSGKnagZgYmlyj/sIWo78sl+5YhHA= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Luca Ceresoli , Tomi Valkeinen Subject: [PATCH v6 2/8] i2c: add I2C Address Translator (ATR) support Date: Thu, 5 Jan 2023 16:03:01 +0200 Message-Id: <20230105140307.272052-3-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191567713349585?= X-GMAIL-MSGID: =?utf-8?q?1754191567713349585?= From: Luca Ceresoli An ATR is a device that looks similar to an i2c-mux: it has an I2C slave "upstream" port and N master "downstream" ports, and forwards transactions from upstream to the appropriate downstream port. But is is different in that the forwarded transaction has a different slave address. The address used on the upstream bus is called the "alias" and is (potentially) different from the physical slave address of the downstream chip. Add a helper file (just like i2c-mux.c for a mux or switch) to allow implementing ATR features in a device driver. The helper takes care or adapter creation/destruction and translates addresses at each transaction. Signed-off-by: Luca Ceresoli Signed-off-by: Tomi Valkeinen --- Documentation/i2c/index.rst | 1 + Documentation/i2c/muxes/i2c-atr.rst | 83 +++++ MAINTAINERS | 8 + drivers/i2c/Kconfig | 9 + drivers/i2c/Makefile | 1 + drivers/i2c/i2c-atr.c | 501 ++++++++++++++++++++++++++++ include/linux/i2c-atr.h | 118 +++++++ 7 files changed, 721 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..0668c98886ad --- /dev/null +++ b/drivers/i2c/i2c-atr.c @@ -0,0 +1,501 @@ +// 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 99 /* Just a sanity limit */ +#define ATR_MAX_SYMLINK_LEN 16 /* Longest name is 10 chars: "channel-99" */ + +/** + * struct i2c_atr_cli2alias_pair - Holds the alias assigned to a client. + * @node: List node + * @client: Pointer to the client on the child bus + * @alias: I2C alias address assigned by the driver. + * This is the address that will be used to issue I2C transactions + * on the parent (physical) bus. + */ +struct i2c_atr_cli2alias_pair { + struct list_head node; + const struct i2c_client *client; + u16 alias; +}; + +/** + * struct i2c_atr_chan - Data for a channel. + * @adap: The &struct i2c_adapter for the channel + * @atr: The parent I2C ATR + * @chan_id: The ID of this channel + * @alias_list: List of @struct i2c_atr_cli2alias_pair containing the + * assigned aliases + * @orig_addrs_lock: Mutex protecting @orig_addrs + * @orig_addrs: Buffer used to store the original addresses during transmit + * @orig_addrs_size: Size of @orig_addrs + */ +struct i2c_atr_chan { + struct i2c_adapter adap; + struct i2c_atr *atr; + u32 chan_id; + + struct list_head alias_list; + + /* Lock orig_addrs during xfer */ + struct mutex orig_addrs_lock; + u16 *orig_addrs; + unsigned int orig_addrs_size; +}; + +/** + * struct i2c_atr - The I2C ATR instance + * @parent: The parent &struct i2c_adapter + * @dev: The device that owns the I2C ATR instance + * @ops: &struct i2c_atr_ops + * @priv: Private driver data, set with i2c_atr_set_driver_data() + * @algo: The &struct i2c_algorithm for adapters + * @lock: Lock for the I2C bus segment (see &struct i2c_lock_operations) + * @max_adapters: Maximum number of adapters this I2C ATR can have + * @adapter: Array of adapters + */ +struct i2c_atr { + struct i2c_adapter *parent; + struct device *dev; + const struct i2c_atr_ops *ops; + + void *priv; + + struct i2c_algorithm algo; + /* lock for the I2C bus segment (see struct i2c_lock_operations) */ + struct mutex lock; + int max_adapters; + + struct i2c_adapter *adapter[]; +}; + +static struct i2c_atr_cli2alias_pair * +i2c_atr_find_mapping_by_client(const struct list_head *list, + const struct i2c_client *client) +{ + struct i2c_atr_cli2alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client == client) + return c2a; + } + + return NULL; +} + +static struct i2c_atr_cli2alias_pair * +i2c_atr_find_mapping_by_addr(const struct list_head *list, u16 phys_addr) +{ + struct i2c_atr_cli2alias_pair *c2a; + + list_for_each_entry(c2a, list, node) { + if (c2a->client->addr == phys_addr) + return c2a; + } + + return NULL; +} + +/* + * Replace all message addresses with their aliases, saving the original + * addresses. + * + * This function is internal for use in i2c_atr_master_xfer(). It must be + * followed by i2c_atr_unmap_msgs() to restore the original addresses. + */ +static int i2c_atr_map_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, + int num) +{ + struct i2c_atr *atr = chan->atr; + static struct i2c_atr_cli2alias_pair *c2a; + int i; + + /* Ensure we have enough room to save the original addresses */ + if (unlikely(chan->orig_addrs_size < num)) { + u16 *new_buf; + + new_buf = kmalloc_array(num, sizeof(*new_buf), GFP_KERNEL); + if (!new_buf) + return -ENOMEM; + + kfree(chan->orig_addrs); + chan->orig_addrs = new_buf; + chan->orig_addrs_size = num; + } + + for (i = 0; i < num; i++) { + chan->orig_addrs[i] = msgs[i].addr; + + c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, + msgs[i].addr); + if (!c2a) { + dev_err(atr->dev, "client 0x%02x not mapped!\n", + msgs[i].addr); + return -ENXIO; + } + + msgs[i].addr = c2a->alias; + } + + return 0; +} + +/* + * Restore all message address aliases with the original addresses. This + * function is internal for use in i2c_atr_master_xfer(). + * + * @see i2c_atr_map_msgs() + */ +static void i2c_atr_unmap_msgs(struct i2c_atr_chan *chan, struct i2c_msg *msgs, + int num) +{ + int i; + + for (i = 0; i < num; i++) + msgs[i].addr = chan->orig_addrs[i]; +} + +static int i2c_atr_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, + int num) +{ + struct i2c_atr_chan *chan = adap->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_adapter *parent = atr->parent; + int ret; + + /* Switch to the right atr port */ + if (atr->ops->select) { + ret = atr->ops->select(atr, chan->chan_id); + if (ret < 0) + goto out_deselect; + } + + /* Translate addresses */ + mutex_lock(&chan->orig_addrs_lock); + ret = i2c_atr_map_msgs(chan, msgs, num); + if (ret < 0) + goto out_unlock_deselect; + + /* Perform the transfer */ + ret = i2c_transfer(parent, msgs, num); + + /* Restore addresses */ + i2c_atr_unmap_msgs(chan, msgs, num); + +out_unlock_deselect: + mutex_unlock(&chan->orig_addrs_lock); + +out_deselect: + if (atr->ops->deselect) + atr->ops->deselect(atr, chan->chan_id); + + return ret; +} + +static int i2c_atr_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, u8 command, + int size, union i2c_smbus_data *data) +{ + struct i2c_atr_chan *chan = adap->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_adapter *parent = atr->parent; + struct i2c_atr_cli2alias_pair *c2a; + int ret = 0; + + c2a = i2c_atr_find_mapping_by_addr(&chan->alias_list, addr); + if (!c2a) { + dev_err(atr->dev, "client 0x%02x not mapped!\n", addr); + return -ENXIO; + } + + if (atr->ops->select) + ret = atr->ops->select(atr, chan->chan_id); + if (!ret) + ret = i2c_smbus_xfer(parent, c2a->alias, flags, read_write, + command, size, data); + if (atr->ops->deselect) + atr->ops->deselect(atr, chan->chan_id); + + return ret; +} + +static u32 i2c_atr_functionality(struct i2c_adapter *adap) +{ + struct i2c_atr_chan *chan = adap->algo_data; + struct i2c_adapter *parent = chan->atr->parent; + + return parent->algo->functionality(parent); +} + +static void i2c_atr_lock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + + mutex_lock(&atr->lock); +} + +static int i2c_atr_trylock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + + return mutex_trylock(&atr->lock); +} + +static void i2c_atr_unlock_bus(struct i2c_adapter *adapter, unsigned int flags) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + + mutex_unlock(&atr->lock); +} + +static const struct i2c_lock_operations i2c_atr_lock_ops = { + .lock_bus = i2c_atr_lock_bus, + .trylock_bus = i2c_atr_trylock_bus, + .unlock_bus = i2c_atr_unlock_bus, +}; + +static int i2c_atr_attach_client(struct i2c_adapter *adapter, + const struct i2c_board_info *info, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_atr_cli2alias_pair *c2a; + u16 alias_id; + int ret; + + c2a = kzalloc(sizeof(*c2a), GFP_KERNEL); + if (!c2a) + return -ENOMEM; + + ret = atr->ops->attach_client(atr, chan->chan_id, info, client, + &alias_id); + if (ret) + goto err_free; + + c2a->client = client; + c2a->alias = alias_id; + list_add(&c2a->node, &chan->alias_list); + + return 0; + +err_free: + kfree(c2a); + + return ret; +} + +static void i2c_atr_detach_client(struct i2c_adapter *adapter, + const struct i2c_client *client) +{ + struct i2c_atr_chan *chan = adapter->algo_data; + struct i2c_atr *atr = chan->atr; + struct i2c_atr_cli2alias_pair *c2a; + + atr->ops->detach_client(atr, chan->chan_id, client); + + c2a = i2c_atr_find_mapping_by_client(&chan->alias_list, client); + if (c2a) { + list_del(&c2a->node); + kfree(c2a); + } +} + +static const struct i2c_attach_operations i2c_atr_attach_ops = { + .attach_client = i2c_atr_attach_client, + .detach_client = i2c_atr_detach_client, +}; + +struct i2c_atr *i2c_atr_new(struct i2c_adapter *parent, struct device *dev, + const struct i2c_atr_ops *ops, int max_adapters) +{ + struct i2c_atr *atr; + + if (max_adapters > ATR_MAX_ADAPTERS) + return ERR_PTR(-EINVAL); + + if (!ops || !ops->attach_client || !ops->detach_client) + return ERR_PTR(-EINVAL); + + atr = devm_kzalloc(dev, struct_size(atr, adapter, max_adapters), + GFP_KERNEL); + if (!atr) + return ERR_PTR(-ENOMEM); + + mutex_init(&atr->lock); + + atr->parent = parent; + atr->dev = dev; + atr->ops = ops; + atr->max_adapters = max_adapters; + + if (parent->algo->master_xfer) + atr->algo.master_xfer = i2c_atr_master_xfer; + if (parent->algo->smbus_xfer) + atr->algo.smbus_xfer = i2c_atr_smbus_xfer; + atr->algo.functionality = i2c_atr_functionality; + + return atr; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_new, I2C_ATR); + +void i2c_atr_delete(struct i2c_atr *atr) +{ + mutex_destroy(&atr->lock); +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_delete, I2C_ATR); + +int i2c_atr_add_adapter(struct i2c_atr *atr, u32 chan_id, + struct fwnode_handle *bus_handle) +{ + struct i2c_adapter *parent = atr->parent; + struct device *dev = atr->dev; + struct i2c_atr_chan *chan; + char symlink_name[ATR_MAX_SYMLINK_LEN]; + int ret; + + if (chan_id >= atr->max_adapters) { + dev_err(dev, "No room for more i2c-atr adapters\n"); + return -EINVAL; + } + + if (atr->adapter[chan_id]) { + dev_err(dev, "Adapter %d already present\n", chan_id); + return -EEXIST; + } + + chan = kzalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + chan->atr = atr; + chan->chan_id = chan_id; + INIT_LIST_HEAD(&chan->alias_list); + mutex_init(&chan->orig_addrs_lock); + + snprintf(chan->adap.name, sizeof(chan->adap.name), "i2c-%d-atr-%d", + i2c_adapter_id(parent), chan_id); + chan->adap.owner = THIS_MODULE; + chan->adap.algo = &atr->algo; + chan->adap.algo_data = chan; + chan->adap.dev.parent = dev; + chan->adap.retries = parent->retries; + chan->adap.timeout = parent->timeout; + chan->adap.quirks = parent->quirks; + chan->adap.lock_ops = &i2c_atr_lock_ops; + chan->adap.attach_ops = &i2c_atr_attach_ops; + + if (bus_handle) { + device_set_node(&chan->adap.dev, fwnode_handle_get(bus_handle)); + } else { + struct fwnode_handle *atr_node; + struct fwnode_handle *child; + u32 reg; + + atr_node = device_get_named_child_node(dev, "i2c-atr"); + + fwnode_for_each_child_node(atr_node, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + continue; + if (chan_id == reg) + break; + } + + device_set_node(&chan->adap.dev, child); + fwnode_handle_put(atr_node); + } + + ret = i2c_add_adapter(&chan->adap); + if (ret) { + dev_err(dev, "failed to add atr-adapter %u (error=%d)\n", + chan_id, ret); + goto err_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)); + + atr->adapter[chan_id] = &chan->adap; + + return 0; + +err_fwnode_put: + fwnode_handle_put(dev_fwnode(&chan->adap.dev)); + mutex_destroy(&chan->orig_addrs_lock); + kfree(chan); + return ret; +} +EXPORT_SYMBOL_NS_GPL(i2c_atr_add_adapter, I2C_ATR); + +void i2c_atr_del_adapter(struct i2c_atr *atr, u32 chan_id) +{ + char symlink_name[ATR_MAX_SYMLINK_LEN]; + + struct i2c_adapter *adap = atr->adapter[chan_id]; + struct i2c_atr_chan *chan = adap->algo_data; + struct fwnode_handle *fwnode = dev_fwnode(&adap->dev); + struct device *dev = atr->dev; + + if (!adap) + return; + + dev_dbg(dev, "Removing ATR child bus %d\n", i2c_adapter_id(adap)); + + atr->adapter[chan_id] = NULL; + + snprintf(symlink_name, sizeof(symlink_name), "channel-%u", + chan->chan_id); + sysfs_remove_link(&dev->kobj, symlink_name); + sysfs_remove_link(&chan->adap.dev.kobj, "atr_device"); + + i2c_del_adapter(adap); + fwnode_handle_put(fwnode); + mutex_destroy(&chan->orig_addrs_lock); + kfree(chan->orig_addrs); + kfree(chan); +} +EXPORT_SYMBOL_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..5b879115dfc6 --- /dev/null +++ b/include/linux/i2c-atr.h @@ -0,0 +1,118 @@ +/* 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. + * @select: Ask the driver to select a child bus (optional) + * @deselect: Ask the driver to deselect a child bus (optional) + * @attach_client: Notify the driver of a new device connected on a child + * bus. The driver must choose an I2C alias, configure the + * hardware to use it and return it in `alias_id`. + * @detach_client: Notify the driver of a device getting disconnected. The + * driver must configure the hardware to stop using the + * alias. + * + * All these functions return 0 on success, a negative error code otherwise. + */ +struct i2c_atr_ops { + int (*select)(struct i2c_atr *atr, u32 chan_id); + int (*deselect)(struct i2c_atr *atr, u32 chan_id); + int (*attach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_board_info *info, + const struct i2c_client *client, u16 *alias_id); + void (*detach_client)(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client); +}; + +/** + * 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 Thu Jan 5 14:03:02 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39600 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp315315wrt; Thu, 5 Jan 2023 06:04:40 -0800 (PST) X-Google-Smtp-Source: AMrXdXv5nrQUrtlzXHqG3BRp2k69KMcMduZ1QM8nNhG++DJBLltphjGyui7thC9Fvy1fSIqq+Tht X-Received: by 2002:a05:6a20:12d1:b0:b5:4928:3ab9 with SMTP id v17-20020a056a2012d100b000b549283ab9mr876369pzg.35.1672927480090; Thu, 05 Jan 2023 06:04:40 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927480; cv=none; d=google.com; s=arc-20160816; b=c8Dh7evBA68Ip02hvfPPd3NTG3mESk5qnfuHfQfQSNu1t+Jr5UTCXDTlVA0KF3nipx yWlnxl7Exg/8f4KKXGBuIvK/6Rp69RHd+SKTZP/DML5OIgX0xoFjovv2TFJUSeDUzCS9 HugVLUcFWv9KjER+0sKhUsVfTaVolz89akQ0jSJOSTK/wFlSOpSvkW8esoA+co/lIQsf iKMPXtx5pDLLNuAuK0F8BxwWZ7aHW0Fz6gLAsyflQvfxswLw9SVZBsfOWTkFsEbuuNJr 5/YFqq4uuCqdW4AQwYIGQxqkWTnnNqIafkxfn7UZQgprz96FDN835adBkbEV1SnvCqRh xQCA== 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=JuY+k6Nqx++Aa9VS9hXkPt0dHxh8BYOJFMxPwUOOUi4=; b=M5O5A09bp3hgZPzeKRiQWPuhZeRdjWVDXbqDQKdcXVHaDidJlxVcrl5X09DfWpiuSg 0dLjxAKhlxz4EjsfwRCR/VSDxrtxpBTe78DXAQg2CYJsfIpXjrS4RdOkEYVZJiEJVgto D+jeHdX4crSREtYKBrLTLnEXWB6++EusEvp2cVs+asyFs/LOy43o2FcMfvTPnZT5yav7 U37S7qeLm2W5TUTeAsBH/lL0TnhiXh4jn2+roRD584J1b9RX777FcS5wi2xylxzNu4YN DbhB1cmwGtmz63nRLmfpZiaUtAAL5CMgGTlNcsj+3p6YW5nHgRszVsMidoVOWg4u5XYV WRGQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=DCdVz06F; 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 a32-20020a634d20000000b0046f57b135c9si36109073pgb.182.2023.01.05.06.04.26; Thu, 05 Jan 2023 06:04:40 -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=DCdVz06F; 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 S234585AbjAEOD6 (ORCPT + 99 others); Thu, 5 Jan 2023 09:03:58 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49452 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234063AbjAEODa (ORCPT ); Thu, 5 Jan 2023 09:03:30 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A9188F5A0; Thu, 5 Jan 2023 06:03:29 -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 D054FBC0; Thu, 5 Jan 2023 15:03:26 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927408; bh=LsJLOwzVESJOEp9yfRoOO6DX4J3gM9s2APCPmMfWxGM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=DCdVz06F4VNz1tHGbpyFkoP23V2OA4C6J6uweJd0cCddRqxJpP2jvoEXyorLbMsp2 YGIPqxmcjSlHgNLxjzcaKLwjpBOgxvYsrGYTuMFMvxYgkhuwaEJbxa5JRcuIWncrS0 xBSX9zsmTrj3zOOiHgJOuTHqMUdnPQDBvMZPwaHQ= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Tomi Valkeinen Subject: [PATCH v6 3/8] dt-bindings: media: add TI DS90UB913 FPD-Link III Serializer Date: Thu, 5 Jan 2023 16:03:02 +0200 Message-Id: <20230105140307.272052-4-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191605261970506?= X-GMAIL-MSGID: =?utf-8?q?1754191605261970506?= 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 Thu Jan 5 14:03:03 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39602 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp315834wrt; Thu, 5 Jan 2023 06:05:33 -0800 (PST) X-Google-Smtp-Source: AMrXdXvv5k1iMsBzqCL9HxMWMnwL1TGgvbag7NkHD+7OMiC88TxS5MfDCAseADY2egwMUbp+MqXi X-Received: by 2002:a05:6a20:6699:b0:af:b771:1d01 with SMTP id o25-20020a056a20669900b000afb7711d01mr56133696pzh.49.1672927533158; Thu, 05 Jan 2023 06:05:33 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927533; cv=none; d=google.com; s=arc-20160816; b=XahcgATSm8TEJFXt24+yRXWwZp1XdlJrdrCaIPQJHSMpTy5l7eaaq2+Y06uk4aoSWg 0Nf1JJ6zV0fG+jRaSdN2nJSzjhZLj+I9k9EHTNB7T3M4pA9OlUWw5RdL5Z3Xnf95xBWk i1a5KfD5I/cEZWeyULaExu4Ard119pIIhb7lIrfj7Ac71RvhpRxjP18eiQIOY3s69dZi beC9HJTDW3SUEDdVdf0SBvHvYvFnhzffr1Mao+b/r4sf+zw+9qKCzKeh90/oVI3q6UDD xCukhEV8K5D/M9Uh4AnMISVA5784rzPy27mA8oPtmp0gK0ML2MbP4Jo3JeWLm5VgrWxn tVEA== 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=EqfOxTGC5aEgfteJSTrMe91ashBm/YBqBsWjeaMIz1g=; b=JaW7PNmy//MmVQQ8/DFOZesQw+jBI84RLtViHkU6R2t/GxxLJz84+DAz9M79P81ben l+CXee1ujNNUhw8AFpt6Cx+18g/0kgopgjYdvDBr1WH67G/vjcxbW4/h7ME1xAUy5RYc NbaGfjYzupkm/r/FCe0pIc7gliXTGQTI3jyYSld/mUheJTQAhR1kBI9u68dmFmxuplMk c/xkS/eoTim2l0ceiBEJp1HpMd1ZFo8K9jAEDaGsJpe9riqFTMXdpTDaOuemkG8FaQtd Y9gHHvTtezBNF0/JPnVc0pH0ka0rL9P6zYQP5hvMinc11vo1Xh2uSkHZXQikz3EbL1N/ 6+tA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=YPnIIsd9; 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 c19-20020a63d513000000b004a1c82305e6si13247562pgg.1.2023.01.05.06.05.19; Thu, 05 Jan 2023 06:05:33 -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=YPnIIsd9; 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 S234674AbjAEOE2 (ORCPT + 99 others); Thu, 5 Jan 2023 09:04:28 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49430 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234247AbjAEODd (ORCPT ); Thu, 5 Jan 2023 09:03:33 -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 2EAB02008; Thu, 5 Jan 2023 06:03:32 -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 3F621D8C; Thu, 5 Jan 2023 15:03:28 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927409; bh=nnlXKrVGAl8DumbyGRI6UA9wEiCBmXKebELeEKLetmI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=YPnIIsd9WPhNWoXCgGYsBgsRQAYdT1upaJ4pU1s6TK1lViaydg+ISOiTgbWxfETDO vCd8s1q+Os0h+7v4ocoAcP0cuBEoqbxluISE8JzsnbWP2CFs65YluSWsPjbn7v7bgi BSC98vOZiYTKBFLwrXO1ue1DzF5Uq84YEeWfllrc= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Tomi Valkeinen Subject: [PATCH v6 4/8] dt-bindings: media: add TI DS90UB953 FPD-Link III Serializer Date: Thu, 5 Jan 2023 16:03:03 +0200 Message-Id: <20230105140307.272052-5-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191661193141008?= X-GMAIL-MSGID: =?utf-8?q?1754191661193141008?= 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 Thu Jan 5 14:03:04 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39604 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp323233wrt; Thu, 5 Jan 2023 06:20:08 -0800 (PST) X-Google-Smtp-Source: AMrXdXs3OgyF+4GIzH6KZAd0ce0g0R6GNjsRr4P6RfuNWPLqxBe1k9iDAmJ6kyTJAIVPwRC7WfYX X-Received: by 2002:a17:902:8642:b0:192:c809:a1dd with SMTP id y2-20020a170902864200b00192c809a1ddmr15987545plt.20.1672928408608; Thu, 05 Jan 2023 06:20:08 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672928408; cv=none; d=google.com; s=arc-20160816; b=Kkl9Xe+Gpkd7btkmMHWCUaEvMLuBt68Iu6AVZquT224WjiYm4z+0oBcemJ1QNpKA3J wSGtgo3aT1yOSgguXx4U0YrJofO4m3FWNBE8jAKhpyjg9aGyyHRH3KQImWi56Bu7JY+6 HaLSeLdqSeM3WX8nA+uKlo1Xb2o/Ac3X5+mK/FVwNpTVdKtWxLay8sIBXlOU1vTOdFN7 ae1DSEKpwAnGoubFcG5vbVOebW8Q7HVx8KyaGxkGSoI81pq7YNz4WZDbtPetdpaSOgO1 Mxw+JhqIeAQ9teiY7nhXObCyFPh97GzKyuyRpdSkznOxgPH65wDYXVcP+saUw6kTxwmH 99HQ== 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=ew+0JGCWYH1XUxRDItMlLFi7DM7EG5Fbl1+FYGTQA7w=; b=A0bQTOMbSf0GEYsiVCXmXr14Y/Yq0M+wN35J003huX6Z8NmR06ZWeOaAxL3ZwkTCFM PZXvEmIXkP/2x5oD0P7LbNx7DUBsgUFKRWHk8zCM/2P+JKW3T33ZCCQJ4V75icnZ7Et8 dJO/6CH7ugvtNjFeg7HOM4xJkpgJq5u/xBLSPTOPTXX18Paqy0IxTHelOvHggf3L0m14 eVZJ51fTk8j404F2NtOCsstkdLrwo8fS4fAVRKMPsNfOPD3TwypbvZh93NQVhStFiSyR K96IijPE0H43a4JWDNbo4yGfFfQ4NMZcR5fYU3ziPdmoj/eabRku5GUYf8R1M0iw0p6S 0B6w== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=unsEPvfc; 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 o19-20020a656a53000000b0047839aeb6e4si46088383pgu.363.2023.01.05.06.19.54; Thu, 05 Jan 2023 06:20:08 -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=unsEPvfc; 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 S234028AbjAEOKk (ORCPT + 99 others); Thu, 5 Jan 2023 09:10:40 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49480 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234304AbjAEODe (ORCPT ); Thu, 5 Jan 2023 09:03:34 -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 540A1F5A0; Thu, 5 Jan 2023 06:03:33 -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 A54F4E35; Thu, 5 Jan 2023 15:03:29 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927410; bh=obzqZhLFhQBasGDxJabdtYs1n/X81EgemWVOcfNuR+Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=unsEPvfcLkH9KT9kHYAqBAg4I6ex6PFwjFFPmNBCTI8khbWsVw59F5Put+Q5s3ije qG4wIX7ZwkRitCAZjp13dF+Z46BFoG9eJTZSCVlOozfPi3IcvliZcet06OmO11uaM/ IqPz0igp6L3h8rNy7k7E8Rb8LQuv1ZHtESj1hKXw= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Tomi Valkeinen , Rob Herring Subject: [PATCH v6 5/8] dt-bindings: media: add TI DS90UB960 FPD-Link III Deserializer Date: Thu, 5 Jan 2023 16:03:04 +0200 Message-Id: <20230105140307.272052-6-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754192579052199473?= X-GMAIL-MSGID: =?utf-8?q?1754192579052199473?= Add DT bindings for TI DS90UB960 FPD-Link III Deserializer. Signed-off-by: Tomi Valkeinen Reviewed-by: Rob Herring --- .../bindings/media/i2c/ti,ds90ub960.yaml | 402 ++++++++++++++++++ 1 file changed, 402 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..664799ae55be --- /dev/null +++ b/Documentation/devicetree/bindings/media/i2c/ti,ds90ub960.yaml @@ -0,0 +1,402 @@ +# 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 camera module. + + 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 camera module. + + 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 + + required: + - data-lanes + + port@5: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: CSI-2 Output 1 + + properties: + endpoint: + $ref: /schemas/media/video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + minItems: 1 + maxItems: 4 + + required: + - data-lanes + +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 4, CSI-2 TX */ + port@4 { + reg = <4>; + ds90ub960_0_csi_out: endpoint { + data-lanes = <1 2 3 4>; + link-frequencies = /bits/ 64 <800000000>; + remote-endpoint = <&csi2_phy0>; + }; + }; + }; + + links { + #address-cells = <1>; + #size-cells = <0>; + + /* Link 0 has DS90UB953 serializer and IMX274 sensor */ + + link@0 { + reg = <0>; + i2c-alias = <0x44>; + + ti,rx-mode = <3>; + + serializer1: serializer { + compatible = "ti,ds90ub953-q1"; + + gpio-controller; + #gpio-cells = <2>; + + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ub953_1_in: endpoint { + data-lanes = <1 2 3 4>; + remote-endpoint = <&sensor_1_out>; + }; + }; + + port@1 { + reg = <1>; + + ub953_1_out: endpoint { + remote-endpoint = <&ub960_fpd3_1_in>; + }; + }; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + sensor@1a { + compatible = "sony,imx274"; + reg = <0x1a>; + + reset-gpios = <&serializer1 0 GPIO_ACTIVE_LOW>; + + port { + sensor_1_out: endpoint { + remote-endpoint = <&ub953_1_in>; + }; + }; + }; + }; + }; + }; /* End of link@0 */ + + /* Link 1 has DS90UB913 serializer and MT9V111 sensor */ + + link@1 { + reg = <1>; + i2c-alias = <0x45>; + + ti,rx-mode = <0>; + + serializer2: serializer { + compatible = "ti,ds90ub913a-q1"; + + gpio-controller; + #gpio-cells = <2>; + + clocks = <&clk_cam_48M>; + clock-names = "clkin"; + + #clock-cells = <0>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + ub913_2_in: endpoint { + remote-endpoint = <&sensor_2_out>; + 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 Thu Jan 5 14:03:05 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39601 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp315561wrt; Thu, 5 Jan 2023 06:05:06 -0800 (PST) X-Google-Smtp-Source: AMrXdXtGgzIlZvpsodS6xWU+c5rlHgP2Qqbpdc5curP81qwHcjJ8wmPV1o0hzkmGPVZP9HAATDu3 X-Received: by 2002:a17:90b:4c46:b0:226:7e0a:1cc3 with SMTP id np6-20020a17090b4c4600b002267e0a1cc3mr13694332pjb.46.1672927505817; Thu, 05 Jan 2023 06:05:05 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927505; cv=none; d=google.com; s=arc-20160816; b=gJ96BiPcNvX/F/jPB+1tkWEFESQcbT3/UFOsiQBpeNzueA/cN5oiTlzJ1B6LLW3911 3RlVjq+gskIdqosObwTSXcnqqzzQzgpYuE37kUUDLHSY0WQDft3eRvgg6eXadaGouJR3 h+DY42t4p2r2+xNA5nU+eZxaDqgnBbMpepg7VDq4UYgZ9s8UH66FC7WTjKG11zKRKRSn PzHpHHxkMiWMyKDddL2nfiJBC1neUxST3mzMAvWPMANJQdylJY4n/ZiITPyFqfXStyrJ a2VWIsLbC+7eoPapDmpJ9rCmT8tJBIZtHo/gnOb/25f+eLgvRXSqULMEp9tfxmnmG4LL QKXg== 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=Y33FfUiJ9ubYGNglCzYu0I4sdGUymlBdR9Na055bQrU=; b=K9rxRmi+KjJVB8x62Qvxg3NoiCqbvdexFYNeJsYzYk9Fk64Ya7yRdjzru2l/6f94yL /CI6vWW58hB7QSjvhvtN907uxQ2iNC78IhNkd+pHbT3zz4qAqhYN0+XorjzM0aIBNbbg 4UdMMcjTEJllU3HlVqng8msqRL9tzzX+DdOC/Xb2KIT/5C5PCQliIfH/EcL03YNuyKma tuyc6wuI3WGpiOdDcSbVIyewBA+nMWeGQldUrxJmYyr7wt/nRIMWbDt9CnmNrUDGDpPH 1JPiJTb4rVA4sGm2mZU0cxbAU8Z34lMiBOTppHdIa42n8wS8SNfjMIqNf2Sb3Vuncd2m HPBA== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=eIcDaaPX; 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 mq7-20020a17090b380700b00219741bac53si2247586pjb.123.2023.01.05.06.04.52; Thu, 05 Jan 2023 06:05:05 -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=eIcDaaPX; 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 S234664AbjAEOEY (ORCPT + 99 others); Thu, 5 Jan 2023 09:04:24 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49500 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234399AbjAEODi (ORCPT ); Thu, 5 Jan 2023 09:03:38 -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 862BB2008; Thu, 5 Jan 2023 06:03:34 -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 26642E51; Thu, 5 Jan 2023 15:03:31 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927412; bh=yGiebJXUVBm6axAQ7EQ4x9LRPMs2NcwZY/Fl+oS0YyY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=eIcDaaPXG02f6Im34x+0fotz6KrjBk7PMt0yk0qeOYEGpjp+xuQf/CuO4ZrT79NhR zdP1oKiqjkSy6Un6pNJtfSVT6IxZPHtJK38JMcIc5RW+upTKHZRN6EN1luOfrXjTrS 3UmJTFi4C2SgUpLPEi1X1lF3n4MA6B1TdP0IyLqA= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Tomi Valkeinen Subject: [PATCH v6 6/8] media: i2c: add DS90UB960 driver Date: Thu, 5 Jan 2023 16:03:05 +0200 Message-Id: <20230105140307.272052-7-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191632213394580?= X-GMAIL-MSGID: =?utf-8?q?1754191632213394580?= 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 | 2 + drivers/media/i2c/ds90ub960.c | 4295 +++++++++++++++++++++++++++++++++ include/media/i2c/ds90ub9xx.h | 16 + 5 files changed, 4342 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..f95a54454e17 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 DS90UB960 Deserializer" + depends on OF && I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + select REGMAP_I2C + select OF_GPIO + select I2C_ATR + help + Device driver for the Texas Instruments DS90UB960 + FPD-Link III Deserializer and DS90UB9702 FPD-Link IV Deserializer. + +endmenu + endif # VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 0a2933103dd9..2735b00437bb 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -142,3 +142,5 @@ obj-$(CONFIG_VIDEO_VPX3220) += vpx3220.o obj-$(CONFIG_VIDEO_VS6624) += vs6624.o obj-$(CONFIG_VIDEO_WM8739) += wm8739.o obj-$(CONFIG_VIDEO_WM8775) += wm8775.o +obj-$(CONFIG_VIDEO_DS90UB960) += ds90ub960.o + diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c new file mode 100644 index 000000000000..b532a93f3da7 --- /dev/null +++ b/drivers/media/i2c/ds90ub960.c @@ -0,0 +1,4295 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB960-Q1 video deserializer + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2022 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define UB960_POLL_TIME_MS 500 + +#define UB960_MAX_RX_NPORTS 4 +#define UB960_MAX_TX_NPORTS 2 +#define UB960_MAX_NPORTS (UB960_MAX_RX_NPORTS + UB960_MAX_TX_NPORTS) + +#define UB960_MAX_PORT_ALIASES 8 +#define UB960_MAX_POOL_ALIASES (UB960_MAX_RX_NPORTS * UB960_MAX_PORT_ALIASES) + +#define UB960_NUM_BC_GPIOS 4 + +/* + * Register map + * + * 0x00-0x32 Shared (UB960_SR) + * 0x33-0x3A CSI-2 TX (per-port paged on DS90UB960, shared on 954) (UB960_TR) + * 0x4C Shared (UB960_SR) + * 0x4D-0x7F FPD-Link RX, per-port paged (UB960_RR) + * 0xB0-0xBF Shared (UB960_SR) + * 0xD0-0xDF FPD-Link RX, per-port paged (UB960_RR) + * 0xF0-0xF5 Shared (UB960_SR) + * 0xF8-0xFB Shared (UB960_SR) + * All others Reserved + * + * Register prefixes: + * UB960_SR_* = Shared register + * UB960_RR_* = FPD-Link RX, per-port paged register + * UB960_TR_* = CSI-2 TX, per-port paged register + * UB960_XR_* = Reserved register + * UB960_IR_* = Indirect register + */ + +#define UB960_SR_I2C_DEV_ID 0x00 +#define UB960_SR_RESET 0x01 +#define UB960_SR_RESET_DIGITAL_RESET1 BIT(1) +#define UB960_SR_RESET_DIGITAL_RESET0 BIT(0) +#define UB960_SR_RESET_GPIO_LOCK_RELEASE BIT(5) + +#define UB960_SR_GEN_CONFIG 0x02 +#define UB960_SR_REV_MASK 0x03 +#define UB960_SR_DEVICE_STS 0x04 +#define UB960_SR_PAR_ERR_THOLD_HI 0x05 +#define UB960_SR_PAR_ERR_THOLD_LO 0x06 +#define UB960_SR_BCC_WDOG_CTL 0x07 +#define UB960_SR_I2C_CTL1 0x08 +#define UB960_SR_I2C_CTL2 0x09 +#define UB960_SR_SCL_HIGH_TIME 0x0A +#define UB960_SR_SCL_LOW_TIME 0x0B +#define UB960_SR_RX_PORT_CTL 0x0C +#define UB960_SR_IO_CTL 0x0D +#define UB960_SR_GPIO_PIN_STS 0x0E +#define UB960_SR_GPIO_INPUT_CTL 0x0F +#define UB960_SR_GPIO_PIN_CTL(n) (0x10 + (n)) /* n < UB960_NUM_GPIOS */ +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SEL 5 +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_SRC_SHIFT 2 +#define UB960_SR_GPIO_PIN_CTL_GPIO_OUT_EN BIT(0) + +#define UB960_SR_FS_CTL 0x18 +#define UB960_SR_FS_HIGH_TIME_1 0x19 +#define UB960_SR_FS_HIGH_TIME_0 0x1A +#define UB960_SR_FS_LOW_TIME_1 0x1B +#define UB960_SR_FS_LOW_TIME_0 0x1C +#define UB960_SR_MAX_FRM_HI 0x1D +#define UB960_SR_MAX_FRM_LO 0x1E +#define UB960_SR_CSI_PLL_CTL 0x1F + +#define UB960_SR_FWD_CTL1 0x20 +#define UB960_SR_FWD_CTL1_PORT_DIS(n) BIT((n) + 4) + +#define UB960_SR_FWD_CTL2 0x21 +#define UB960_SR_FWD_STS 0x22 + +#define UB960_SR_INTERRUPT_CTL 0x23 +#define UB960_SR_INTERRUPT_CTL_INT_EN BIT(7) +#define UB960_SR_INTERRUPT_CTL_IE_CSI_TX0 BIT(4) +#define UB960_SR_INTERRUPT_CTL_IE_RX(n) BIT((n)) /* rxport[n] IRQ */ +#define UB960_SR_INTERRUPT_CTL_ALL 0x83 /* TODO 0x93 to enable CSI */ + +#define UB960_SR_INTERRUPT_STS 0x24 +#define UB960_SR_INTERRUPT_STS_INT BIT(7) +#define UB960_SR_INTERRUPT_STS_IS_CSI_TX(n) BIT(4 + (n)) /* txport[n] IRQ */ +#define UB960_SR_INTERRUPT_STS_IS_RX(n) BIT((n)) /* rxport[n] IRQ */ + +#define UB960_SR_TS_CONFIG 0x25 +#define UB960_SR_TS_CONTROL 0x26 +#define UB960_SR_TS_LINE_HI 0x27 +#define UB960_SR_TS_LINE_LO 0x28 +#define UB960_SR_TS_STATUS 0x29 +#define UB960_SR_TIMESTAMP_P0_HI 0x2A +#define UB960_SR_TIMESTAMP_P0_LO 0x2B +#define UB960_SR_TIMESTAMP_P1_HI 0x2C +#define UB960_SR_TIMESTAMP_P1_LO 0x2D + +#define UB960_SR_CSI_PORT_SEL 0x32 + +#define UB960_TR_CSI_CTL 0x33 +#define UB960_TR_CSI_CTL_CSI_CAL_EN BIT(6) +#define UB960_TR_CSI_CTL_CSI_ENABLE BIT(0) + +#define UB960_TR_CSI_CTL2 0x34 +#define UB960_TR_CSI_STS 0x35 +#define UB960_TR_CSI_TX_ICR 0x36 + +#define UB960_TR_CSI_TX_ISR 0x37 +#define UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR BIT(3) +#define UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR BIT(1) + +#define UB960_TR_CSI_TEST_CTL 0x38 +#define UB960_TR_CSI_TEST_PATT_HI 0x39 +#define UB960_TR_CSI_TEST_PATT_LO 0x3A + +#define UB960_XR_SFILTER_CFG 0x41 +#define UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT 4 +#define UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT 0 + +#define UB960_XR_AEQ_CTL1 0x42 +#define UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_SHIFT 4 +#define UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN BIT(0) + +#define UB960_XR_AEQ_ERR_THOLD 0x43 + +#define UB960_RR_BCC_ERR_CTL 0x46 +#define UB960_RR_BCC_STATUS 0x47 +#define UB960_RR_BCC_STATUS_SEQ_ERROR BIT(5) +#define UB960_RR_BCC_STATUS_MASTER_ERR BIT(4) +#define UB960_RR_BCC_STATUS_MASTER_TO BIT(3) +#define UB960_RR_BCC_STATUS_SLAVE_ERR BIT(2) +#define UB960_RR_BCC_STATUS_SLAVE_TO BIT(1) +#define UB960_RR_BCC_STATUS_RESP_ERR BIT(0) + +#define UB960_RR_FPD3_CAP 0x4A +#define UB960_RR_RAW_EMBED_DTYPE 0x4B + +#define UB960_SR_FPD3_PORT_SEL 0x4C + +#define UB960_RR_RX_PORT_STS1 0x4D +#define UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR BIT(5) +#define UB960_RR_RX_PORT_STS1_LOCK_STS_CHG BIT(4) +#define UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR BIT(3) +#define UB960_RR_RX_PORT_STS1_PARITY_ERROR BIT(2) +#define UB960_RR_RX_PORT_STS1_PORT_PASS BIT(1) +#define UB960_RR_RX_PORT_STS1_LOCK_STS BIT(0) + +#define UB960_RR_RX_PORT_STS2 0x4E +#define UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE BIT(7) +#define UB960_RR_RX_PORT_STS2_LINE_LEN_CHG BIT(6) +#define UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR BIT(5) +#define UB960_RR_RX_PORT_STS2_BUFFER_ERROR BIT(4) +#define UB960_RR_RX_PORT_STS2_CSI_ERROR BIT(3) +#define UB960_RR_RX_PORT_STS2_FREQ_STABLE BIT(2) +#define UB960_RR_RX_PORT_STS2_CABLE_FAULT BIT(1) +#define UB960_RR_RX_PORT_STS2_LINE_CNT_CHG BIT(0) + +#define UB960_RR_RX_FREQ_HIGH 0x4F +#define UB960_RR_RX_FREQ_LOW 0x50 +#define UB960_RR_SENSOR_STS_0 0x51 +#define UB960_RR_SENSOR_STS_1 0x52 +#define UB960_RR_SENSOR_STS_2 0x53 +#define UB960_RR_SENSOR_STS_3 0x54 +#define UB960_RR_RX_PAR_ERR_HI 0x55 +#define UB960_RR_RX_PAR_ERR_LO 0x56 +#define UB960_RR_BIST_ERR_COUNT 0x57 + +#define UB960_RR_BCC_CONFIG 0x58 +#define UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH BIT(6) + +#define UB960_RR_DATAPATH_CTL1 0x59 +#define UB960_RR_DATAPATH_CTL2 0x5A +#define UB960_RR_SER_ID 0x5B +#define UB960_RR_SER_ALIAS_ID 0x5C + +/* For these two register sets: n < UB960_MAX_PORT_ALIASES */ +#define UB960_RR_SLAVE_ID(n) (0x5D + (n)) +#define UB960_RR_SLAVE_ALIAS(n) (0x65 + (n)) + +#define UB960_RR_PORT_CONFIG 0x6D +#define UB960_RR_BC_GPIO_CTL(n) (0x6E + (n)) /* n < 2 */ +#define UB960_RR_RAW10_ID 0x70 +#define UB960_RR_RAW12_ID 0x71 +#define UB960_RR_CSI_VC_MAP 0x72 +#define UB960_RR_LINE_COUNT_HI 0x73 +#define UB960_RR_LINE_COUNT_LO 0x74 +#define UB960_RR_LINE_LEN_1 0x75 +#define UB960_RR_LINE_LEN_0 0x76 +#define UB960_RR_FREQ_DET_CTL 0x77 +#define UB960_RR_MAILBOX_1 0x78 +#define UB960_RR_MAILBOX_2 0x79 + +#define UB960_RR_CSI_RX_STS 0x7A +#define UB960_RR_CSI_RX_STS_LENGTH_ERR BIT(3) +#define UB960_RR_CSI_RX_STS_CKSUM_ERR BIT(2) +#define UB960_RR_CSI_RX_STS_ECC2_ERR BIT(1) +#define UB960_RR_CSI_RX_STS_ECC1_ERR BIT(0) + +#define UB960_RR_CSI_ERR_COUNTER 0x7B +#define UB960_RR_PORT_CONFIG2 0x7C +#define UB960_RR_PORT_CONFIG2_LV_POL_LOW BIT(1) +#define UB960_RR_PORT_CONFIG2_FV_POL_LOW BIT(0) + +#define UB960_RR_PORT_PASS_CTL 0x7D +#define UB960_RR_SEN_INT_RISE_CTL 0x7E +#define UB960_RR_SEN_INT_FALL_CTL 0x7F + +#define UB960_SR_CSI_FRAME_COUNT_HI(n) (0x90 + 8 * (n)) +#define UB960_SR_CSI_FRAME_COUNT_LO(n) (0x91 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_HI(n) (0x92 + 8 * (n)) +#define UB960_SR_CSI_FRAME_ERR_COUNT_LO(n) (0x93 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_HI(n) (0x94 + 8 * (n)) +#define UB960_SR_CSI_LINE_COUNT_LO(n) (0x95 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_HI(n) (0x96 + 8 * (n)) +#define UB960_SR_CSI_LINE_ERR_COUNT_LO(n) (0x97 + 8 * (n)) + +#define UB960_XR_REFCLK_FREQ 0xA5 /* UB960 */ + +#define UB960_RR_VC_ID_MAP(x) (0xa0 + (x)) /* UB9702 */ + +#define UB960_SR_IND_ACC_CTL 0xB0 +#define UB960_SR_IND_ACC_CTL_IA_AUTO_INC BIT(1) + +#define UB960_SR_IND_ACC_ADDR 0xB1 +#define UB960_SR_IND_ACC_DATA 0xB2 +#define UB960_SR_BIST_CONTROL 0xB3 +#define UB960_SR_MODE_IDX_STS 0xB8 +#define UB960_SR_LINK_ERROR_COUNT 0xB9 +#define UB960_SR_FPD3_ENC_CTL 0xBA +#define UB960_SR_FV_MIN_TIME 0xBC +#define UB960_SR_GPIO_PD_CTL 0xBE + +#define UB960_SR_FPD_RATE_CFG 0xc2 /* UB9702 */ +#define UB960_SR_CSI_PLL_DIV 0xc9 /* UB9702 */ + +#define UB960_RR_PORT_DEBUG 0xD0 +#define UB960_RR_AEQ_CTL2 0xD2 +#define UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR BIT(2) + +#define UB960_RR_AEQ_STATUS 0xD3 + +#define UB960_RR_AEQ_BYPASS 0xD4 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT 5 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK GENMASK(7, 5) +#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT 1 +#define UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK GENMASK(3, 1) +#define UB960_RR_AEQ_BYPASS_ENABLE BIT(0) + +#define UB960_RR_AEQ_MIN_MAX 0xD5 +#define UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT 4 +#define UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT 0 + +#define UB960_RR_SFILTER_STS_0 0xD6 +#define UB960_RR_SFILTER_STS_1 0xD7 +#define UB960_RR_PORT_ICR_HI 0xD8 +#define UB960_RR_PORT_ICR_LO 0xD9 +#define UB960_RR_PORT_ISR_HI 0xDA +#define UB960_RR_PORT_ISR_LO 0xDB +#define UB960_RR_FC_GPIO_STS 0xDC +#define UB960_RR_FC_GPIO_ICR 0xDD +#define UB960_RR_SEN_INT_RISE_STS 0xDE +#define UB960_RR_SEN_INT_FALL_STS 0xDF + +#define UB960_RR_CHANNEL_MODE 0xe4 /* UB9702 */ + +#define UB960_SR_FPD3_RX_ID(n) (0xF0 + (n)) + +#define UB960_SR_I2C_RX_ID(n) (0xF8 + (n)) /* < UB960_FPD_RX_NPORTS */ + +/* Indirect register blocks */ +#define UB960_IND_TARGET_PAT_GEN 0x00 +#define UB960_IND_TARGET_RX_ANA(n) (0x01 + (n)) +#define UB960_IND_TARGET_CSI_CSIPLL_REG_1 0x92 /* UB9702 */ +#define UB960_IND_TARGET_CSI_ANA 0x07 + +/* UB960_IR_PGEN_*: Indirect Registers for Test Pattern Generator */ + +#define UB960_IR_PGEN_CTL 0x01 +#define UB960_IR_PGEN_CTL_PGEN_ENABLE BIT(0) + +#define UB960_IR_PGEN_CFG 0x02 +#define UB960_IR_PGEN_CSI_DI 0x03 +#define UB960_IR_PGEN_LINE_SIZE1 0x04 +#define UB960_IR_PGEN_LINE_SIZE0 0x05 +#define UB960_IR_PGEN_BAR_SIZE1 0x06 +#define UB960_IR_PGEN_BAR_SIZE0 0x07 +#define UB960_IR_PGEN_ACT_LPF1 0x08 +#define UB960_IR_PGEN_ACT_LPF0 0x09 +#define UB960_IR_PGEN_TOT_LPF1 0x0A +#define UB960_IR_PGEN_TOT_LPF0 0x0B +#define UB960_IR_PGEN_LINE_PD1 0x0C +#define UB960_IR_PGEN_LINE_PD0 0x0D +#define UB960_IR_PGEN_VBP 0x0E +#define UB960_IR_PGEN_VFP 0x0F +#define UB960_IR_PGEN_COLOR(n) (0x10 + (n)) /* n < 15 */ + +#define UB960_IR_RX_ANA_STROBE_SET_CLK 0x08 +#define UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY BIT(3) +#define UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK GENMASK(2, 0) + +#define UB960_IR_RX_ANA_STROBE_SET_DATA 0x09 +#define UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY BIT(3) +#define UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK GENMASK(2, 0) + +/* EQ related */ + +#define UB960_MIN_AEQ_STROBE_POS -7 +#define UB960_MAX_AEQ_STROBE_POS 7 + +#define UB960_MIN_MANUAL_STROBE_POS -(7 + 6) +#define UB960_MAX_MANUAL_STROBE_POS (7 + 6) +#define UB960_NUM_MANUAL_STROBE_POS (UB960_MAX_MANUAL_STROBE_POS - UB960_MIN_MANUAL_STROBE_POS + 1) + +#define UB960_MIN_EQ_LEVEL 0 +#define UB960_MAX_EQ_LEVEL 14 +#define UB960_NUM_EQ_LEVELS (UB960_MAX_EQ_LEVEL - UB960_MIN_EQ_LEVEL + 1) + +struct ub960_hw_data { + const char *model; + u8 num_rxports; + u8 num_txports; + bool ub9702; + bool fpdlink4; +}; + +enum ub960_rxport_mode { + RXPORT_MODE_RAW10 = 0, + RXPORT_MODE_RAW12_HF = 1, + RXPORT_MODE_RAW12_LF = 2, + RXPORT_MODE_CSI2_SYNC = 3, + RXPORT_MODE_CSI2_ASYNC = 4, + RXPORT_MODE_LAST = RXPORT_MODE_CSI2_ASYNC, +}; + +enum ub960_rxport_cdr { + RXPORT_CDR_FPD3 = 0, + RXPORT_CDR_FPD4 = 1, + RXPORT_CDR_LAST = RXPORT_CDR_FPD4, +}; + +struct ub960_rxport { + struct ub960_data *priv; + u8 nport; /* RX port number, and index in priv->rxport[] */ + + struct v4l2_subdev *sd; /* Connected subdev */ + struct fwnode_handle *fwnode; + + enum ub960_rxport_mode rx_mode; + enum ub960_rxport_cdr cdr_mode; + + struct device_node *remote_of_node; /* 'serializer' OF node */ + struct i2c_client *ser_client; /* Serializer */ + unsigned short ser_alias; /* Serializer i2c alias (lower 7 bits) */ + + u8 lv_fv_pol; /* LV and FV polarities */ + + struct regulator *vpoc; + + /* EQ settings */ + struct { + bool manual_eq; + + s8 strobe_pos; + + union { + struct { + u8 eq_level_min; + u8 eq_level_max; + } aeq; + + struct { + u8 eq_level; + } manual; + }; + } eq; + + struct ds90ub9xx_platform_data ser_platform_data; +}; + +struct ub960_asd { + struct v4l2_async_subdev base; + struct ub960_rxport *rxport; +}; + +static inline struct ub960_asd *to_ub960_asd(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct ub960_asd, base); +} + +struct ub960_txport { + struct ub960_data *priv; + u8 nport; /* TX port number, and index in priv->txport[] */ + + u32 num_data_lanes; +}; + +struct atr_alias_table_entry { + u16 alias_id; /* Alias ID from DT */ + + bool reserved; + u8 nport; + u8 slave_id; /* i2c client's local i2c address */ + u8 port_reg_idx; +}; + +struct atr_alias_table { + /* Protects fields in this struct */ + struct mutex lock; + + size_t num_entries; + struct atr_alias_table_entry *entries; +}; + +struct ub960_data { + const struct ub960_hw_data *hw_data; + struct i2c_client *client; /* for shared local registers */ + struct regmap *regmap; + + /* lock for register access */ + struct mutex reg_lock; + + struct clk *refclk; + + struct regulator *vddio; + + struct gpio_desc *pd_gpio; + struct delayed_work poll_work; + struct i2c_atr *atr; + struct ub960_rxport *rxports[UB960_MAX_RX_NPORTS]; + struct ub960_txport *txports[UB960_MAX_TX_NPORTS]; + + struct v4l2_subdev sd; + struct media_pad pads[UB960_MAX_NPORTS]; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_async_notifier notifier; + + u32 tx_data_rate; /* Nominal data rate (Gb/s) */ + s64 tx_link_freq[1]; + + struct atr_alias_table atr_alias_table; + + u8 current_read_rxport; + u8 current_write_rxport_mask; + + u8 current_read_csiport; + u8 current_write_csiport_mask; + + u8 current_indirect_target; + + bool streaming; + + u8 stored_fwd_ctl; + + u64 stream_enable_mask[UB960_MAX_NPORTS]; + + /* These are common to all ports */ + struct { + bool manual; + + s8 min; + s8 max; + } strobe; +}; + +static void ub960_reset(struct ub960_data *priv, bool reset_regs); + +static inline struct ub960_data *sd_to_ub960(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub960_data, sd); +} + +enum { + TEST_PATTERN_DISABLED = 0, + TEST_PATTERN_V_COLOR_BARS_1, + TEST_PATTERN_V_COLOR_BARS_2, + TEST_PATTERN_V_COLOR_BARS_4, + TEST_PATTERN_V_COLOR_BARS_8, +}; + +static const char * const ub960_tpg_qmenu[] = { + "Disabled", + "1 vertical color bar", + "2 vertical color bars", + "4 vertical color bars", + "8 vertical color bars", +}; + +static inline bool ub960_pad_is_sink(struct ub960_data *priv, u32 pad) +{ + return pad < priv->hw_data->num_rxports; +} + +static inline bool ub960_pad_is_source(struct ub960_data *priv, u32 pad) +{ + return pad >= priv->hw_data->num_rxports && + pad < (priv->hw_data->num_rxports + priv->hw_data->num_txports); +} + +static inline unsigned int ub960_pad_to_port(struct ub960_data *priv, u32 pad) +{ + if (ub960_pad_is_sink(priv, pad)) + return pad; + else + return pad - priv->hw_data->num_rxports; +} + +struct ub960_format_info { + u32 code; + u32 bpp; + u8 datatype; + bool meta; +}; + +static const struct ub960_format_info ub960_formats[] = { + { .code = MEDIA_BUS_FMT_YUYV8_1X16, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_UYVY8_1X16, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_VYUY8_1X16, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_YVYU8_1X16, .bpp = 16, .datatype = 0x1e, }, + + /* Legacy */ + { .code = MEDIA_BUS_FMT_YUYV8_2X8, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_UYVY8_2X8, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_VYUY8_2X8, .bpp = 16, .datatype = 0x1e, }, + { .code = MEDIA_BUS_FMT_YVYU8_2X8, .bpp = 16, .datatype = 0x1e, }, + + /* RAW */ + { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, .datatype = 0x2c, }, + { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, .datatype = 0x2c, }, +}; + +static const struct ub960_format_info *ub960_find_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ub960_formats); ++i) { + if (ub960_formats[i].code == code) + return &ub960_formats[i]; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Basic device access + */ + +static int ub960_read(struct ub960_data *priv, u8 reg, u8 *val) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + else + *val = v; + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write(struct ub960_data *priv, u8 reg, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_update_bits(struct ub960_data *priv, u8 reg, u8 mask, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub960_rxport_select(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_read_rxport == nport && + priv->current_write_rxport_mask == BIT(nport)) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_FPD3_PORT_SEL, + (nport << 4) | (1 << nport)); + if (ret) { + dev_err(dev, "%s: cannot select rxport %d (%d)!\n", __func__, + nport, ret); + return ret; + } + + priv->current_read_rxport = nport; + priv->current_write_rxport_mask = BIT(nport); + + return 0; +} + +static int ub960_rxport_read(struct ub960_data *priv, u8 nport, u8 reg, u8 *val) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + else + *val = v; + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_rxport_write(struct ub960_data *priv, u8 nport, u8 reg, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_rxport_update_bits(struct ub960_data *priv, u8 nport, u8 reg, + u8 mask, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_rxport_select(priv, nport); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub960_csiport_select(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_read_csiport == nport && + priv->current_write_csiport_mask == BIT(nport)) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_CSI_PORT_SEL, + (nport << 4) | (1 << nport)); + if (ret) { + dev_err(dev, "%s: cannot select csi port %d (%d)!\n", __func__, + nport, ret); + return ret; + } + + priv->current_read_csiport = nport; + priv->current_write_csiport_mask = BIT(nport); + + return 0; +} + +static int ub960_csiport_read(struct ub960_data *priv, u8 nport, u8 reg, + u8 *val) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(dev, "%s: cannot read register 0x%02x (%d)!\n", + __func__, reg, ret); + else + *val = v; + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_csiport_write(struct ub960_data *priv, u8 nport, u8 reg, + u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(dev, "%s: cannot write register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_csiport_update_bits(struct ub960_data *priv, u8 nport, u8 reg, + u8 mask, u8 val) +{ + struct device *dev = &priv->client->dev; + int ret; + + mutex_lock(&priv->reg_lock); + + _ub960_csiport_select(priv, nport); + + ret = regmap_update_bits(priv->regmap, reg, mask, val); + + if (ret) + dev_err(dev, "%s: cannot update register 0x%02x (%d)!\n", + __func__, reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub960_select_ind_reg_block(struct ub960_data *priv, u8 block) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_indirect_target == block) + return 0; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_CTL, block << 2); + if (ret) { + dev_err(dev, "%s: cannot select indirect target %u (%d)!\n", + __func__, block, ret); + return ret; + } + + priv->current_indirect_target = block; + + return 0; +} + +static int ub960_read_ind(struct ub960_data *priv, u8 block, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v); + if (ret) + goto out; + + *val = v; + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write_ind(struct ub960_data *priv, u8 block, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val); + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_write_ind16(struct ub960_data *priv, u8 block, u8 reg, u16 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val >> 8); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg + 1); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, val & 0xff); + if (ret) + goto out; + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub960_ind_update_bits(struct ub960_data *priv, u8 block, u8 reg, + u8 mask, u8 val) +{ + int ret; + u32 v; + + mutex_lock(&priv->reg_lock); + + ret = _ub960_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, UB960_SR_IND_ACC_DATA, &v); + if (ret) + goto out; + + v &= ~mask; + v |= val; + + ret = regmap_write(priv->regmap, UB960_SR_IND_ACC_DATA, v); + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * I2C-ATR (address translator) + */ + +static int ub960_atr_attach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_board_info *info, + const struct i2c_client *client, + u16 *alias_id) +{ + struct ub960_data *priv = i2c_atr_get_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%d: %s\n", chan_id, __func__); + + mutex_lock(&priv->atr_alias_table.lock); + + /* + * Go through the alias table and: + * 1. Look for an unreserved entry + * 2. Construct a bitmask of port's used alias entries + */ + + for (pool_idx = 0; pool_idx < priv->atr_alias_table.num_entries; pool_idx++) { + struct atr_alias_table_entry *e; + + e = &priv->atr_alias_table.entries[pool_idx]; + + if (!entry && !e->reserved) + entry = e; + + if (e->reserved && e->nport == rxport->nport) + port_reg_idx_mask |= BIT(e->port_reg_idx); + } + + if (!entry) { + dev_err(dev, "rx%d: alias pool exhausted\n", rxport->nport); + ret = -EADDRNOTAVAIL; + goto out; + } + + if (port_reg_idx_mask == (1 << UB960_MAX_PORT_ALIASES) - 1) { + dev_err(dev, "rx%d: all aliases in use\n", rxport->nport); + ret = -EADDRNOTAVAIL; + goto out; + } + + alias = entry->alias_id; + + reg_idx = ffz(port_reg_idx_mask); + + entry->reserved = true; + entry->nport = rxport->nport; + entry->slave_id = client->addr; + entry->port_reg_idx = reg_idx; + + /* Map alias to slave */ + + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ID(reg_idx), + client->addr << 1); + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), + alias << 1); + + *alias_id = alias; /* tell the atr which alias we chose */ + + dev_dbg(dev, "rx%d: client 0x%02x mapped at alias 0x%02x (%s)\n", + rxport->nport, client->addr, alias, client->name); + +out: + mutex_unlock(&priv->atr_alias_table.lock); + return ret; +} + +static void ub960_atr_detach_client(struct i2c_atr *atr, u32 chan_id, + const struct i2c_client *client) +{ + struct ub960_data *priv = i2c_atr_get_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%d: %s\n", chan_id, __func__); + + mutex_lock(&priv->atr_alias_table.lock); + + /* Find alias mapped to this client */ + + for (pool_idx = 0; pool_idx < priv->atr_alias_table.num_entries; pool_idx++) { + entry = &priv->atr_alias_table.entries[pool_idx]; + + if (entry->reserved && entry->nport == rxport->nport && + entry->slave_id == client->addr) + break; + } + + if (pool_idx == priv->atr_alias_table.num_entries) { + dev_err(dev, "rx%d: client 0x%02x is not mapped!\n", + rxport->nport, client->addr); + goto out; + } + + alias = entry->alias_id; + + reg_idx = entry->port_reg_idx; + + /* Unmap */ + + ub960_rxport_write(priv, chan_id, UB960_RR_SLAVE_ALIAS(reg_idx), 0); + + entry->reserved = false; + + dev_dbg(dev, "rx%d: client 0x%02x unmapped from alias 0x%02x (%s)\n", + rxport->nport, client->addr, alias, client->name); + +out: + mutex_unlock(&priv->atr_alias_table.lock); +} + +static const struct i2c_atr_ops ub960_atr_ops = { + .attach_client = ub960_atr_attach_client, + .detach_client = ub960_atr_detach_client, +}; + +/* ----------------------------------------------------------------------------- + * CSI ports + */ + +static int ub960_parse_dt_txport(struct ub960_data *priv, + const struct device_node *np, u8 nport) +{ + struct device *dev = &priv->client->dev; + struct ub960_txport *txport; + int ret; + u64 freq; + + if (priv->txports[nport]) { + dev_err(dev, "OF: %s: duplicate tx port\n", + of_node_full_name(np)); + return -EADDRINUSE; + } + + txport = kzalloc(sizeof(*txport), GFP_KERNEL); + if (!txport) + return -ENOMEM; + + txport->priv = priv; + txport->nport = nport; + + priv->txports[nport] = txport; + + ret = of_property_count_u32_elems(np, "data-lanes"); + + if (ret <= 0) { + dev_err(dev, "OF: %s: failed to parse data-lanes: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + txport->num_data_lanes = ret; + + ret = of_property_count_u64_elems(np, "link-frequencies"); + if (ret < 0) { + dev_err(dev, "OF: %s: failed to parse link-frequencies: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + if (ret != 1) { + dev_err(dev, "OF: %s: link-frequencies must contain a single frequency: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + ret = of_property_read_u64(np, "link-frequencies", &freq); + if (ret) { + dev_err(dev, "OF: %s: failed to read link frequency: %d\n", + of_node_full_name(np), ret); + goto err_free_txport; + } + + if (of_property_read_u32(np, "link-frequencies", &priv->tx_data_rate) != 0) { + dev_err(dev, "OF: %s: missing \"data-rate\" node\n", + of_node_full_name(np)); + return -EINVAL; + } + + priv->tx_link_freq[0] = freq; + priv->tx_data_rate = freq * 2; + + if (priv->tx_data_rate != 1600000000 && + priv->tx_data_rate != 1200000000 && + priv->tx_data_rate != 800000000 && + priv->tx_data_rate != 400000000) { + dev_err(dev, "OF: %s: invalid \"link-frequencies\" value\n", + of_node_full_name(np)); + return -EINVAL; + } + + dev_dbg(dev, "Nominal data rate: %u", priv->tx_data_rate); + + return 0; + +err_free_txport: + kfree(txport); + + return ret; +} + +static void ub960_csi_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + u8 csi_tx_isr; + int ret; + + ret = ub960_csiport_read(priv, nport, UB960_TR_CSI_TX_ISR, &csi_tx_isr); + + if (!ret) { + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_SYNC_ERROR) + dev_warn(dev, "TX%u: CSI_SYNC_ERROR\n", nport); + + if (csi_tx_isr & UB960_TR_CSI_TX_ISR_IS_CSI_PASS_ERROR) + dev_warn(dev, "TX%u: CSI_PASS_ERROR\n", nport); + } +} + +/* ----------------------------------------------------------------------------- + * RX ports + */ + +static int ub960_rxport_enable_vpocs(struct ub960_data *priv) +{ + unsigned int nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport || !rxport->vpoc) + continue; + + ret = regulator_enable(rxport->vpoc); + if (ret) + goto err_disable_vpocs; + } + + return 0; + +err_disable_vpocs: + for (; nport > 0; --nport) { + struct ub960_rxport *rxport = priv->rxports[nport - 1]; + + if (!rxport || !rxport->vpoc) + continue; + + regulator_disable(rxport->vpoc); + } + + return ret; +} + +static void ub960_rxport_disable_vpocs(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport || !rxport->vpoc) + continue; + + regulator_disable(rxport->vpoc); + } +} + +static void ub960_rxport_clear_errors(struct ub960_data *priv, + unsigned int nport) +{ + u8 v; + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v); + ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &v); + ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &v); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v); +} + +static void ub960_clear_rx_errors(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) + ub960_rxport_clear_errors(priv, nport); +} + +static int ub960_rxport_get_strobe_pos(struct ub960_data *priv, + unsigned int nport, s8 *strobe_pos) +{ + u8 v; + u8 clk_delay, data_delay; + int ret; + + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, &v); + + clk_delay = v & UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY ? 0 : 6; + + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, &v); + + data_delay = v & UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY ? 0 : 6; + + ret = ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_0, &v); + if (ret) + return ret; + + clk_delay += v & UB960_IR_RX_ANA_STROBE_SET_CLK_DELAY_MASK; + + ub960_rxport_read(priv, nport, UB960_RR_SFILTER_STS_1, &v); + if (ret) + return ret; + + data_delay += v & UB960_IR_RX_ANA_STROBE_SET_DATA_DELAY_MASK; + + *strobe_pos = data_delay - clk_delay; + + return 0; +} + +static void ub960_rxport_set_strobe_pos(struct ub960_data *priv, + unsigned int nport, s8 strobe_pos) +{ + u8 clk_delay, data_delay; + + if (WARN_ON(strobe_pos < UB960_MIN_MANUAL_STROBE_POS || + strobe_pos > UB960_MAX_MANUAL_STROBE_POS)) + return; + + clk_delay = UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + data_delay = UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + if (strobe_pos < -7) + clk_delay = abs(strobe_pos) - 6; + else if (strobe_pos > 7) + data_delay = strobe_pos - 6; + else if (strobe_pos < 0) + clk_delay = abs(strobe_pos) | UB960_IR_RX_ANA_STROBE_SET_CLK_NO_EXTRA_DELAY; + else if (strobe_pos > 0) + data_delay = strobe_pos | UB960_IR_RX_ANA_STROBE_SET_DATA_NO_EXTRA_DELAY; + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_CLK, clk_delay); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), + UB960_IR_RX_ANA_STROBE_SET_DATA, data_delay); +} + +static void ub960_rxport_set_strobe_range(struct ub960_data *priv, + s8 strobe_min, s8 strobe_max) +{ + WARN_ON(strobe_min < UB960_MIN_AEQ_STROBE_POS); + WARN_ON(strobe_max > UB960_MAX_AEQ_STROBE_POS); + + strobe_min += 7; + strobe_max += 7; + + ub960_write(priv, UB960_XR_SFILTER_CFG, + ((u8)strobe_min << UB960_XR_SFILTER_CFG_SFILTER_MIN_SHIFT) | + ((u8)strobe_max << UB960_XR_SFILTER_CFG_SFILTER_MAX_SHIFT)); +} + +static int ub960_rxport_get_eq_level(struct ub960_data *priv, + unsigned int nport, u8 *eq_level) +{ + int ret; + u8 v; + + ret = ub960_rxport_read(priv, nport, UB960_RR_AEQ_STATUS, &v); + if (ret) + return ret; + + *eq_level = (v & 0x7) + ((v >> 3) & 0x7); + + return 0; +} + +static void ub960_rxport_set_eq_level(struct ub960_data *priv, + unsigned int nport, u8 eq_level) +{ + u8 eq_stage_1_select_value, eq_stage_2_select_value; + u8 v; + + if (eq_level <= 7) { + eq_stage_1_select_value = eq_level; + eq_stage_2_select_value = 0; + } else { + eq_stage_1_select_value = 7; + eq_stage_2_select_value = eq_level - 7; + } + + WARN_ON(eq_stage_1_select_value > 7); + WARN_ON(eq_stage_2_select_value > 7); + + ub960_rxport_read(priv, nport, UB960_RR_AEQ_BYPASS, &v); + + v &= ~(UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_MASK | + UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_MASK); + v |= eq_stage_1_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE1_VALUE_SHIFT; + v |= eq_stage_2_select_value << UB960_RR_AEQ_BYPASS_EQ_STAGE2_VALUE_SHIFT; + v |= UB960_RR_AEQ_BYPASS_ENABLE; /* Enable AEQ Bypass */ + + ub960_rxport_write(priv, nport, UB960_RR_AEQ_BYPASS, v); +} + +static void ub960_rxport_set_eq_range(struct ub960_data *priv, + unsigned int nport, u8 eq_min, u8 eq_max) +{ + ub960_rxport_write(priv, nport, UB960_RR_AEQ_MIN_MAX, + (eq_min << UB960_RR_AEQ_MIN_MAX_AEQ_FLOOR_SHIFT) | + (eq_max << UB960_RR_AEQ_MIN_MAX_AEQ_MAX_SHIFT)); + + /* Enable AEQ min setting */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_CTL2, + UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR, + UB960_RR_AEQ_CTL2_SET_AEQ_FLOOR); +} + +static void ub960_rxport_config_eq(struct ub960_data *priv, unsigned int nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + + /* We also set common settings here. Should be moved elsewhere. */ + + if (priv->strobe.manual) { + /* Disable AEQ_SFILTER_EN */ + ub960_update_bits(priv, UB960_XR_AEQ_CTL1, + UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN, 0); + } else { + /* Enable SFILTER and error control */ + ub960_write(priv, UB960_XR_AEQ_CTL1, + (0x7 << UB960_XR_AEQ_CTL1_AEQ_ERR_CTL_SHIFT) | + UB960_XR_AEQ_CTL1_AEQ_SFILTER_EN); + + /* Set AEQ strobe range */ + ub960_rxport_set_strobe_range(priv, priv->strobe.min, + priv->strobe.max); + } + + /* The rest are port specific */ + + if (priv->strobe.manual) + ub960_rxport_set_strobe_pos(priv, nport, rxport->eq.strobe_pos); + else + ub960_rxport_set_strobe_pos(priv, nport, 0); + + if (rxport->eq.manual_eq) { + ub960_rxport_set_eq_level(priv, nport, + rxport->eq.manual.eq_level); + + /* Enable AEQ Bypass */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + UB960_RR_AEQ_BYPASS_ENABLE, + UB960_RR_AEQ_BYPASS_ENABLE); + } else { + ub960_rxport_set_eq_range(priv, nport, + rxport->eq.aeq.eq_level_min, + rxport->eq.aeq.eq_level_max); + + /* Disable AEQ Bypass */ + ub960_rxport_update_bits(priv, nport, UB960_RR_AEQ_BYPASS, + UB960_RR_AEQ_BYPASS_ENABLE, 0); + } +} + +static int ub960_rxport_link_ok(struct ub960_data *priv, unsigned int nport, + bool *ok) +{ + u8 rx_port_sts1, rx_port_sts2; + unsigned int parity_errors; + u8 csi_rx_sts; + u8 csi_err_cnt; + u8 v1, v2; + u8 bcc_sts; + int ret; + bool errors; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &rx_port_sts1); + if (ret) + return ret; + + if (!(rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS)) { + *ok = false; + return 0; + } + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, &csi_rx_sts); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, + &csi_err_cnt); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, &bcc_sts); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + if (ret) + return ret; + + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + if (ret) + return ret; + + parity_errors = (v1 << 8) | v2; + + errors = (rx_port_sts1 & 0x2c) || (rx_port_sts2 & 0x20) || + (bcc_sts & 0x3f) || (csi_rx_sts & 0xf) || csi_err_cnt || + parity_errors; + + *ok = !errors; + + return 0; +} + +/* + * Wait for the RX ports to lock, have no errors and have stable strobe position + * and EQ level. + */ +static int ub960_rxport_wait_locks(struct ub960_data *priv, + unsigned long port_mask, + unsigned int *lock_mask) +{ + struct device *dev = &priv->client->dev; + unsigned long timeout; + unsigned int link_ok_mask; + unsigned int missing; + unsigned int loops; + u8 nport; + int ret; + + if (port_mask == 0) + return 0; + + if (port_mask >= BIT(priv->hw_data->num_rxports)) + return -EINVAL; + + timeout = jiffies + msecs_to_jiffies(1000); + loops = 0; + link_ok_mask = 0; + + while (time_before(jiffies, timeout)) { + missing = 0; + + for_each_set_bit(nport, &port_mask, + priv->hw_data->num_rxports) { + struct ub960_rxport *rxport = priv->rxports[nport]; + bool ok; + + if (!rxport) + continue; + + ret = ub960_rxport_link_ok(priv, nport, &ok); + if (ret) + return ret; + + if (!ok || !(link_ok_mask & BIT(nport))) + missing++; + + if (ok) + link_ok_mask |= BIT(nport); + else + link_ok_mask &= ~BIT(nport); + } + + loops++; + + if (missing == 0) + break; + + msleep(50); + } + + if (lock_mask) + *lock_mask = link_ok_mask; + + dev_dbg(dev, "Wait locks done in %u loops\n", loops); + for_each_set_bit(nport, &port_mask, priv->hw_data->num_rxports) { + struct ub960_rxport *rxport = priv->rxports[nport]; + s8 strobe_pos, eq_level; + u8 v1, v2; + + if (!rxport) + continue; + + if (!(link_ok_mask & BIT(nport))) { + dev_dbg(dev, "\trx%u: not locked\n", nport); + continue; + } + + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2); + + ret = ub960_rxport_get_strobe_pos(priv, nport, &strobe_pos); + if (ret) + return ret; + + ret = ub960_rxport_get_eq_level(priv, nport, &eq_level); + if (ret) + return ret; + + dev_dbg(dev, "\trx%u: locked, SP: %d, EQ: %u, freq %u Hz\n", + nport, strobe_pos, eq_level, + v1 * 1000000 + v2 * 1000000 / 256); + } + + return 0; +} + +static int ub960_init_atr(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + struct i2c_adapter *parent_adap = priv->client->adapter; + + priv->atr = i2c_atr_new(parent_adap, dev, &ub960_atr_ops, + priv->hw_data->num_rxports); + if (IS_ERR(priv->atr)) + return PTR_ERR(priv->atr); + + i2c_atr_set_driver_data(priv->atr, priv); + + return 0; +} + +static void ub960_uninit_atr(struct ub960_data *priv) +{ + i2c_atr_delete(priv->atr); + priv->atr = NULL; +} + +static unsigned long ub960_calc_bc_clk_rate_ub960(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int mult; + unsigned int div; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + mult = 1; + div = 10; + break; + + case RXPORT_MODE_CSI2_SYNC: + mult = 2; + div = 1; + break; + + case RXPORT_MODE_CSI2_ASYNC: + mult = 2; + div = 5; + break; + + default: + WARN_ON(true); + return 0; + } + + return clk_get_rate(priv->refclk) * mult / div; +} + +static unsigned long ub960_calc_bc_clk_rate_ub9702(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + return 2359400; + + case RXPORT_MODE_CSI2_SYNC: + return 47187500; + + case RXPORT_MODE_CSI2_ASYNC: + return 9437500; + + default: + WARN_ON(true); + return 0; + } +} + +static int ub960_rxport_add_serializer(struct ub960_data *priv, u8 nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + struct device *dev = &priv->client->dev; + struct ds90ub9xx_platform_data *ser_pdata = &rxport->ser_platform_data; + struct i2c_board_info ser_info = { + .of_node = rxport->remote_of_node, + .platform_data = ser_pdata, + }; + + ser_pdata->port = nport; + ser_pdata->atr = priv->atr; + if (priv->hw_data->ub9702) + ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub9702(priv, rxport); + else + ser_pdata->bc_rate = ub960_calc_bc_clk_rate_ub960(priv, rxport); + + /* + * Adding the serializer under rxport->adap would be cleaner, but it + * would need tweaks to bypass the alias table. Adding to the + * upstream adapter is way simpler. + */ + ser_info.addr = rxport->ser_alias; + rxport->ser_client = + i2c_new_client_device(priv->client->adapter, &ser_info); + if (!rxport->ser_client) { + dev_err(dev, "rx%d: cannot add %s i2c device", nport, + ser_info.type); + return -EIO; + } + + dev_dbg(dev, "rx%d: remote serializer at alias 0x%02x (%u-%04x)\n", + nport, rxport->ser_client->addr, + rxport->ser_client->adapter->nr, rxport->ser_client->addr); + + return 0; +} + +static void ub960_rxport_remove_serializer(struct ub960_data *priv, u8 nport) +{ + struct ub960_rxport *rxport = priv->rxports[nport]; + + i2c_unregister_device(rxport->ser_client); + rxport->ser_client = NULL; +} + +/* Add serializer i2c devices for all initialized ports */ +static int ub960_rxport_add_serializers(struct ub960_data *priv) +{ + unsigned int nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + ret = ub960_rxport_add_serializer(priv, nport); + if (ret) + goto err_remove_sers; + } + + return 0; + +err_remove_sers: + for (; nport > 0; --nport) { + struct ub960_rxport *rxport = priv->rxports[nport - 1]; + + if (!rxport) + continue; + + rxport = priv->rxports[nport - 1]; + if (!rxport) + continue; + + ub960_rxport_remove_serializer(priv, nport - 1); + } + + return ret; +} + +static void ub960_rxport_remove_serializers(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + ub960_rxport_remove_serializer(priv, nport); + } +} + +static void ub960_init_tx_port(struct ub960_data *priv, + struct ub960_txport *txport) +{ + unsigned int nport = txport->nport; + u8 csi_ctl = 0; + + /* + * From the datasheet: "initial CSI Skew-Calibration + * sequence [...] should be set when operating at 1.6 Gbps" + */ + if (priv->tx_data_rate == 1600000000) + csi_ctl |= UB960_TR_CSI_CTL_CSI_CAL_EN; + + csi_ctl |= (4 - txport->num_data_lanes) << 4; + + ub960_csiport_write(priv, nport, UB960_TR_CSI_CTL, csi_ctl); +} + +static int ub960_init_tx_ports(struct ub960_data *priv) +{ + unsigned int nport; + u8 speed_select; + u8 pll_div; + + /* TX ports */ + + switch (priv->tx_data_rate) { + case 1600000000: + default: + speed_select = 0; + pll_div = 0x10; + break; + case 1200000000: + speed_select = 1; + break; + case 800000000: + speed_select = 2; + pll_div = 0x10; + break; + case 400000000: + speed_select = 3; + pll_div = 0x10; + break; + } + + ub960_write(priv, UB960_SR_CSI_PLL_CTL, speed_select); + + if (priv->hw_data->ub9702) { + ub960_write(priv, UB960_SR_CSI_PLL_DIV, pll_div); + + switch (priv->tx_data_rate) { + case 1600000000: + default: + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0x80); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4B, 0x2A); + break; + case 800000000: + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0x90); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4F, 0x2A); + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x4B, 0x2A); + break; + case 400000000: + ub960_write_ind(priv, UB960_IND_TARGET_CSI_ANA, 0x92, 0xA0); + break; + } + } + + for (nport = 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + if (!txport) + continue; + + ub960_init_tx_port(priv, txport); + } + + return 0; +} + +static void ub960_init_rx_port_ub960(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u32 bc_freq_val; + + /* + * Back channel frequency select. + * Override FREQ_SELECT from the strap. + * 0 - 2.5 Mbps (DS90UB913A-Q1 / DS90UB933-Q1) + * 2 - 10 Mbps + * 6 - 50 Mbps (DS90UB953-Q1) + * + * Note that changing this setting will result in some errors on the back + * channel for a short period of time. + */ + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val = 2; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + break; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + /* FPD3_MODE = RAW10 Mode (DS90UB913A-Q1 / DS90UB933-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3, + 0x3); + + /* + * RAW10_8BIT_CTL = 0b11 : 8-bit processing using lower 8 bits + * 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, + 0x3 << 6, 0x2 << 6); + + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + /* CSI-2 Mode (DS90UB953-Q1 compatible) */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG, 0x3, + 0x0); + + break; + } + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, + rxport->lv_fv_pol); + + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07); + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH); + + /* Enable I2C communication to the serializer via the alias addr */ + ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID, + rxport->ser_alias << 1); + + /* Configure EQ related settings */ + ub960_rxport_config_eq(priv, nport); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport)); +} + +static void ub960_init_rx_port_ub9702_fpd3(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u8 bc_freq_val; + u8 fpd_func_mode; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + bc_freq_val = 0; + fpd_func_mode = 5; + break; + + case RXPORT_MODE_RAW12_HF: + bc_freq_val = 0; + fpd_func_mode = 4; + break; + + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + fpd_func_mode = 6; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + fpd_func_mode = 2; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val = 2; + fpd_func_mode = 2; + break; + + default: + WARN_ON(true); + break; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, fpd_func_mode); + + /* set serdes_eq_mode = 1 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xA8, 0x80); + + /* enable serdes driver */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x0D, 0x7F); + + /* set serdes_eq_offset=4 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04); + + /* init default serdes_eq_max in 0xA9 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xA9, 0x23); + + /* init serdes_eq_min in 0xAA */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xAA, 0); + + /* serdes_driver_ctl2 control: DS90UB953-Q1/DS90UB933-Q1/DS90UB913A-Q1 */ + ub960_ind_update_bits(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1b, + BIT(3), BIT(3)); + + /* RX port to half-rate */ + ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2), + 1 << (nport * 2)); +} + +static void ub960_init_rx_port_ub9702_fpd4_aeq(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + bool first_time_power_up = true; + + if (first_time_power_up) { + u8 v; + + /* AEQ init */ + ub960_read_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2C, &v); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, v); + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, v + 1); + + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x00); + } + + /* enable serdes_eq_ctl2 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x9E, 0x00); + + /* enable serdes_eq_ctl1 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x90, 0x40); + + /* enable serdes_eq_en */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2E, 0x40); + + /* disable serdes_eq_override */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0xF0, 0x00); + + /* disable serdes_gain_override */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x71, 0x00); +} + +static void ub960_init_rx_port_ub9702_fpd4(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + u8 bc_freq_val; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + bc_freq_val = 0; + break; + + case RXPORT_MODE_RAW12_HF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_RAW12_LF: + bc_freq_val = 0; + break; + + case RXPORT_MODE_CSI2_SYNC: + bc_freq_val = 6; + break; + + case RXPORT_MODE_CSI2_ASYNC: + bc_freq_val = 2; + break; + + default: + WARN_ON(true); + break; + } + + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, 0x7, + bc_freq_val); + + /* FPD4 Sync Mode */ + ub960_rxport_write(priv, nport, UB960_RR_CHANNEL_MODE, 0); + + /* add serdes_eq_offset of 4 */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x2B, 0x04); + + /* FPD4 serdes_start_eq in 0x27: assign default */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x27, 0x0); + /* FPD4 serdes_end_eq in 0x28: assign default */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x28, 0x23); + + /* set serdes_driver_mode into FPD IV mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x04, 0x00); + /* set FPD PBC drv into FPD IV mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x1B, 0x00); + + /* set serdes_system_init to 0x2f */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x21, 0x2f); + /* set serdes_system_rst in reset mode */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0xC1); + + /* RX port to 7.55G mode */ + ub960_update_bits(priv, UB960_SR_FPD_RATE_CFG, 0x3 << (nport * 2), + 0 << (nport * 2)); + + ub960_init_rx_port_ub9702_fpd4_aeq(priv, rxport); +} + +static void ub960_init_rx_port_ub9702(struct ub960_data *priv, + struct ub960_rxport *rxport) +{ + unsigned int nport = rxport->nport; + + if (rxport->cdr_mode == RXPORT_CDR_FPD3) + ub960_init_rx_port_ub9702_fpd3(priv, rxport); + else /* RXPORT_CDR_FPD4 */ + ub960_init_rx_port_ub9702_fpd4(priv, rxport); + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + /* + * RAW10_8BIT_CTL = 0b11 : 8-bit processing using lower 8 bits + * 0b10 : 8-bit processing using upper 8 bits + */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, + 0x3 << 6, 0x2 << 6); + + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + + break; + } + + /* LV_POLARITY & FV_POLARITY */ + ub960_rxport_update_bits(priv, nport, UB960_RR_PORT_CONFIG2, 0x3, 0x1); + + /* Enable all interrupt sources from this port */ + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_HI, 0x07); + ub960_rxport_write(priv, nport, UB960_RR_PORT_ICR_LO, 0x7f); + + /* Enable I2C_PASS_THROUGH */ + ub960_rxport_update_bits(priv, nport, UB960_RR_BCC_CONFIG, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH, + UB960_RR_BCC_CONFIG_I2C_PASS_THROUGH); + + /* Enable I2C communication to the serializer via the alias addr */ + ub960_rxport_write(priv, nport, UB960_RR_SER_ALIAS_ID, + rxport->ser_alias << 1); + + /* Enable RX port */ + ub960_update_bits(priv, UB960_SR_RX_PORT_CTL, BIT(nport), BIT(nport)); + + if (rxport->cdr_mode == RXPORT_CDR_FPD4) { + /* unreset 960 AEQ */ + ub960_write_ind(priv, UB960_IND_TARGET_RX_ANA(nport), 0x25, 0x41); + } +} + +static int ub960_init_rx_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; nport++) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + if (priv->hw_data->ub9702) + ub960_init_rx_port_ub9702(priv, rxport); + else + ub960_init_rx_port_ub960(priv, rxport); + } + + return 0; +} + +static void ub960_rxport_handle_events(struct ub960_data *priv, u8 nport) +{ + struct device *dev = &priv->client->dev; + u8 rx_port_sts1; + u8 rx_port_sts2; + u8 csi_rx_sts; + u8 bcc_sts; + int ret = 0; + + /* Read interrupts (also clears most of them) */ + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, + &rx_port_sts1); + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, + &rx_port_sts2); + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_CSI_RX_STS, + &csi_rx_sts); + if (!ret) + ret = ub960_rxport_read(priv, nport, UB960_RR_BCC_STATUS, + &bcc_sts); + + if (ret) + return; + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PARITY_ERROR) { + u8 v1, v2; + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + dev_err(dev, "RX%u parity errors: %u\n", nport, (v1 << 8) | v2); + } + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_CRC_ERROR) + dev_err(dev, "RX%u BCC CRC error\n", nport); + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_BCC_SEQ_ERROR) + dev_err(dev, "RX%u BCC SEQ error\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_UNSTABLE) + dev_err(dev, "RX%u line length unstable\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FPD3_ENCODE_ERROR) + dev_err(dev, "RX%u FPD3 encode error\n", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_BUFFER_ERROR) + dev_err(dev, "RX%u buffer error\n", nport); + + if (csi_rx_sts) + dev_err(dev, "RX%u CSI error: %#02x\n", nport, csi_rx_sts); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC1_ERR) + dev_err(dev, "RX%u CSI ECC1 error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_ECC2_ERR) + dev_err(dev, "RX%u CSI ECC2 error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_CKSUM_ERR) + dev_err(dev, "RX%u CSI checksum error\n", nport); + + if (csi_rx_sts & UB960_RR_CSI_RX_STS_LENGTH_ERR) + dev_err(dev, "RX%u CSI length error\n", nport); + + if (bcc_sts) + dev_err(dev, "RX%u BCC error: %#02x\n", nport, bcc_sts); + + if (bcc_sts & UB960_RR_BCC_STATUS_RESP_ERR) + dev_err(dev, "RX%u BCC response error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_TO) + dev_err(dev, "RX%u BCC slave timeout", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SLAVE_ERR) + dev_err(dev, "RX%u BCC slave error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_TO) + dev_err(dev, "RX%u BCC master timeout", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_MASTER_ERR) + dev_err(dev, "RX%u BCC master error", nport); + + if (bcc_sts & UB960_RR_BCC_STATUS_SEQ_ERROR) + dev_err(dev, "RX%u BCC sequence error", nport); + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_LEN_CHG) { + u8 v1, v2; + + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2); + dev_dbg(dev, "RX%u line len changed: %u\n", nport, + (v1 << 8) | v2); + } + + if (rx_port_sts2 & UB960_RR_RX_PORT_STS2_LINE_CNT_CHG) { + u8 v1, v2; + + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2); + dev_dbg(dev, "RX%u line count changed: %u\n", nport, + (v1 << 8) | v2); + } + + if (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS_CHG) { + dev_dbg(dev, "RX%u: %s, %s, %s, %s\n", nport, + (rx_port_sts1 & UB960_RR_RX_PORT_STS1_LOCK_STS) ? "locked" : "unlocked", + (rx_port_sts1 & UB960_RR_RX_PORT_STS1_PORT_PASS) ? "passed" : "not passed", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_CABLE_FAULT) ? "no clock" : "clock ok", + (rx_port_sts2 & UB960_RR_RX_PORT_STS2_FREQ_STABLE) ? "stable freq" : "unstable freq"); + } +} + +/* ----------------------------------------------------------------------------- + * V4L2 + */ + +static void ub960_get_vc_maps(struct ub960_data *priv, + struct v4l2_subdev_state *state, u8 *vc) +{ + const struct v4l2_subdev_krouting *routing = &state->routing; + u8 cur_vc[UB960_MAX_TX_NPORTS] = { 0 }; + u8 handled_mask = 0; + unsigned int i; + + for (i = 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route = &routing->routes[i]; + unsigned int rx, tx; + + rx = ub960_pad_to_port(priv, route->sink_pad); + + if (BIT(rx) & handled_mask) + continue; + + tx = ub960_pad_to_port(priv, route->source_pad); + + vc[rx] = cur_vc[tx]++; + handled_mask |= BIT(rx); + } +} + +static int ub960_enable_tx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "enable TX port %u\n", nport); + + ub960_csiport_update_bits(priv, nport, UB960_TR_CSI_CTL, + UB960_TR_CSI_CTL_CSI_ENABLE, + UB960_TR_CSI_CTL_CSI_ENABLE); + + return 0; +} + +static void ub960_disable_tx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "disable TX port %u\n", nport); + + ub960_csiport_update_bits(priv, nport, UB960_TR_CSI_CTL, + UB960_TR_CSI_CTL_CSI_ENABLE, 0); +} + +static int ub960_enable_rx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "enable RX port %u\n", nport); + + /* Enable forwarding */ + ub960_update_bits(priv, UB960_SR_FWD_CTL1, BIT(4 + nport), 0); + + return 0; +} + +static void ub960_disable_rx_port(struct ub960_data *priv, unsigned int nport) +{ + struct device *dev = &priv->client->dev; + + dev_dbg(dev, "disable RX port %u\n", nport); + + /* Disable forwarding */ + ub960_update_bits(priv, UB960_SR_FWD_CTL1, BIT(4 + nport), + BIT(4 + nport)); +} + +static int ub960_configure_ports_for_streaming(struct ub960_data *priv, + struct v4l2_subdev_state *state) +{ + const struct v4l2_subdev_krouting *routing = &state->routing; + unsigned int i; + unsigned int nport; + u8 fwd_ctl; + struct { + u32 num_streams; + u8 pixel_dt; + u8 meta_dt; + u32 meta_lines; + u32 tx_port; + } rx_data[UB960_MAX_RX_NPORTS] = { 0 }; + u8 vc_map[UB960_MAX_RX_NPORTS] = { 0 }; + + ub960_get_vc_maps(priv, state, vc_map); + + for (i = 0; i < routing->num_routes; ++i) { + struct v4l2_subdev_route *route = &routing->routes[i]; + struct ub960_rxport *rxport; + struct ub960_txport *txport; + struct v4l2_mbus_framefmt *fmt; + const struct ub960_format_info *ub960_fmt; + unsigned int nport; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + rxport = priv->rxports[nport]; + if (!rxport) + return -EINVAL; + + txport = priv->txports[ub960_pad_to_port(priv, route->source_pad)]; + if (!txport) + return -EINVAL; + + rx_data[nport].tx_port = ub960_pad_to_port(priv, route->source_pad); + + rx_data[nport].num_streams++; + + /* For the rest, we are only interested in parallel busses */ + if (rxport->rx_mode == RXPORT_MODE_CSI2_SYNC || + rxport->rx_mode == RXPORT_MODE_CSI2_ASYNC) + continue; + + if (rx_data[nport].num_streams > 2) + return -EPIPE; + + fmt = v4l2_subdev_state_get_stream_format(state, route->sink_pad, + route->sink_stream); + if (!fmt) + return -EPIPE; + + ub960_fmt = ub960_find_format(fmt->code); + if (!ub960_fmt) + return -EPIPE; + + if (ub960_fmt->meta) { + if (fmt->height > 3) { + dev_err(&priv->client->dev, + "Unsupported metadata height %u\n", + fmt->height); + return -EPIPE; + } + + rx_data[nport].meta_dt = ub960_fmt->datatype; + rx_data[nport].meta_lines = fmt->height; + } else { + rx_data[nport].pixel_dt = ub960_fmt->datatype; + } + } + + /* Configure RX ports */ + + fwd_ctl = 0; + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + u8 vc = vc_map[nport]; + + if (rx_data[nport].num_streams == 0) + continue; + + switch (rxport->rx_mode) { + default: + WARN_ON(true); + fallthrough; + + case RXPORT_MODE_RAW10: + ub960_rxport_write(priv, nport, UB960_RR_RAW10_ID, + rx_data[nport].pixel_dt | (vc << 6)); + + ub960_rxport_write(priv, rxport->nport, + UB960_RR_RAW_EMBED_DTYPE, + (rx_data[nport].meta_lines << 6) | rx_data[nport].meta_dt); + + break; + + case RXPORT_MODE_CSI2_SYNC: + case RXPORT_MODE_CSI2_ASYNC: + if (!priv->hw_data->ub9702) { + /* Map all VCs from this port to the same VC */ + ub960_rxport_write(priv, nport, UB960_RR_CSI_VC_MAP, + (vc << 6) | (vc << 4) | + (vc << 2) | (vc << 0)); + } else { + unsigned int i; + + /* Map all VCs from this port to VC(nport) */ + for (i = 0; i < 8; ++i) + ub960_rxport_write(priv, nport, + UB960_RR_VC_ID_MAP(i), nport); + } + + break; + } + + /* Forwarding */ + + fwd_ctl |= BIT(4 + nport); /* forward disable */ + + if (rx_data[nport].tx_port == 1) + fwd_ctl |= BIT(nport); /* forward to TX1 */ + else + fwd_ctl &= ~BIT(nport); /* forward to TX0 */ + } + + ub960_write(priv, UB960_SR_FWD_CTL1, fwd_ctl); + + return 0; +} + +static void ub960_update_streaming_status(struct ub960_data *priv) +{ + unsigned int i; + bool streaming = false; + + for (i = 0; i < UB960_MAX_NPORTS; ++i) { + if (priv->stream_enable_mask[i]) { + streaming = true; + break; + } + } + + priv->streaming = streaming; +} + +static int ub960_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 source_pad, + u64 source_streams_mask) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + const struct v4l2_subdev_krouting *routing; + unsigned int source_stream; + int ret; + u64 sink_streams[UB960_MAX_RX_NPORTS] = { 0 }; + unsigned int nport; + unsigned int failed_port; + + dev_dbg(dev, "Enable streams %u:%#llx\n", source_pad, + source_streams_mask); + + if (priv->stream_enable_mask[source_pad] & source_streams_mask) { + dev_err(dev, + "cannot enable already enabled streams on pad %u mask %#llx\n", + source_pad, source_streams_mask); + return -EBUSY; + } + + routing = &state->routing; + + if (!priv->streaming) { + dev_dbg(dev, "Prepare for streaming\n"); + ret = ub960_configure_ports_for_streaming(priv, state); + if (ret) + return ret; + } + + /* Enable TX port if not yet enabled */ + if (!priv->stream_enable_mask[source_pad]) { + ret = ub960_enable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + if (ret) + return ret; + } + + priv->stream_enable_mask[source_pad] |= source_streams_mask; + + /* Collect sink streams per pad which we need to enable */ + for (source_stream = 0; source_stream < sizeof(source_streams_mask) * 8; + ++source_stream) { + struct v4l2_subdev_route *route; + + if (!(source_streams_mask & BIT_ULL(source_stream))) + continue; + + for_each_active_route(routing, route) { + if (!(route->source_pad == source_pad) || + !(route->source_stream == source_stream)) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |= BIT_ULL(route->sink_stream); + } + } + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct v4l2_subdev *source_sd; + struct media_pad *source_sd_pad; + + if (!sink_streams[nport]) + continue; + + /* Enable the RX port if not yet enabled */ + if (!priv->stream_enable_mask[nport]) { + ret = ub960_enable_rx_port(priv, nport); + if (ret) { + failed_port = nport; + goto err; + } + } + + priv->stream_enable_mask[nport] |= sink_streams[nport]; + + dev_dbg(dev, "Enable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + source_sd_pad = media_pad_remote_pad_first(&priv->pads[nport]); + source_sd = priv->rxports[nport]->sd; + + ret = v4l2_subdev_enable_streams( + source_sd, source_sd_pad->index, sink_streams[nport]); + if (ret) { + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + + failed_port = nport; + goto err; + } + } + + priv->streaming = true; + + return 0; + +err: + for (nport = 0; nport < failed_port; ++nport) { + struct v4l2_subdev *source_sd; + struct media_pad *source_sd_pad; + + if (!sink_streams[nport]) + continue; + + dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + source_sd_pad = media_pad_remote_pad_first(&priv->pads[nport]); + source_sd = priv->rxports[nport]->sd; + + ret = v4l2_subdev_disable_streams( + source_sd, source_sd_pad->index, sink_streams[nport]); + WARN_ON(ret); + + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + /* Disable RX port if no active streams */ + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + } + + priv->stream_enable_mask[source_pad] &= ~source_streams_mask; + + if (!priv->stream_enable_mask[source_pad]) + ub960_disable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + + ub960_update_streaming_status(priv); + + return ret; +} + +static int ub960_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 source_pad, u64 source_streams_mask) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + const struct v4l2_subdev_krouting *routing; + int ret; + unsigned int source_stream; + u64 sink_streams[UB960_MAX_RX_NPORTS] = { 0 }; + unsigned int nport; + + dev_dbg(dev, "Disable streams %u:%#llx\n", source_pad, + source_streams_mask); + + if ((priv->stream_enable_mask[source_pad] & source_streams_mask) != source_streams_mask) { + dev_err(dev, + "cannot disable already disabled streams on pad %u mask %#llx\n", + source_pad, source_streams_mask); + return -EBUSY; + } + + routing = &state->routing; + + /* Collect sink streams per pad which we need to disable */ + for (source_stream = 0; source_stream < sizeof(source_streams_mask) * 8; + ++source_stream) { + struct v4l2_subdev_route *route; + + if (!(source_streams_mask & BIT_ULL(source_stream))) + continue; + + for_each_active_route(routing, route) { + if (!(route->source_pad == source_pad) || + !(route->source_stream == source_stream)) + continue; + + nport = ub960_pad_to_port(priv, route->sink_pad); + + sink_streams[nport] |= BIT_ULL(route->sink_stream); + } + } + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct v4l2_subdev *source_sd; + struct media_pad *source_sd_pad; + + if (!sink_streams[nport]) + continue; + + dev_dbg(dev, "Disable RX port %u streams %#llx\n", nport, + sink_streams[nport]); + + source_sd_pad = media_pad_remote_pad_first(&priv->pads[nport]); + source_sd = priv->rxports[nport]->sd; + + ret = v4l2_subdev_disable_streams( + source_sd, source_sd_pad->index, sink_streams[nport]); + WARN_ON(ret); + + priv->stream_enable_mask[nport] &= ~sink_streams[nport]; + + /* Disable RX port if no active streams */ + if (!priv->stream_enable_mask[nport]) + ub960_disable_rx_port(priv, nport); + } + + /* Disable TX port if no active streams */ + + priv->stream_enable_mask[source_pad] &= ~source_streams_mask; + + if (!priv->stream_enable_mask[source_pad]) + ub960_disable_tx_port(priv, + ub960_pad_to_port(priv, source_pad)); + + ub960_update_streaming_status(priv); + + return 0; +} + +static int ub960_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct ub960_data *priv = sd_to_ub960(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_state *state; + struct v4l2_subdev_route *route; + u64 pad_stream_masks[UB960_MAX_TX_NPORTS] = { 0 }; + unsigned int nport; + int ret = 0; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + routing = &state->routing; + + for_each_active_route(routing, route) + pad_stream_masks[ub960_pad_to_port(priv, route->source_pad)] |= + BIT_ULL(route->source_stream); + + if (enable) { + for (nport = 0; nport < UB960_MAX_TX_NPORTS; ++nport) { + if (pad_stream_masks[nport] == 0) + continue; + + ret = ub960_enable_streams( + sd, state, priv->hw_data->num_rxports + nport, + pad_stream_masks[nport]); + + if (ret) { + for (; nport > 0; --nport) { + if (pad_stream_masks[nport - 1] == 0) + continue; + + ub960_disable_streams( + sd, state, + priv->hw_data->num_rxports + + nport - 1, + pad_stream_masks[nport - 1]); + } + + break; + } + } + } else { + for (nport = 0; nport < UB960_MAX_TX_NPORTS; ++nport) { + if (pad_stream_masks[nport] == 0) + continue; + + ub960_disable_streams( + sd, state, priv->hw_data->num_rxports + nport, + pad_stream_masks[nport]); + } + } + + v4l2_subdev_unlock_state(state); + + return ret; +} + +static const struct v4l2_subdev_video_ops ub960_video_ops = { + .s_stream = ub960_s_stream, +}; + +static int _ub960_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + const struct v4l2_mbus_framefmt format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + /* + * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until + * frame desc is made dynamically allocated. + */ + + if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -E2BIG; + + /* + * TODO: We need a new flag to validate that all streams from a sink pad + * go to a single source pad. + */ + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + ret = v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); + if (ret) + return ret; + + return 0; +} + +static int ub960_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + struct ub960_data *priv = sd_to_ub960(sd); + + if (which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + return _ub960_set_routing(sd, state, routing); +} + +static int ub960_get_source_frame_desc(struct ub960_data *priv, + struct v4l2_mbus_frame_desc *desc, + u8 nport) +{ + struct v4l2_subdev *sd; + struct media_pad *pad; + int ret; + + pad = media_pad_remote_pad_first(&priv->pads[nport]); + if (!pad) + return -EPIPE; + + sd = priv->rxports[nport]->sd; + + ret = v4l2_subdev_call(sd, pad, get_frame_desc, pad->index, desc); + if (ret) + return ret; + + return 0; +} + +static int ub960_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub960_data *priv = sd_to_ub960(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret = 0; + struct device *dev = &priv->client->dev; + u8 vc_map[UB960_MAX_RX_NPORTS] = { 0 }; + + if (!ub960_pad_is_source(priv, pad)) + return -EINVAL; + + state = v4l2_subdev_lock_and_get_active_state(&priv->sd); + + ub960_get_vc_maps(priv, state, vc_map); + + routing = &state->routing; + + memset(fd, 0, sizeof(*fd)); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + for_each_active_route(routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + struct v4l2_mbus_frame_desc source_fd; + unsigned int j; + unsigned int nrxport; + + if (route->source_pad != pad) + continue; + + ret = ub960_get_source_frame_desc(priv, &source_fd, + route->sink_pad); + if (ret) { + dev_err(dev, + "Failed to get source frame desc for port %u\n", + route->sink_pad); + goto out; + } + + for (j = 0; j < source_fd.num_entries; ++j) + if (source_fd.entry[j].stream == route->sink_stream) { + source_entry = &source_fd.entry[j]; + break; + } + + if (!source_entry) { + dev_err(dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_entry->flags; + fd->entry[fd->num_entries].length = source_entry->length; + fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode; + + nrxport = ub960_pad_to_port(priv, route->sink_pad); + + fd->entry[fd->num_entries].bus.csi2.vc = vc_map[nrxport]; + + if (source_fd.type == V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + fd->entry[fd->num_entries].bus.csi2.dt = + source_entry->bus.csi2.dt; + } else { + const struct ub960_format_info *ub960_fmt; + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_state_get_stream_format( + state, pad, route->source_stream); + + if (!fmt) { + ret = -EINVAL; + goto out; + } + + ub960_fmt = ub960_find_format(fmt->code); + if (!ub960_fmt) { + dev_err(dev, "Unable to find format\n"); + ret = -EINVAL; + goto out; + } + + fd->entry[fd->num_entries].bus.csi2.dt = + ub960_fmt->datatype; + } + + fd->num_entries++; + } + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub960_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && priv->streaming) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (ub960_pad_is_source(priv, format->pad)) + return v4l2_subdev_get_fmt(sd, state, format); + + /* TODO: implement fmt validation */ + + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub960_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct ub960_data *priv = sd_to_ub960(sd); + + struct v4l2_subdev_route routes[] = { + { + .sink_pad = 0, + .sink_stream = 0, + .source_pad = priv->hw_data->num_rxports, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub960_set_routing(sd, state, &routing); +} + +static const struct v4l2_subdev_pad_ops ub960_pad_ops = { + .enable_streams = ub960_enable_streams, + .disable_streams = ub960_disable_streams, + + .set_routing = ub960_set_routing, + .get_frame_desc = ub960_get_frame_desc, + + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub960_set_fmt, + + .init_cfg = ub960_init_cfg, +}; + +static int ub960_log_status(struct v4l2_subdev *sd) +{ + struct ub960_data *priv = sd_to_ub960(sd); + struct device *dev = &priv->client->dev; + struct v4l2_subdev_state *state; + unsigned int nport; + u8 v = 0, v1 = 0, v2 = 0; + unsigned int i; + char id[7]; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + for (i = 0; i < 6; ++i) + ub960_read(priv, UB960_SR_FPD3_RX_ID(i), &id[i]); + id[6] = 0; + + dev_info(dev, "ID '%s'\n", id); + + for (nport = 0; nport < priv->hw_data->num_txports; ++nport) { + struct ub960_txport *txport = priv->txports[nport]; + + dev_info(dev, "TX %u\n", nport); + + if (!txport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ub960_csiport_read(priv, nport, UB960_TR_CSI_STS, &v); + dev_info(dev, "\tsync %u, pass %u\n", v & (u8)BIT(1), v & (u8)BIT(0)); + + ub960_read(priv, UB960_SR_CSI_FRAME_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_FRAME_COUNT_LO(nport), &v2); + dev_info(dev, "\tframe counter %u\n", (v1 << 8) | v2); + + ub960_read(priv, UB960_SR_CSI_FRAME_ERR_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_FRAME_ERR_COUNT_LO(nport), &v2); + dev_info(dev, "\tframe error counter %u\n", (v1 << 8) | v2); + + ub960_read(priv, UB960_SR_CSI_LINE_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_LINE_COUNT_LO(nport), &v2); + dev_info(dev, "\tline counter %u\n", (v1 << 8) | v2); + + ub960_read(priv, UB960_SR_CSI_LINE_ERR_COUNT_HI(nport), &v1); + ub960_read(priv, UB960_SR_CSI_LINE_ERR_COUNT_LO(nport), &v2); + dev_info(dev, "\tline error counter %u\n", (v1 << 8) | v2); + } + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct ub960_rxport *rxport = priv->rxports[nport]; + u8 eq_level; + s8 strobe_pos; + unsigned int i; + + dev_info(dev, "RX %u\n", nport); + + if (!rxport) { + dev_info(dev, "\tNot initialized\n"); + continue; + } + + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS1, &v); + + if (v & UB960_RR_RX_PORT_STS1_LOCK_STS) + dev_info(dev, "\tLocked\n"); + else + dev_info(dev, "\tNot locked\n"); + + dev_info(dev, "\trx_port_sts1 %#x\n", v); + ub960_rxport_read(priv, nport, UB960_RR_RX_PORT_STS2, &v); + dev_info(dev, "\trx_port_sts2 %#x\n", v); + + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_HIGH, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_FREQ_LOW, &v2); + dev_info(dev, "\tlink freq %u MHz\n", v1 * 1000000 + v2 * 1000000 / 256); + + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_RX_PAR_ERR_LO, &v2); + dev_info(dev, "\tparity errors %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_HI, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_COUNT_LO, &v2); + dev_info(dev, "\tlines per frame %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_1, &v1); + ub960_rxport_read(priv, nport, UB960_RR_LINE_LEN_0, &v2); + dev_info(dev, "\tbytes per line %u\n", (v1 << 8) | v2); + + ub960_rxport_read(priv, nport, UB960_RR_CSI_ERR_COUNTER, &v); + dev_info(dev, "\tcsi_err_counter %u\n", v); + + /* Strobe */ + + ub960_read(priv, UB960_XR_AEQ_CTL1, &v); + + dev_info(dev, "\t%s strobe\n", + (v & 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); + } + + ub960_rxport_get_eq_level(priv, nport, &eq_level); + + dev_info(dev, "\tEQ level %u\n", eq_level); + + /* GPIOs */ + for (i = 0; i < UB960_NUM_BC_GPIOS; ++i) { + u8 ctl_reg; + u8 ctl_shift; + + ctl_reg = UB960_RR_BC_GPIO_CTL(i / 2); + ctl_shift = (i % 2) * 4; + + ub960_rxport_read(priv, nport, ctl_reg, &v); + + dev_info(dev, "\tGPIO%u: mode %u\n", i, + (v >> ctl_shift) & 0xf); + } + } + + v4l2_subdev_unlock_state(state); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub960_subdev_core_ops = { + .log_status = ub960_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ub960_subdev_ops = { + .core = &ub960_subdev_core_ops, + .video = &ub960_video_ops, + .pad = &ub960_pad_ops, +}; + +static const struct media_entity_operations ub960_entity_ops = { + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, + .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, +}; + +static void ub960_enable_tpg(struct ub960_data *priv, int tpg_num) +{ + /* + * Note: no need to write UB960_REG_IND_ACC_CTL: the only indirect + * registers target we use is "CSI-2 Pattern Generator & Timing + * Registers", which is the default + */ + + /* + * TPG can only provide a single stream per CSI TX port. If + * multiple streams are currently enabled, only the first + * one will use the TPG, other streams will be halted. + */ + + struct v4l2_mbus_framefmt *fmt; + u8 vbp, vfp; + u16 blank_lines; + u16 width; + u16 height; + + u16 bytespp = 2; /* For MEDIA_BUS_FMT_UYVY8_1X16 */ + u8 cbars_idx = tpg_num - TEST_PATTERN_V_COLOR_BARS_1; + u8 num_cbars = 1 << cbars_idx; + + u16 line_size; /* Line size [bytes] */ + u16 bar_size; /* cbar size [bytes] */ + u16 act_lpf; /* active lines/frame */ + u16 tot_lpf; /* tot lines/frame */ + u16 line_pd; /* Line period in 10-ns units */ + + struct v4l2_subdev_state *state; + + state = v4l2_subdev_get_locked_active_state(&priv->sd); + + vbp = 33; + vfp = 10; + blank_lines = vbp + vfp + 2; /* total blanking lines */ + + fmt = v4l2_subdev_state_get_stream_format(state, 4, 0); + if (!fmt) { + dev_err(&priv->client->dev, "failed to enable TPG\n"); + return; + } + + width = fmt->width; + height = fmt->height; + + line_size = width * bytespp; + bar_size = line_size / num_cbars; + act_lpf = height; + tot_lpf = act_lpf + blank_lines; + line_pd = 100000000 / 60 / tot_lpf; + + /* Disable forwarding from FPD-3 RX ports */ + ub960_read(priv, UB960_SR_FWD_CTL1, &priv->stored_fwd_ctl); + ub960_write(priv, UB960_SR_FWD_CTL1, 0xf << 4); + + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CTL, + UB960_IR_PGEN_CTL_PGEN_ENABLE); + + /* YUV422 8bit: 2 bytes/block, CSI-2 data type 0x1e */ + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CFG, + cbars_idx << 4 | 0x2); + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CSI_DI, + 0x1e); + + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_LINE_SIZE1, line_size); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_BAR_SIZE1, bar_size); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_ACT_LPF1, act_lpf); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_TOT_LPF1, tot_lpf); + ub960_write_ind16(priv, UB960_IND_TARGET_PAT_GEN, + UB960_IR_PGEN_LINE_PD1, line_pd); + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_VBP, vbp); + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_VFP, vfp); +} + +static void ub960_disable_tpg(struct ub960_data *priv) +{ + /* TPG off, enable forwarding from FPD-3 RX ports */ + ub960_write(priv, UB960_SR_FWD_CTL1, priv->stored_fwd_ctl); + + ub960_write_ind(priv, UB960_IND_TARGET_PAT_GEN, UB960_IR_PGEN_CTL, 0x00); +} + +static int ub960_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ub960_data *priv = + container_of(ctrl->handler, struct ub960_data, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + if (ctrl->val == 0) + ub960_disable_tpg(priv); + else + ub960_enable_tpg(priv, ctrl->val); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops ub960_ctrl_ops = { + .s_ctrl = ub960_s_ctrl, +}; + +/* ----------------------------------------------------------------------------- + * Core + */ + +static irqreturn_t ub960_handle_events(int irq, void *arg) +{ + struct ub960_data *priv = arg; + unsigned int i; + u8 int_sts; + int ret; + + ret = ub960_read(priv, UB960_SR_INTERRUPT_STS, &int_sts); + + if (!ret && int_sts) { + u8 fwd_sts; + + dev_dbg(&priv->client->dev, "INTERRUPT_STS %x\n", int_sts); + + ub960_read(priv, UB960_SR_FWD_STS, &fwd_sts); + + dev_dbg(&priv->client->dev, "FWD_STS %#x\n", fwd_sts); + + for (i = 0; i < priv->hw_data->num_txports; ++i) { + if (int_sts & UB960_SR_INTERRUPT_STS_IS_CSI_TX(i)) + ub960_csi_handle_events(priv, i); + } + + for (i = 0; i < priv->hw_data->num_rxports; i++) { + if (!priv->rxports[i]) + continue; + + if (int_sts & UB960_SR_INTERRUPT_STS_IS_RX(i)) + ub960_rxport_handle_events(priv, i); + } + } + + return IRQ_HANDLED; +} + +static void ub960_handler_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct ub960_data *priv = + container_of(dwork, struct ub960_data, poll_work); + + ub960_handle_events(0, priv); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); +} + +static void ub960_txport_free_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_txports; nport++) { + struct ub960_txport *txport = priv->txports[nport]; + + if (!txport) + continue; + + kfree(txport); + priv->txports[nport] = NULL; + } +} + +static void ub960_rxport_free_ports(struct ub960_data *priv) +{ + unsigned int nport; + + for (nport = 0; nport < priv->hw_data->num_rxports; nport++) { + struct ub960_rxport *rxport = priv->rxports[nport]; + + if (!rxport) + continue; + + fwnode_handle_put(rxport->fwnode); + of_node_put(rxport->remote_of_node); + + kfree(rxport); + priv->rxports[nport] = NULL; + } +} + +static int ub960_parse_dt_base(struct ub960_data *priv) +{ + struct device_node *np = priv->client->dev.of_node; + struct device *dev = &priv->client->dev; + size_t table_size; + u16 *aliases; + int ret; + + ret = of_property_count_u16_elems(np, "i2c-alias-pool"); + if (ret < 0) { + dev_err(dev, "Failed to count 'i2c-alias-pool' property: %d\n", + ret); + return ret; + } + + table_size = ret; + priv->atr_alias_table.num_entries = ret; + + if (!table_size) + return 0; + + priv->atr_alias_table.entries = + devm_kcalloc(dev, table_size, + sizeof(struct atr_alias_table_entry), GFP_KERNEL); + if (!priv->atr_alias_table.entries) + return -ENOMEM; + + aliases = kcalloc(table_size, sizeof(u16), GFP_KERNEL); + if (!aliases) + return -ENOMEM; + + ret = of_property_read_u16_array(np, "i2c-alias-pool", aliases, + table_size); + if (ret < 0) { + dev_err(dev, "Failed to read 'i2c-alias-pool' property: %d\n", + ret); + kfree(aliases); + return ret; + } + + for (unsigned int i = 0; i < table_size; ++i) + priv->atr_alias_table.entries[i].alias_id = aliases[i]; + + kfree(aliases); + + dev_dbg(dev, "i2c-alias-pool has %zu aliases", table_size); + + return 0; +} + +static int ub960_parse_dt_rxport_link_properties(struct ub960_data *priv, + struct device_node *np, + struct ub960_rxport *rxport) +{ + struct device *dev = &priv->client->dev; + int ret; + u32 rx_mode; + u32 cdr_mode; + s32 strobe_pos; + u32 eq_level; + unsigned int nport = rxport->nport; + + ret = of_property_read_u32(np, "ti,cdr-mode", &cdr_mode); + if (ret == -EINVAL) { + cdr_mode = RXPORT_CDR_FPD3; + } else if (ret < 0) { + dev_err(dev, "Failed to get RX port CDR mode: %d\n", ret); + return ret; + } + + if (cdr_mode > RXPORT_CDR_LAST) { + dev_err(dev, "Bad RX port cdr %u\n", cdr_mode); + return -EINVAL; + } + + if (!priv->hw_data->fpdlink4 && cdr_mode == RXPORT_CDR_FPD4) { + dev_err(dev, "FPD-Link 4 CDR not supported on RX port %u\n", + nport); + return -EINVAL; + } + + rxport->cdr_mode = cdr_mode; + + ret = of_property_read_u32(np, "ti,rx-mode", &rx_mode); + if (ret < 0) { + dev_err(dev, "Missing RX port RX mode: %d\n", ret); + return ret; + } + + if (rx_mode > RXPORT_MODE_LAST) { + dev_err(dev, "Bad RX port RX mode %u\n", rx_mode); + return -EINVAL; + } + + rxport->rx_mode = rx_mode; + + /* EQ & Strobe related */ + + /* Defaults */ + rxport->eq.manual_eq = false; + rxport->eq.aeq.eq_level_min = UB960_MIN_EQ_LEVEL; + rxport->eq.aeq.eq_level_max = UB960_MAX_EQ_LEVEL; + + ret = of_property_read_s32(np, "ti,strobe-pos", &strobe_pos); + if (ret) { + if (ret != -EINVAL) { + dev_err(dev, "Failed to read 'ti,strobe-pos': %d\n", ret); + return ret; + } + } else if (strobe_pos < UB960_MIN_MANUAL_STROBE_POS || + strobe_pos > UB960_MAX_MANUAL_STROBE_POS) { + dev_err(dev, "Illegal 'strobe-pos' value: %d\n", strobe_pos); + } else { + // NOTE: ignored unless global manual strobe pos is set + rxport->eq.strobe_pos = strobe_pos; + if (!priv->strobe.manual) + dev_warn(dev, "'ti,strobe-pos' ignored as 'ti,manual-strobe' not set\n"); + } + + ret = of_property_read_u32(np, "ti,eq-level", &eq_level); + if (ret) { + if (ret != -EINVAL) { + dev_err(dev, "Failed to read 'ti,eq-level': %d\n", ret); + return ret; + } + } else if (eq_level > UB960_MAX_EQ_LEVEL) { + dev_err(dev, "Illegal 'ti,eq-level' value: %d\n", eq_level); + } else { + rxport->eq.manual_eq = true; + rxport->eq.manual.eq_level = eq_level; + } + + return 0; +} + +static int ub960_parse_dt_rxport_ep_properties(struct ub960_data *priv, struct ub960_rxport *rxport) +{ + struct device *dev = &priv->client->dev; + struct device_node *ep_np; + int ret; + u32 v; + + switch (rxport->rx_mode) { + case RXPORT_MODE_RAW10: + case RXPORT_MODE_RAW12_HF: + case RXPORT_MODE_RAW12_LF: + break; + default: + return 0; + } + + ep_np = of_graph_get_endpoint_by_regs(priv->client->dev.of_node, + rxport->nport, 0); + if (!ep_np) + return -EINVAL; + + ret = of_property_read_u32(ep_np, "hsync-active", &v); + if (ret) { + dev_err(dev, "Failed to parse 'hsync-active' for port %u: %d\n", + rxport->nport, ret); + goto err; + } + + rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_LV_POL_LOW : 0; + + ret = of_property_read_u32(ep_np, "vsync-active", &v); + if (ret) { + dev_err(dev, "Failed to parse 'vsync-active' for port %u: %d\n", + rxport->nport, ret); + goto err; + } + + rxport->lv_fv_pol |= v ? UB960_RR_PORT_CONFIG2_FV_POL_LOW : 0; + + return 0; +err: + of_node_put(ep_np); + return ret; +} + +static int ub960_parse_dt_rxport(struct ub960_data *priv, + struct device_node *np) +{ + const char *vpoc_names[UB960_MAX_RX_NPORTS] = { "vpoc0", "vpoc1", + "vpoc2", "vpoc3" }; + struct device *dev = &priv->client->dev; + unsigned int nport; + int ret; + struct ub960_rxport *rxport; + u32 ser_i2c_alias; + struct device_node *ep_np; + struct fwnode_handle *remote_ep_node; + + ret = of_property_read_u32(np, "reg", &nport); + if (ret) { + dev_err(dev, "Failed to get link reg\n"); + return ret; + } + + if (priv->rxports[nport]) { + dev_err(dev, "OF: %s: reg value %d is duplicated\n", + of_node_full_name(np), nport); + return -EADDRINUSE; + } + + rxport = kzalloc(sizeof(*rxport), GFP_KERNEL); + if (!rxport) + return -ENOMEM; + + priv->rxports[nport] = rxport; + + rxport->nport = nport; + rxport->priv = priv; + + ret = ub960_parse_dt_rxport_link_properties(priv, np, rxport); + if (ret) + goto err_free_rxport; + + ret = of_property_read_u32(np, "i2c-alias", &ser_i2c_alias); + if (ret) { + dev_err(dev, "Failed to get serializer i2c-alias: %d\n", ret); + goto err_free_rxport; + } + rxport->ser_alias = ser_i2c_alias; + + rxport->vpoc = devm_regulator_get_optional(dev, vpoc_names[nport]); + if (IS_ERR(rxport->vpoc)) { + ret = PTR_ERR(rxport->vpoc); + if (ret == -ENODEV) { + rxport->vpoc = NULL; + } else { + dev_err(dev, "Failed to get VPOC supply: %d\n", ret); + goto err_free_rxport; + } + } + + rxport->remote_of_node = of_get_child_by_name(np, "serializer"); + if (!rxport->remote_of_node) { + dev_err(dev, "OF: %s: missing remote-chip child\n", + of_node_full_name(np)); + ret = -EINVAL; + goto err_free_rxport; + } + + /* We also parse the endpoint data here */ + + ep_np = of_graph_get_endpoint_by_regs(priv->client->dev.of_node, nport, 0); + if (!ep_np) { + dev_err(dev, "Missing OF graph endpoint for link %u\n", nport); + ret = -EINVAL; + goto err_ser_node_put; + } + + remote_ep_node = fwnode_graph_get_remote_endpoint(of_fwnode_handle(ep_np)); + + of_node_put(ep_np); + + if (!remote_ep_node) { + dev_err(dev, "No remote endpoint for rxport%d\n", nport); + ret = -ENODEV; + goto err_ser_node_put; + } + + rxport->fwnode = remote_ep_node; + + ret = ub960_parse_dt_rxport_ep_properties(priv, rxport); + if (ret) + goto err_remote_ep_node_put; + + return 0; + +err_remote_ep_node_put: + fwnode_handle_put(rxport->fwnode); +err_ser_node_put: + of_node_put(rxport->remote_of_node); +err_free_rxport: + priv->rxports[nport] = NULL; + kfree(rxport); + return ret; +} + +static struct device_node * +ub960_of_get_link_by_regs(struct device_node *links_np, unsigned int nport) +{ + struct device_node *link_np; + int ret; + + for_each_child_of_node(links_np, link_np) { + u32 link_num; + + if (!of_node_name_eq(link_np, "link")) + continue; + + ret = of_property_read_u32(link_np, "reg", &link_num); + if (ret) + return NULL; + + if (nport == link_num) { + of_node_get(link_np); + return link_np; + } + } + + return NULL; +} + +static int ub960_parse_dt_rxports(struct ub960_data *priv) +{ + struct device_node *np = priv->client->dev.of_node; + struct device *dev = &priv->client->dev; + struct device_node *links_np; + unsigned int nport; + int ret; + + links_np = of_get_child_by_name(np, "links"); + if (!links_np) { + dev_err(dev, "links node missing\n"); + return -ENODEV; + } + + /* Defaults, recommended by TI */ + priv->strobe.min = 2; + priv->strobe.max = 3; + + priv->strobe.manual = of_property_read_bool(links_np, "ti,manual-strobe"); + + for (nport = 0; nport < priv->hw_data->num_rxports; ++nport) { + struct device_node *link_np; + + link_np = ub960_of_get_link_by_regs(links_np, nport); + if (!link_np) + continue; + + ret = ub960_parse_dt_rxport(priv, link_np); + + of_node_put(link_np); + + if (ret) { + dev_err(dev, "Failed to parse link node\n"); + goto err_put_links; + } + } + + of_node_put(links_np); + + return 0; + +err_put_links: + of_node_put(links_np); + + return ret; +} + +static int ub960_parse_dt_txports(struct ub960_data *priv) +{ + struct device_node *np = priv->client->dev.of_node; + u32 nport; + int ret; + + for (nport = 0; nport < priv->hw_data->num_txports; ++nport) { + struct device_node *ep_np; + + ep_np = of_graph_get_endpoint_by_regs(np, nport + priv->hw_data->num_rxports, 0); + if (!ep_np) + continue; + + ret = ub960_parse_dt_txport(priv, ep_np, nport); + + of_node_put(ep_np); + + if (ret) + break; + } + + return 0; +} + +static int ub960_parse_dt(struct ub960_data *priv) +{ + struct device_node *np = priv->client->dev.of_node; + struct device *dev = &priv->client->dev; + int ret; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + ret = ub960_parse_dt_base(priv); + if (ret) + return ret; + + ret = ub960_parse_dt_rxports(priv); + if (ret) + return ret; + + ret = ub960_parse_dt_txports(priv); + if (ret) + goto err_free_rxports; + + return 0; + +err_free_rxports: + ub960_rxport_free_ports(priv); + + return ret; +} + +static int ub960_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct ub960_data *priv = sd_to_ub960(notifier->sd); + struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport; + struct device *dev = &priv->client->dev; + u8 nport = rxport->nport; + unsigned int src_pad; + unsigned int i; + int ret; + + ret = media_entity_get_fwnode_pad(&subdev->entity, rxport->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", subdev->name); + return ret; + } + + rxport->sd = subdev; + src_pad = ret; + + ret = media_create_pad_link(&rxport->sd->entity, src_pad, + &priv->sd.entity, nport, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:%u\n", + rxport->sd->name, src_pad, priv->sd.name, nport); + return ret; + } + + dev_dbg(dev, "Bound %s pad: %u on index %u\n", subdev->name, src_pad, + nport); + + for (i = 0; i < priv->hw_data->num_rxports; ++i) { + if (priv->rxports[i] && !priv->rxports[i]->sd) { + dev_dbg(dev, "Waiting for more subdevs to be bound\n"); + return 0; + } + } + + dev_dbg(dev, "All subdevs bound\n"); + + return 0; +} + +static void ub960_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct ub960_rxport *rxport = to_ub960_asd(asd)->rxport; + + rxport->sd = NULL; +} + +static const struct v4l2_async_notifier_operations ub960_notify_ops = { + .bound = ub960_notify_bound, + .unbind = ub960_notify_unbind, +}; + +static int ub960_v4l2_notifier_register(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int i; + int ret; + + v4l2_async_nf_init(&priv->notifier); + + for (i = 0; i < priv->hw_data->num_rxports; ++i) { + struct ub960_rxport *rxport = priv->rxports[i]; + struct ub960_asd *asd; + + if (!rxport) + continue; + + asd = v4l2_async_nf_add_fwnode(&priv->notifier, rxport->fwnode, + struct ub960_asd); + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev for source %u: %ld", + i, PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + asd->rxport = rxport; + } + + priv->notifier.ops = &ub960_notify_ops; + + ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub960_v4l2_notifier_unregister(struct ub960_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub960_create_subdev(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + unsigned int i; + int ret; + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub960_subdev_ops); + v4l2_ctrl_handler_init(&priv->ctrl_handler, + ARRAY_SIZE(ub960_tpg_qmenu) - 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub960_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ub960_tpg_qmenu) - 1, 0, 0, + ub960_tpg_qmenu); + + v4l2_ctrl_new_int_menu(&priv->ctrl_handler, NULL, V4L2_CID_LINK_FREQ, + ARRAY_SIZE(priv->tx_link_freq) - 1, 0, + priv->tx_link_freq); + + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_free_ctrl; + } + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS | + V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub960_entity_ops; + + for (i = 0; i < priv->hw_data->num_rxports + priv->hw_data->num_txports; i++) { + priv->pads[i].flags = ub960_pad_is_sink(priv, i) ? + MEDIA_PAD_FL_SINK : MEDIA_PAD_FL_SOURCE; + } + + ret = media_entity_pads_init(&priv->sd.entity, + priv->hw_data->num_rxports + + priv->hw_data->num_txports, + priv->pads); + if (ret) + goto err_free_ctrl; + + priv->sd.state_lock = priv->sd.ctrl_handler->lock; + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub960_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + return 0; + +err_unreg_notif: + ub960_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + media_entity_cleanup(&priv->sd.entity); +err_free_ctrl: + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + return ret; +} + +static void ub960_destroy_subdev(struct ub960_data *priv) +{ + ub960_v4l2_notifier_unregister(priv); + v4l2_async_unregister_subdev(&priv->sd); + + v4l2_subdev_cleanup(&priv->sd); + + media_entity_cleanup(&priv->sd.entity); + v4l2_ctrl_handler_free(&priv->ctrl_handler); +} + +static const struct regmap_config ub960_regmap_config = { + .name = "ds90ub960", + + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0xff, + + /* + * We do locking in the driver to cover the TX/RX port selection and the + * indirect register access. + */ + .disable_locking = true, +}; + +static void ub960_reset(struct ub960_data *priv, bool reset_regs) +{ + struct device *dev = &priv->client->dev; + unsigned int v; + int ret; + u8 bit; + + bit = reset_regs ? UB960_SR_RESET_DIGITAL_RESET1 : + UB960_SR_RESET_DIGITAL_RESET0; + + ub960_write(priv, UB960_SR_RESET, bit); + + mutex_lock(&priv->reg_lock); + + ret = regmap_read_poll_timeout(priv->regmap, UB960_SR_RESET, v, + (v & bit) == 0, 2000, 100000); + + mutex_unlock(&priv->reg_lock); + + if (ret) + dev_err(dev, "reset failed: %d\n", ret); +} + +static int ub960_get_hw_resources(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + int ret; + + priv->regmap = devm_regmap_init_i2c(priv->client, &ub960_regmap_config); + if (IS_ERR(priv->regmap)) + return PTR_ERR(priv->regmap); + + priv->vddio = devm_regulator_get(dev, "vddio"); + if (IS_ERR(priv->vddio)) { + dev_err(dev, "cannot get VDDIO regulator\n"); + return PTR_ERR(priv->vddio); + } + + /* get power-down pin from DT */ + priv->pd_gpio = + devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(priv->pd_gpio)) { + ret = PTR_ERR(priv->pd_gpio); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Cannot get powerdown GPIO (%d)", ret); + return ret; + } + + priv->refclk = devm_clk_get(dev, "refclk"); + if (IS_ERR(priv->refclk)) { + ret = PTR_ERR(priv->refclk); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Cannot get REFCLK (%d)", ret); + return ret; + } + + return 0; +} + +static int ub960_enable_core_hw(struct ub960_data *priv) +{ + struct device *dev = &priv->client->dev; + u8 rev_mask; + int ret; + u8 dev_sts; + u8 refclk_freq; + + ret = regulator_enable(priv->vddio); + if (ret) { + dev_err(dev, "failed to enable VDDIO regulator\n"); + return ret; + } + + ret = clk_prepare_enable(priv->refclk); + if (ret) { + dev_err(dev, "Failed to renable refclk: %d\n", ret); + goto err_disable_vddio; + } + + if (priv->pd_gpio) { + gpiod_set_value_cansleep(priv->pd_gpio, 1); + /* wait min 2 ms for reset to complete */ + usleep_range(2000, 5000); + gpiod_set_value_cansleep(priv->pd_gpio, 0); + /* wait min 2 ms for power up to finish */ + usleep_range(2000, 5000); + } + + ub960_reset(priv, true); + + /* Runtime check register accessibility */ + ret = ub960_read(priv, UB960_SR_REV_MASK, &rev_mask); + if (ret) { + dev_err(dev, "Cannot read first register (%d), abort\n", ret); + goto err_pd_gpio; + } + + dev_dbg(dev, "Found %s (rev/mask %#04x)\n", priv->hw_data->model, + rev_mask); + + ret = ub960_read(priv, UB960_SR_DEVICE_STS, &dev_sts); + if (ret) + goto err_pd_gpio; + + ret = ub960_read(priv, UB960_XR_REFCLK_FREQ, &refclk_freq); + if (ret) + goto err_pd_gpio; + + dev_dbg(dev, "refclk valid %u freq %u MHz (clk fw freq %lu MHz)\n", + !!(dev_sts & BIT(4)), refclk_freq, + clk_get_rate(priv->refclk) / 1000000); + + /* Disable all RX ports by default */ + ub960_write(priv, UB960_SR_RX_PORT_CTL, 0); + + return 0; + +err_pd_gpio: + if (priv->pd_gpio) + gpiod_set_value_cansleep(priv->pd_gpio, 1); + + clk_disable_unprepare(priv->refclk); +err_disable_vddio: + regulator_disable(priv->vddio); + + return ret; +} + +static void ub960_disable_core_hw(struct ub960_data *priv) +{ + if (priv->pd_gpio) + gpiod_set_value_cansleep(priv->pd_gpio, 1); + + clk_disable_unprepare(priv->refclk); + + regulator_disable(priv->vddio); +} + +static int ub960_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub960_data *priv; + int ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->hw_data = of_device_get_match_data(dev); + if (!priv->hw_data) + return -ENODEV; + + mutex_init(&priv->reg_lock); + mutex_init(&priv->atr_alias_table.lock); + + INIT_DELAYED_WORK(&priv->poll_work, ub960_handler_work); + + /* + * Initialize these to invalid values so that the first reg writes will + * configure the target. + */ + priv->current_indirect_target = 0xff; + priv->current_read_rxport = 0xff; + priv->current_write_rxport_mask = 0xff; + priv->current_read_csiport = 0xff; + priv->current_write_csiport_mask = 0xff; + + ret = ub960_get_hw_resources(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub960_enable_core_hw(priv); + if (ret) + goto err_mutex_destroy; + + /* release GPIO lock */ + if (priv->hw_data->ub9702) + ub960_update_bits(priv, UB960_SR_RESET, + UB960_SR_RESET_GPIO_LOCK_RELEASE, + UB960_SR_RESET_GPIO_LOCK_RELEASE); + + ret = ub960_parse_dt(priv); + if (ret) + goto err_disable_core_hw; + + ret = ub960_init_tx_ports(priv); + if (ret) + goto err_free_ports; + + ret = ub960_rxport_enable_vpocs(priv); + if (ret) + goto err_free_ports; + + ret = ub960_init_rx_ports(priv); + if (ret) + goto err_disable_vpocs; + + ub960_reset(priv, false); + + ub960_rxport_wait_locks(priv, 0xf, NULL); + + /* + * Clear any errors caused by switching the RX port settings while + * probing. + */ + ub960_clear_rx_errors(priv); + + ret = ub960_init_atr(priv); + if (ret) + goto err_disable_vpocs; + + ret = ub960_rxport_add_serializers(priv); + if (ret) + goto err_uninit_atr; + + ret = ub960_create_subdev(priv); + if (ret) + goto err_free_sers; + + if (client->irq) + dev_warn(dev, "irq support not implemented, using polling\n"); + + schedule_delayed_work(&priv->poll_work, + msecs_to_jiffies(UB960_POLL_TIME_MS)); + + return 0; + +err_free_sers: + ub960_rxport_remove_serializers(priv); +err_uninit_atr: + ub960_uninit_atr(priv); +err_disable_vpocs: + ub960_rxport_disable_vpocs(priv); +err_free_ports: + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); +err_disable_core_hw: + ub960_disable_core_hw(priv); +err_mutex_destroy: + mutex_destroy(&priv->atr_alias_table.lock); + mutex_destroy(&priv->reg_lock); + return ret; +} + +static void ub960_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub960_data *priv = sd_to_ub960(sd); + + cancel_delayed_work_sync(&priv->poll_work); + + ub960_destroy_subdev(priv); + ub960_rxport_remove_serializers(priv); + ub960_uninit_atr(priv); + ub960_rxport_disable_vpocs(priv); + ub960_rxport_free_ports(priv); + ub960_txport_free_ports(priv); + ub960_disable_core_hw(priv); + mutex_destroy(&priv->atr_alias_table.lock); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub960_hw_data ds90ub960_hw = { + .model = "ub960", + .num_rxports = 4, + .num_txports = 2, +}; + +static const struct ub960_hw_data ds90ub9702_hw = { + .model = "ub9702", + .num_rxports = 4, + .num_txports = 2, + .ub9702 = true, + .fpdlink4 = true, +}; + +static const struct i2c_device_id ub960_id[] = { + { "ds90ub960-q1", 0 }, + { "ds90ub9702-q1", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ub960_id); + +#ifdef CONFIG_OF +static const struct of_device_id ub960_dt_ids[] = { + { .compatible = "ti,ds90ub960-q1", .data = &ds90ub960_hw }, + { .compatible = "ti,ds90ub9702-q1", .data = &ds90ub9702_hw }, + { } +}; +MODULE_DEVICE_TABLE(of, ub960_dt_ids); +#endif + +static struct i2c_driver ds90ub960_driver = { + .probe_new = ub960_probe, + .remove = ub960_remove, + .id_table = ub960_id, + .driver = { + .name = "ds90ub960", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ub960_dt_ids), + }, +}; + +module_i2c_driver(ds90ub960_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB960-Q1 FPD-Link deserializer 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 Thu Jan 5 14:03:06 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39598 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp315238wrt; Thu, 5 Jan 2023 06:04:33 -0800 (PST) X-Google-Smtp-Source: AMrXdXvea1UI7goPqYwsWHn99zZKEqStpdUeHotX99Dpwt9UfeasEXGyHw3ZehwHMc3r0Zw2iK6H X-Received: by 2002:aa7:9486:0:b0:581:e756:76a8 with SMTP id z6-20020aa79486000000b00581e75676a8mr30520076pfk.19.1672927473232; Thu, 05 Jan 2023 06:04:33 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927473; cv=none; d=google.com; s=arc-20160816; b=WCcFhW03OOVgpZUc+pIQfqFJeciFi2Ft1OUkN9kDilBqtMzekDXJMcurQx+HbmHceg MV7UUlJhEfTPhWxYstTOF5gmzcE/FhwuTLDef6/eqiGZiS+zUGJR+DCLE07HdtNrmcvo jC55S2p/BTYtCepkw1XEXNsaQGtIxHaNcawWA2Ix5ZSB+gpd9PholiWH7IFObyIHtKCx x+CMfi43PJhZsAcpKKf6CLtfAbWOW5HPAj4Rzcu4euSsN/ADVVge/Gyi4R3wfSlpXv8m 3Iuzb+aJ3F4yMhND61+/vfUKw3wKQi6lDtOkV2xJRGJjzdOp3Gsp6cb29HcuCOGdE81X 4H8g== 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=8g6wOYP8hrDaD9KnzBCdKiHRL+vTNrIbdei+vJUS6ss=; b=sc7R2r4HSCyKRR9M1/wXBx89oXY8TklXy6d8825d803DGZyZv0FU94k5QBQoqeA9wr xFvbK6bHnJgI68mAgKDJSUrNCHvoWp9AuFK7xwf/E82IdDNnaLUmPkCb7i4GlLSwwrwY eQYpp1kCCjrnxua9Dv7qbQqVyn1DhE2hBvZ/dLA9ilqabNFf5T68NEQv0POwFrinFAZo fnoS0f2BmhnWVM6l2wa/4T/tKxPtgBtYbqkSoAdpkvgsdbGvckAIgfrv1t74fS1qnOgN V7WqAPr9pONx9DaRmuJjGzrHJJIXBdeXtfMyA/W88hMjeGkb281vXRdQ9yVN7WnIuvOp STLg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=hQ7hYm1b; 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 w21-20020a056a0014d500b00557bb4f6977si29839238pfu.106.2023.01.05.06.04.20; Thu, 05 Jan 2023 06:04:33 -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=hQ7hYm1b; 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 S234600AbjAEOED (ORCPT + 99 others); Thu, 5 Jan 2023 09:04:03 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49496 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234383AbjAEODh (ORCPT ); Thu, 5 Jan 2023 09:03:37 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id A4BAEF5A0; Thu, 5 Jan 2023 06:03:35 -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 A899549C; Thu, 5 Jan 2023 15:03:32 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927413; bh=LwRkvIJWfeXtYE20CUt010ZBWd3OcGONC+5ZRQ9Zctk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hQ7hYm1b5WWJm/AqTiKjKUGNjKjaLXKWsY1GQ30Pet7Jenw0K8f9rlRxWIIZ6Jdvp B2gbxhgV8y0BXsDHYsgHVduBBtRm2mBh5zjh3Zno5gFE+ztIVO0nbd7t8+6Z9vvUqX G2rJZmPiZHUC4aufz3E4Ox3kNqCHAV2Jh9MXM2B0= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Tomi Valkeinen Subject: [PATCH v6 7/8] media: i2c: add DS90UB913 driver Date: Thu, 5 Jan 2023 16:03:06 +0200 Message-Id: <20230105140307.272052-8-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191598282021941?= X-GMAIL-MSGID: =?utf-8?q?1754191598282021941?= Add driver for TI DS90UB913 FPD-Link III Serializer. Signed-off-by: Tomi Valkeinen --- drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 2 +- drivers/media/i2c/ds90ub913.c | 871 ++++++++++++++++++++++++++++++++++ 3 files changed, 885 insertions(+), 1 deletion(-) create mode 100644 drivers/media/i2c/ds90ub913.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index f95a54454e17..eb312d6e9cf6 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 Serializer" + depends on OF && I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + select REGMAP_I2C + select OF_GPIO + select I2C_ATR + help + Device driver for the Texas Instruments DS90UB913 + FPD-Link III Serializer. + config VIDEO_DS90UB960 tristate "TI DS90UB960 Deserializer" depends on OF && I2C && VIDEO_DEV diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 2735b00437bb..c4875ec8c3b9 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile @@ -142,5 +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..0a60afb09cd3 --- /dev/null +++ b/drivers/media/i2c/ds90ub913.c @@ -0,0 +1,871 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB913 video serializer + * + * Based on a driver from Luca Ceresoli + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2022 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define UB913_PAD_SINK 0 +#define UB913_PAD_SOURCE 1 + +/* + * UB913 has 4 gpios, but gpios 3 and 4 are reserved for external 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_UP_TO_DATE BIT(4) +#define UB913_REG_MODE_SEL_MODE_OVERRIDE BIT(5) + +#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; + char gpio_chip_name[64]; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + + u64 enabled_source_streams; + + struct device_node *tx_ep_np; + + struct clk_hw *clkout_clk_hw; + + struct ds90ub9xx_platform_data *plat_data; + + 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; + unsigned int field_idx; + int ret; + + reg_idx = offset / 2; + field_idx = offset % 2; + + ret = regmap_update_bits( + priv->regmap, UB913_REG_GPIO_CFG(reg_idx), + UB913_REG_GPIO_CFG_MASK(field_idx), + UB913_REG_GPIO_CFG_ENABLE(field_idx) | + (value ? UB913_REG_GPIO_CFG_OUT_VAL(field_idx) : 0)); + + return ret; +} + +static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + ub913_gpio_direction_out(gc, offset, value); +} + +static int ub913_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub913_gpiochip_probe(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret; + + /* Initialize GPIOs 0 and 1 to local control, tri-state */ + ub913_write(priv, UB913_REG_GPIO_CFG(0), 0); + + scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s", + dev_name(dev)); + + gc->label = priv->gpio_chip_name; + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->can_sleep = 1; + gc->ngpio = UB913_NUM_GPIOS; + gc->get_direction = ub913_gpio_get_direction; + gc->direction_output = ub913_gpio_direction_out; + gc->set = ub913_gpio_set; + gc->of_xlate = ub913_gpio_of_xlate; + gc->of_node = priv->client->dev.of_node; + gc->of_gpio_n_cells = 2; + + ret = gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub913_gpiochip_remove(struct ub913_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +static const struct regmap_config ub913_regmap_config = { + .name = "ds90ub913", + .reg_bits = 8, + .val_bits = 8, + .reg_format_endian = REGMAP_ENDIAN_DEFAULT, + .val_format_endian = REGMAP_ENDIAN_DEFAULT, +}; + +/* + * V4L2 + */ + +static int ub913_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct media_pad *remote_pad; + u64 sink_streams; + int ret; + + if (streams_mask & priv->enabled_source_streams) + return -EALREADY; + + sink_streams = v4l2_subdev_state_xlate_streams( + state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask); + + remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]); + + ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |= streams_mask; + + return 0; +} + +static int ub913_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct media_pad *remote_pad; + int ret; + u64 sink_streams; + + if ((streams_mask & priv->enabled_source_streams) != streams_mask) + return -EALREADY; + + sink_streams = v4l2_subdev_state_xlate_streams( + state, UB913_PAD_SOURCE, UB913_PAD_SINK, &streams_mask); + + remote_pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]); + + ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + return 0; +} + +static int _ub913_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + 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_source_frame_desc(struct ub913_data *priv, + struct v4l2_mbus_frame_desc *desc) +{ + struct media_pad *pad; + int ret; + + pad = media_pad_remote_pad_first(&priv->pads[UB913_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index, + desc); + if (ret) + return ret; + + return 0; +} + +static int ub913_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub913_data *priv = sd_to_ub913(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret; + + if (pad != UB913_PAD_SOURCE) + return -EINVAL; + + ret = ub913_get_source_frame_desc(priv, &source_fd); + if (ret) + return ret; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + routing = &state->routing; + + memset(fd, 0, sizeof(*fd)); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; + + for_each_active_route(routing, route) { + unsigned int i; + + if (route->source_pad != pad) + continue; + + for (i = 0; i < source_fd.num_entries; ++i) + if (source_fd.entry[i].stream == route->sink_stream) + break; + + if (i == source_fd.num_entries) { + dev_err(&priv->client->dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_fd.entry[i].flags; + fd->entry[fd->num_entries].length = source_fd.entry[i].length; + fd->entry[fd->num_entries].pixelcode = + source_fd.entry[i].pixelcode; + + fd->num_entries++; + } + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub913_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == UB913_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub913_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = UB913_PAD_SINK, + .sink_stream = 0, + .source_pad = UB913_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub913_set_routing(sd, state, &routing); +} + +static int ub913_log_status(struct v4l2_subdev *sd) +{ + struct ub913_data *priv = sd_to_ub913(sd); + struct device *dev = &priv->client->dev; + u8 v, v1, v2; + + ub913_read(priv, UB913_REG_MODE_SEL, &v); + dev_info(dev, "MODE_SEL %#x\n", v); + + ub913_read(priv, UB913_REG_CRC_ERRORS_LSB, &v1); + ub913_read(priv, UB913_REG_CRC_ERRORS_MSB, &v2); + dev_info(dev, "CRC errors %u\n", v1 | (v2 << 8)); + + ub913_read(priv, UB913_REG_GENERAL_STATUS, &v); + dev_info(dev, "GENERAL_STATUS %#x\n", v); + + ub913_read(priv, UB913_REG_PLL_OVR, &v); + dev_info(dev, "PLL_OVR %#x\n", v); + + /* clear CRC errors */ + ub913_read(priv, UB913_REG_GENERAL_CFG, &v); + ub913_write(priv, UB913_REG_GENERAL_CFG, v | UB913_REG_GENERAL_CFG_CRC_ERR_RESET); + ub913_write(priv, UB913_REG_GENERAL_CFG, v); + + return 0; +} + +static const struct v4l2_subdev_core_ops ub913_subdev_core_ops = { + .log_status = ub913_log_status, +}; + +static const struct v4l2_subdev_pad_ops ub913_pad_ops = { + .enable_streams = ub913_enable_streams, + .disable_streams = ub913_disable_streams, + .set_routing = ub913_set_routing, + .get_frame_desc = ub913_get_frame_desc, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub913_set_fmt, + .init_cfg = ub913_init_cfg, +}; + +static const struct v4l2_subdev_ops ub913_subdev_ops = { + .core = &ub913_subdev_core_ops, + .pad = &ub913_pad_ops, +}; + +static const struct media_entity_operations ub913_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int ub913_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *source_subdev, + struct v4l2_async_subdev *asd) +{ + struct ub913_data *priv = sd_to_ub913(notifier->sd); + struct device *dev = &priv->client->dev; + unsigned int src_pad; + int ret; + + ret = media_entity_get_fwnode_pad(&source_subdev->entity, + source_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "Failed to find pad for %s\n", + source_subdev->name); + return ret; + } + + priv->source_sd = source_subdev; + src_pad = ret; + + ret = media_create_pad_link(&source_subdev->entity, src_pad, + &priv->sd.entity, UB913_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, src_pad, priv->sd.name); + return ret; + } + + 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 device_node *ep_node; + int ret; + + ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + if (!ep_node) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_nf_init(&priv->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, + of_fwnode_handle(ep_node), + struct v4l2_async_subdev); + + of_node_put(ep_node); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &ub913_notify_ops; + + ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub913_v4l2_nf_unregister(struct ub913_data *priv) +{ + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +} + +static int ub913_register_clkout(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + const char *name; + int ret; + + name = kasprintf(GFP_KERNEL, "ds90ub913.%s.clk_out", dev_name(dev)); + + priv->clkout_clk_hw = devm_clk_hw_register_fixed_factor(dev, name, + __clk_get_name(priv->clkin), 0, 1, 2); + + kfree(name); + + if (IS_ERR(priv->clkout_clk_hw)) + return dev_err_probe(dev, PTR_ERR(priv->clkout_clk_hw), + "Cannot register clkout hw\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + priv->clkout_clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; +} + +static int ub913_i2c_master_init(struct ub913_data *priv) +{ + /* i2c fast mode */ + u32 scl_high = 600 + 300; /* high period + rise time, ns */ + u32 scl_low = 1300 + 300; /* low period + fall time, ns */ + unsigned long ref; + int ret; + + ref = clk_get_rate(priv->clkin) / 2; + + scl_high = div64_u64((u64)scl_high * ref, 1000000000); + scl_low = div64_u64((u64)scl_low * ref, 1000000000); + + ret = ub913_write(priv, UB913_REG_SCL_HIGH_TIME, scl_high); + if (ret) + return ret; + + ret = ub913_write(priv, UB913_REG_SCL_LOW_TIME, scl_low); + if (ret) + return ret; + + return 0; +} + +static int ub913_add_i2c_adapter(struct ub913_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle = device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port, + i2c_handle); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + return 0; +} + +static int ub913_parse_dt(struct ub913_data *priv) +{ + struct device_node *np = priv->client->dev.of_node; + struct device *dev = &priv->client->dev; + int ret; + struct device_node *ep_np; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + ep_np = of_graph_get_endpoint_by_regs(np, 0, 0); + if (!ep_np) { + dev_err(dev, "OF: no endpoint\n"); + return -ENOENT; + } + + ret = of_property_read_u32(ep_np, "pclk-sample", &priv->pclk_polarity); + + of_node_put(ep_np); + + if (ret) { + dev_err(dev, "OF: failed to parse pclk-sample: %d\n", ret); + return ret; + } + + return 0; +} + +static int ub913_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub913_data *priv; + int ret; + u8 v; + bool mode_override; + u8 mode; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->plat_data = dev_get_platdata(&client->dev); + if (!priv->plat_data) { + dev_err(dev, "Platform data missing\n"); + return -ENODEV; + } + + priv->regmap = devm_regmap_init_i2c(client, &ub913_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Failed to init regmap\n"); + return PTR_ERR(priv->regmap); + } + + /* ub913 can also work without ext clock, but that is not supported */ + priv->clkin = devm_clk_get(dev, "clkin"); + if (IS_ERR(priv->clkin)) { + ret = PTR_ERR(priv->clkin); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Cannot get CLKIN (%d)", ret); + return ret; + } + + ret = ub913_parse_dt(priv); + if (ret) + return ret; + + ret = ub913_read(priv, UB913_REG_MODE_SEL, &v); + if (ret) + return ret; + + if (!(v & UB913_REG_MODE_SEL_MODE_UP_TO_DATE)) { + dev_err(dev, "Mode value not stabilized\n"); + return -ENODEV; + } + + mode_override = v & UB913_REG_MODE_SEL_MODE_OVERRIDE; + mode = v & 0xf; + + dev_dbg(dev, "mode from %s: %#x\n", + mode_override ? "reg" : "deserializer", mode); + + ret = ub913_i2c_master_init(priv); + if (ret) { + dev_err(dev, "i2c master init failed: %d\n", ret); + return ret; + } + + ret = ub913_gpiochip_probe(priv); + if (ret) { + dev_err(dev, "Failed to init gpiochip\n"); + return ret; + } + + ret = ub913_register_clkout(priv); + if (ret) { + dev_err(dev, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + 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); + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub913_subdev_ops); + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub913_entity_ops; + + priv->pads[0].flags = MEDIA_PAD_FL_SINK; + priv->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) { + dev_err(dev, "Failed to init pads\n"); + goto err_gpiochip_remove; + } + + priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0); + if (priv->tx_ep_np) + priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np); + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub913_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + ret = ub913_add_i2c_adapter(priv); + if (ret) { + dev_err(dev, "failed to add remote i2c adapter\n"); + goto err_unreg_async_subdev; + } + + return 0; + +err_unreg_async_subdev: + v4l2_async_unregister_subdev(&priv->sd); +err_unreg_notif: + ub913_v4l2_nf_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + if (priv->tx_ep_np) + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); +err_gpiochip_remove: + ub913_gpiochip_remove(priv); + + return ret; +} + +static void ub913_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub913_data *priv = sd_to_ub913(sd); + + i2c_atr_del_adapter(priv->plat_data->atr, + priv->plat_data->port); + + v4l2_async_unregister_subdev(&priv->sd); + + ub913_v4l2_nf_unregister(priv); + + v4l2_subdev_cleanup(&priv->sd); + + if (priv->tx_ep_np) + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); + + ub913_gpiochip_remove(priv); +} + +static const struct i2c_device_id ub913_id[] = { { "ds90ub913a-q1", 0 }, {} }; +MODULE_DEVICE_TABLE(i2c, ub913_id); + +#ifdef CONFIG_OF +static const struct of_device_id ub913_dt_ids[] = { + { .compatible = "ti,ds90ub913a-q1", }, + {} +}; +MODULE_DEVICE_TABLE(of, ub913_dt_ids); +#endif + +static struct i2c_driver ds90ub913_driver = { + .probe_new = ub913_probe, + .remove = ub913_remove, + .id_table = ub913_id, + .driver = { + .name = "ds90ub913a", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ub913_dt_ids), + }, +}; + +module_i2c_driver(ds90ub913_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB913 serializer driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS(I2C_ATR); From patchwork Thu Jan 5 14:03:07 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tomi Valkeinen X-Patchwork-Id: 39597 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:a5d:4e01:0:0:0:0:0 with SMTP id p1csp315104wrt; Thu, 5 Jan 2023 06:04:22 -0800 (PST) X-Google-Smtp-Source: AMrXdXvBWoJJSMRt7MTyD8nG1uOdJbDwZCSOFx2Lk2kPBBFL++85CuVDI/VPdb+07W2w4HVRZevx X-Received: by 2002:a05:6a20:6682:b0:af:73c4:1c1f with SMTP id o2-20020a056a20668200b000af73c41c1fmr54035802pzh.42.1672927462031; Thu, 05 Jan 2023 06:04:22 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1672927462; cv=none; d=google.com; s=arc-20160816; b=XAYMwJffGV4XiumoXc35yQZmfYRtkWP1Xs6R4qv/0iHKqvwOieFWInvSqLRmAdRXZi me3pcltVqxuuXzSlTYoawwOM/TRXAHyK8rvFlnPHgWy++Nxo7upqj4sOYOFoUb6QmLCC wDJnvKPAL77+lJdBnXymHkZkhM7BpA+9rYIx7GsBmCJuftZlFVZwUECmrL0RQJqSrY9t fTxquzFsOQuxXLMUPZbKxTf9u9KixZE2OkSNwPjyoTUm4PqX0K92ttkN+2H1fsEi2vGJ KPI6pKIBerAfjXCRhDOGALxVZQXuWM8U/ROzl3Gb5zLR3h/pdCWC/+GU+qv60SbCBqlE M6XQ== 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=QeXdERdNFTZktC9MD4Aw9Fm9c3CJmNIaz06DGLZlsig=; b=QIJviif1WSIfJEBapnCxsgoL1qocqr4Ytxp3DbqH2JPWFir0ezV0GXfakhPL1k9haB mEKURn9T1iTatVlmCKx9uqcMFdIdDTwt5bPi97jBtlpVvVuGqPMyWNFP8qcjpyuOtxQO cOD5hRaA06ys5GQft4denUe8t4OcYRz9qoET4BOrLV2VkAG4DmutsGuRNTEKkBL1V8NR 4u2ZBdqYa6hJ2CmLKXIId/i1miIJ12bIKSoGcKRNDan48d/N1ukPbxkshfMLzrlrCPXE VxPWYKsbdbMlqdFrLeZ3S2UpvXri+O4GVYOcQE3AMUDg+8h5QJ1x1UfA800UDKImFJuR bKng== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass (test mode) header.i=@ideasonboard.com header.s=mail header.b=U9C+ZwSg; 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 a185-20020a6390c2000000b00489a4cba34fsi39060598pge.21.2023.01.05.06.04.06; Thu, 05 Jan 2023 06:04:21 -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=U9C+ZwSg; 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 S234573AbjAEODv (ORCPT + 99 others); Thu, 5 Jan 2023 09:03:51 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:49526 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S234465AbjAEODm (ORCPT ); Thu, 5 Jan 2023 09:03:42 -0500 Received: from perceval.ideasonboard.com (perceval.ideasonboard.com [213.167.242.64]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id DDFC319295; Thu, 5 Jan 2023 06:03: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 17D1C4AE; Thu, 5 Jan 2023 15:03:34 +0100 (CET) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com; s=mail; t=1672927415; bh=PgKH20ZwwDE91ShE549lZaoK1UK7zqKloIZfEhQ1zBA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=U9C+ZwSgn05geuzibu7X6B27+x1MqK1g8IDCo6CijDIqLlszgCNd90l/esl8cT6Kv BYvs/5VvdLsXUw9KVcgNvNXNL31c4zNa95t/iT7UjY3dSlM3xrOyhvZORLgNBS9XPe EKga9rhF1lUxreg6ZThHUkdiwj1nONzWQah7HYsw= 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 Cc: Mauro Carvalho Chehab , Peter Rosin , Liam Girdwood , Mark Brown , Sakari Ailus , Laurent Pinchart , Michael Tretter , Shawn Tu , Hans Verkuil , Mike Pagano , =?utf-8?q?Krzysztof_Ha=C5=82asa?= , Marek Vasut , Tomi Valkeinen Subject: [PATCH v6 8/8] media: i2c: add DS90UB953 driver Date: Thu, 5 Jan 2023 16:03:07 +0200 Message-Id: <20230105140307.272052-9-tomi.valkeinen@ideasonboard.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230105140307.272052-1-tomi.valkeinen@ideasonboard.com> References: <20230105140307.272052-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?1754191586479105602?= X-GMAIL-MSGID: =?utf-8?q?1754191586479105602?= Add driver for TI DS90UB953 FPD-Link III Serializer. Signed-off-by: Tomi Valkeinen --- drivers/media/i2c/Kconfig | 13 + drivers/media/i2c/Makefile | 1 + drivers/media/i2c/ds90ub953.c | 1590 +++++++++++++++++++++++++++++++++ 3 files changed, 1604 insertions(+) create mode 100644 drivers/media/i2c/ds90ub953.c diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index eb312d6e9cf6..29594c724307 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 DS90UB953 Serializer" + depends on OF && I2C && VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + select REGMAP_I2C + select OF_GPIO + select I2C_ATR + help + Device driver for the Texas Instruments DS90UB953 + FPD-Link III Serializer and DS90UB971 FPD-Link IV Serializer. + config VIDEO_DS90UB960 tristate "TI DS90UB960 Deserializer" 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..251dc8e8adfa --- /dev/null +++ b/drivers/media/i2c/ds90ub953.c @@ -0,0 +1,1590 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for the Texas Instruments DS90UB953 video serializer + * + * Based on a driver from Luca Ceresoli + * + * Copyright (c) 2019 Luca Ceresoli + * Copyright (c) 2022 Tomi Valkeinen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define UB953_PAD_SINK 0 +#define UB953_PAD_SOURCE 1 + +#define UB953_NUM_GPIOS 4 + +#define UB953_REG_RESET_CTL 0x01 +#define UB953_REG_RESET_CTL_DIGITAL_RESET_1 BIT(1) +#define UB953_REG_RESET_CTL_DIGITAL_RESET_0 BIT(0) + +#define UB953_REG_GENERAL_CFG 0x02 +#define UB953_REG_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 ub971; +}; + +struct ub953_data { + const struct ub953_hw_data *hw_data; + + struct i2c_client *client; + struct regmap *regmap; + + u32 num_data_lanes; + + struct gpio_chip gpio_chip; + char gpio_chip_name[64]; + + struct v4l2_subdev sd; + struct media_pad pads[2]; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *source_sd; + + struct v4l2_ctrl_handler ctrl_handler; + + u64 enabled_source_streams; + + struct device_node *tx_ep_np; + + /* lock for register access */ + struct mutex reg_lock; + + u8 current_indirect_target; + + struct clk_hw clkout_clk_hw; + + enum ub953_mode mode; + + struct ds90ub9xx_platform_data *plat_data; +}; + +static inline struct ub953_data *sd_to_ub953(struct v4l2_subdev *sd) +{ + return container_of(sd, struct ub953_data, sd); +} + +/* + * HW Access + */ + +static int ub953_read(struct ub953_data *priv, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_read(priv->regmap, reg, &v); + if (ret) + dev_err(&priv->client->dev, + "Cannot read register 0x%02x: %d!\n", reg, ret); + else + *val = v; + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write(struct ub953_data *priv, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = regmap_write(priv->regmap, reg, val); + if (ret) + dev_err(&priv->client->dev, + "Cannot write register 0x%02x: %d!\n", reg, ret); + + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int _ub953_select_ind_reg_block(struct ub953_data *priv, u8 block) +{ + struct device *dev = &priv->client->dev; + int ret; + + if (priv->current_indirect_target == block) + return 0; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_CTL, block << 2); + if (ret) { + dev_err(dev, "%s: cannot select indirect target %u (%d)!\n", + __func__, block, ret); + return ret; + } + + priv->current_indirect_target = block; + + return 0; +} + +__maybe_unused +static int ub953_read_ind(struct ub953_data *priv, u8 block, u8 reg, u8 *val) +{ + unsigned int v; + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub953_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_read(priv->regmap, UB953_REG_IND_ACC_DATA, &v); + if (ret) + goto out; + + *val = v; + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write_ind(struct ub953_data *priv, u8 block, u8 reg, u8 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub953_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val); + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +static int ub953_write_ind16(struct ub953_data *priv, u8 block, u8 reg, u16 val) +{ + int ret; + + mutex_lock(&priv->reg_lock); + + ret = _ub953_select_ind_reg_block(priv, block); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val >> 8); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_ADDR, reg + 1); + if (ret) + goto out; + + ret = regmap_write(priv->regmap, UB953_REG_IND_ACC_DATA, val & 0xff); + if (ret) + goto out; + +out: + mutex_unlock(&priv->reg_lock); + + return ret; +} + +/* + * GPIO chip + */ +static int ub953_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &v); + if (ret) + return ret; + + if (v & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset)) + return GPIO_LINE_DIRECTION_IN; + else + return GPIO_LINE_DIRECTION_OUT; +} + +static int ub953_gpio_direction_in(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + + return regmap_update_bits( + priv->regmap, UB953_REG_GPIO_INPUT_CTRL, + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) | + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset), + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset)); +} + +static int ub953_gpio_direction_out(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + + ret = regmap_update_bits( + priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : 0); + + if (ret) + return ret; + + return regmap_update_bits( + priv->regmap, UB953_REG_GPIO_INPUT_CTRL, + UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(offset) | + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset), + UB953_REG_GPIO_INPUT_CTRL_OUT_EN(offset)); +} + +static int ub953_gpio_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + int ret; + u8 v; + + ret = ub953_read(priv, UB953_REG_GPIO_PIN_STS, &v); + if (ret) + return ret; + + return !!(v & UB953_REG_GPIO_PIN_STS_GPIO_STS(offset)); +} + +static void ub953_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +{ + struct ub953_data *priv = gpiochip_get_data(gc); + + regmap_update_bits( + priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : 0); +} + +static int ub953_gpio_of_xlate(struct gpio_chip *gc, + const struct of_phandle_args *gpiospec, + u32 *flags) +{ + if (flags) + *flags = gpiospec->args[1]; + + return gpiospec->args[0]; +} + +static int ub953_gpiochip_probe(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct gpio_chip *gc = &priv->gpio_chip; + int ret; + + /* Set all GPIOs to local input mode */ + ub953_write(priv, UB953_REG_LOCAL_GPIO_DATA, 0); + ub953_write(priv, UB953_REG_GPIO_INPUT_CTRL, 0xf); + + scnprintf(priv->gpio_chip_name, sizeof(priv->gpio_chip_name), "%s", + dev_name(dev)); + + gc->label = priv->gpio_chip_name; + gc->parent = dev; + gc->owner = THIS_MODULE; + gc->base = -1; + gc->can_sleep = 1; + gc->ngpio = UB953_NUM_GPIOS; + gc->get_direction = ub953_gpio_get_direction; + gc->direction_input = ub953_gpio_direction_in; + gc->direction_output = ub953_gpio_direction_out; + gc->get = ub953_gpio_get; + gc->set = ub953_gpio_set; + gc->of_xlate = ub953_gpio_of_xlate; + gc->of_node = priv->client->dev.of_node; + gc->of_gpio_n_cells = 2; + + ret = gpiochip_add_data(gc, priv); + if (ret) { + dev_err(dev, "Failed to add GPIOs: %d\n", ret); + return ret; + } + + return 0; +} + +static void ub953_gpiochip_remove(struct ub953_data *priv) +{ + gpiochip_remove(&priv->gpio_chip); +} + +/* + * V4L2 + */ + +static int _ub953_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + 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_source_frame_desc(struct ub953_data *priv, + struct v4l2_mbus_frame_desc *desc) +{ + struct media_pad *pad; + int ret; + + pad = media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret = v4l2_subdev_call(priv->source_sd, pad, get_frame_desc, pad->index, + desc); + if (ret) + return ret; + + return 0; +} + +static int ub953_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct ub953_data *priv = sd_to_ub953(sd); + const struct v4l2_subdev_krouting *routing; + struct v4l2_mbus_frame_desc source_fd; + struct v4l2_subdev_route *route; + struct v4l2_subdev_state *state; + int ret; + + if (pad != UB953_PAD_SOURCE) + return -EINVAL; + + ret = ub953_get_source_frame_desc(priv, &source_fd); + if (ret) + return ret; + + state = v4l2_subdev_lock_and_get_active_state(sd); + + routing = &state->routing; + + memset(fd, 0, sizeof(*fd)); + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_CSI2; + + for_each_active_route(routing, route) { + struct v4l2_mbus_frame_desc_entry *source_entry = NULL; + unsigned int i; + + if (route->source_pad != pad) + continue; + + for (i = 0; i < source_fd.num_entries; ++i) + if (source_fd.entry[i].stream == route->sink_stream) { + source_entry = &source_fd.entry[i]; + break; + } + + if (!source_entry) { + dev_err(&priv->client->dev, + "Failed to find stream from source frame desc\n"); + ret = -EPIPE; + goto out; + } + + fd->entry[fd->num_entries].stream = route->source_stream; + fd->entry[fd->num_entries].flags = source_entry->flags; + fd->entry[fd->num_entries].length = source_entry->length; + fd->entry[fd->num_entries].pixelcode = source_entry->pixelcode; + fd->entry[fd->num_entries].bus.csi2.vc = + source_entry->bus.csi2.vc; + fd->entry[fd->num_entries].bus.csi2.dt = + source_entry->bus.csi2.dt; + + fd->num_entries++; + } + +out: + v4l2_subdev_unlock_state(state); + + return ret; +} + +static int ub953_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct v4l2_mbus_framefmt *fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE && + priv->enabled_source_streams) + return -EBUSY; + + /* No transcoding, source and sink formats must match. */ + if (format->pad == UB953_PAD_SOURCE) + return v4l2_subdev_get_fmt(sd, state, format); + + /* Set sink format */ + fmt = v4l2_subdev_state_get_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + /* Propagate to source format */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + return 0; +} + +static int ub953_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route routes[] = { + { + .sink_pad = UB953_PAD_SINK, + .sink_stream = 0, + .source_pad = UB953_PAD_SOURCE, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, + }; + + struct v4l2_subdev_krouting routing = { + .num_routes = ARRAY_SIZE(routes), + .routes = routes, + }; + + return _ub953_set_routing(sd, state, &routing); +} + +static int ub953_log_status(struct v4l2_subdev *sd) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct device *dev = &priv->client->dev; + u8 v = 0, v1 = 0, v2 = 0; + unsigned int i; + char id[7]; + u8 gpio_local_data; + u8 gpio_input_ctrl; + u8 gpio_pin_sts; + + for (i = 0; i < 6; ++i) + ub953_read(priv, UB953_REG_FPD3_RX_ID(i), &id[i]); + id[6] = 0; + + dev_info(dev, "ID '%s'\n", id); + + ub953_read(priv, UB953_REG_GENERAL_STATUS, &v); + dev_info(dev, "GENERAL_STATUS %#x\n", v); + + ub953_read(priv, UB953_REG_CRC_ERR_CNT1, &v1); + ub953_read(priv, UB953_REG_CRC_ERR_CNT2, &v2); + dev_info(dev, "CRC error count %u\n", v1 | (v2 << 8)); + + ub953_read(priv, UB953_REG_CSI_ERR_CNT, &v); + dev_info(dev, "CSI error count %u\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_STATUS, &v); + dev_info(dev, "CSI_ERR_STATUS %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_DLANE01, &v); + dev_info(dev, "CSI_ERR_DLANE01 %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_DLANE23, &v); + dev_info(dev, "CSI_ERR_DLANE23 %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_ERR_CLK_LANE, &v); + dev_info(dev, "CSI_ERR_CLK_LANE %#x\n", v); + + ub953_read(priv, UB953_REG_CSI_PKT_HDR_VC_ID, &v); + dev_info(dev, "CSI packet header VC %u ID %u\n", v >> 6, v & 0x3f); + + ub953_read(priv, UB953_REG_PKT_HDR_WC_LSB, &v1); + ub953_read(priv, UB953_REG_PKT_HDR_WC_MSB, &v2); + dev_info(dev, "CSI packet header WC %u\n", (v2 << 8) | v1); + + ub953_read(priv, UB953_REG_CSI_ECC, &v); + dev_info(dev, "CSI ECC %#x\n", v); + + ub953_read(priv, UB953_REG_LOCAL_GPIO_DATA, &gpio_local_data); + ub953_read(priv, UB953_REG_GPIO_INPUT_CTRL, &gpio_input_ctrl); + ub953_read(priv, UB953_REG_GPIO_PIN_STS, &gpio_pin_sts); + + for (i = 0; i < UB953_NUM_GPIOS; ++i) { + dev_info(dev, + "GPIO%u: remote: %u is_input: %u is_output: %u val: %u sts: %u\n", + i, + !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_RMTEN(i)), + !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_INPUT_EN(i)), + !!(gpio_input_ctrl & UB953_REG_GPIO_INPUT_CTRL_OUT_EN(i)), + !!(gpio_local_data & UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(i)), + !!(gpio_pin_sts & UB953_REG_GPIO_PIN_STS_GPIO_STS(i))); + } + + return 0; +} + +static int ub953_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct media_pad *remote_pad; + u64 sink_streams; + int ret; + + if (streams_mask & priv->enabled_source_streams) + return -EALREADY; + + sink_streams = v4l2_subdev_state_xlate_streams( + state, UB953_PAD_SOURCE, UB953_PAD_SINK, &streams_mask); + + remote_pad = media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]); + + ret = v4l2_subdev_enable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams |= streams_mask; + + return 0; +} + +static int ub953_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct ub953_data *priv = sd_to_ub953(sd); + struct media_pad *remote_pad; + int ret; + u64 sink_streams; + + if ((streams_mask & priv->enabled_source_streams) != streams_mask) + return -EALREADY; + + sink_streams = v4l2_subdev_state_xlate_streams( + state, UB953_PAD_SOURCE, UB953_PAD_SINK, &streams_mask); + + remote_pad = media_pad_remote_pad_first(&priv->pads[UB953_PAD_SINK]); + + ret = v4l2_subdev_disable_streams(priv->source_sd, remote_pad->index, + sink_streams); + if (ret) + return ret; + + priv->enabled_source_streams &= ~streams_mask; + + return 0; +} + +static const struct v4l2_subdev_pad_ops ub953_pad_ops = { + .enable_streams = ub953_enable_streams, + .disable_streams = ub953_disable_streams, + .set_routing = ub953_set_routing, + .get_frame_desc = ub953_get_frame_desc, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ub953_set_fmt, + .init_cfg = ub953_init_cfg, +}; + +static const struct v4l2_subdev_core_ops ub953_subdev_core_ops = { + .log_status = ub953_log_status, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops ub953_subdev_ops = { + .core = &ub953_subdev_core_ops, + .pad = &ub953_pad_ops, +}; + +static const struct media_entity_operations ub953_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +enum { + TEST_PATTERN_DISABLED = 0, + TEST_PATTERN_V_COLOR_BARS_1, + TEST_PATTERN_V_COLOR_BARS_2, + TEST_PATTERN_V_COLOR_BARS_4, + TEST_PATTERN_V_COLOR_BARS_8, +}; + +static const char *const ub953_tpg_qmenu[] = { + "Disabled", + "1 vertical color bar", + "2 vertical color bars", + "4 vertical color bars", + "8 vertical color bars", +}; + +static 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; + unsigned int src_pad; + 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; + src_pad = ret; + + ret = media_create_pad_link( + &source_subdev->entity, src_pad, &priv->sd.entity, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "Unable to link %s:%u -> %s:0\n", + source_subdev->name, src_pad, priv->sd.name); + return ret; + } + + 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 device_node *ep_node; + int ret; + + ep_node = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0); + if (!ep_node) { + dev_err(dev, "No graph endpoint\n"); + return -ENODEV; + } + + v4l2_async_nf_init(&priv->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&priv->notifier, + of_fwnode_handle(ep_node), + struct v4l2_async_subdev); + + of_node_put(ep_node); + + if (IS_ERR(asd)) { + dev_err(dev, "Failed to add subdev: %ld", PTR_ERR(asd)); + v4l2_async_nf_cleanup(&priv->notifier); + return PTR_ERR(asd); + } + + priv->notifier.ops = &ub953_notify_ops; + + ret = v4l2_async_subdev_nf_register(&priv->sd, &priv->notifier); + if (ret) { + dev_err(dev, "Failed to register subdev_notifier"); + v4l2_async_nf_cleanup(&priv->notifier); + return ret; + } + + return 0; +} + +static void ub953_v4l2_notifier_unregister(struct ub953_data *priv) +{ + 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->hw_data->ub971) { + switch (priv->mode) { + case UB953_MODE_SYNC: + return priv->plat_data->bc_rate * 160ull; + default: + /* Not supported */ + return 0; + } + } else { + switch (priv->mode) { + case UB953_MODE_SYNC: + return priv->plat_data->bc_rate / 2 * 160ull; + default: + /* Not supported */ + return 0; + } + } +} + +static unsigned long ub953_calc_clkout_ub953(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *hs_div, u8 *m, u8 *n) +{ + /* + * We always use 4 as a pre-divider (HS_CLK_DIV = 2). + * + * According to the datasheet: + * - "HS_CLK_DIV typically should be set to either 16, 8, or 4 (default)." + * - "if it is not possible to have an integer ratio of N/M, it is best to + * select a smaller value for HS_CLK_DIV. + * + * For above reasons the default HS_CLK_DIV seems the best in the average + * case. Use always that value to keep the code simple. + */ + static const unsigned long hs_clk_div = 4; + + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout = fc / hs_clk_div * m / n */ + + fc_divided = div_u64(fc, hs_clk_div); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res = div_u64(fc_divided * mul, div); + + *hs_div = hs_clk_div; + *m = mul; + *n = div; + + return res; +} + +static unsigned long ub953_calc_clkout_ub971(struct ub953_data *priv, + unsigned long target, u64 fc, + u8 *m, u8 *n) +{ + u64 fc_divided; + unsigned long mul, div; + unsigned long res; + + /* clkout = fc * m / (8 * n) */ + + fc_divided = div_u64(fc, 8); + + rational_best_approximation(target, fc_divided, (1 << 5) - 1, + (1 << 8) - 1, &mul, &div); + + res = div_u64(fc_divided * mul, div); + + *m = mul; + *n = div; + + return res; +} + +static unsigned long ub953_clkout_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct device *dev = &priv->client->dev; + u8 ctrl0, ctrl1; + u32 mul, div; + u64 fc_rate; + u32 hs_clk_div; + u64 rate; + int ret; + + ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL0, &ctrl0); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL0: %d\n", ret); + return 0; + } + + ret = ub953_read(priv, UB953_REG_CLKOUT_CTRL1, &ctrl1); + if (ret) { + dev_err(dev, "Failed to read CLKOUT_CTRL1: %d\n", ret); + return 0; + } + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->ub971) { + mul = ctrl0 & 0x1f; + div = ctrl1; + + if (div == 0) + return 0; + + rate = div_u64(fc_rate * mul, 8 * div); + + dev_dbg(dev, "clkout: fc rate %llu, mul %u, div %u = %llu\n", + fc_rate, mul, div, rate); + } else { + mul = ctrl0 & 0x1f; + hs_clk_div = 1 << (ctrl0 >> 5); + div = ctrl1; + + if (div == 0) + return 0; + + rate = div_u64(div_u64(fc_rate, hs_clk_div) * mul, div); + + dev_dbg(dev, + "clkout: fc rate %llu, hs_clk_div %u, mul %u, div %u = %llu\n", + fc_rate, hs_clk_div, mul, div, rate); + } + + return rate; +} + +static long ub953_clkout_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + struct device *dev = &priv->client->dev; + unsigned long res; + u64 fc_rate; + u8 hs_div, m, n; + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->ub971) { + res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n); + + dev_dbg(dev, + "%s %llu * %u / (8 * %u) = %lu (requested %lu)", + __func__, fc_rate, m, n, res, rate); + } else { + res = ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n); + + dev_dbg(dev, + "%s %llu / %u * %u / %u = %lu (requested %lu)", + __func__, fc_rate, hs_div, m, n, res, rate); + } + + return res; +} + +static int ub953_clkout_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct ub953_data *priv = container_of(hw, struct ub953_data, clkout_clk_hw); + u64 fc_rate; + u8 hs_div, m, n; + unsigned long res; + + fc_rate = ub953_get_fc_rate(priv); + + if (priv->hw_data->ub971) { + res = ub953_calc_clkout_ub971(priv, rate, fc_rate, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); + } else { + res = ub953_calc_clkout_ub953(priv, rate, fc_rate, &hs_div, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, (__ffs(hs_div) << 5) | m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); + } + + dev_dbg(&priv->client->dev, + "%s %lu (requested %lu)\n", __func__, res, rate); + + return 0; +} + +static const struct clk_ops ub953_clkout_ops = { + .recalc_rate = ub953_clkout_recalc_rate, + .round_rate = ub953_clkout_round_rate, + .set_rate = ub953_clkout_set_rate, +}; + +static void ub953_init_clkout_ub953(struct ub953_data *priv) +{ + u64 fc_rate; + u8 hs_div, m, n; + + fc_rate = ub953_get_fc_rate(priv); + + ub953_calc_clkout_ub953(priv, 25000000, fc_rate, &hs_div, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, (__ffs(hs_div) << 5) | m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); +} + +static void ub953_init_clkout_ub971(struct ub953_data *priv) +{ + u64 fc_rate; + u8 m, n; + + fc_rate = ub953_get_fc_rate(priv); + + ub953_calc_clkout_ub971(priv, 25000000, fc_rate, &m, &n); + + ub953_write(priv, UB953_REG_CLKOUT_CTRL0, m); + ub953_write(priv, UB953_REG_CLKOUT_CTRL1, n); +} + +static int ub953_register_clkout(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + const struct clk_init_data init = { + .name = kasprintf(GFP_KERNEL, "ds90%s.%s.clk_out", + priv->hw_data->model, dev_name(dev)), + .ops = &ub953_clkout_ops, + }; + int ret; + + /* Initialize clkout to 25MHz by default */ + if (priv->hw_data->ub971) + ub953_init_clkout_ub971(priv); + else + ub953_init_clkout_ub953(priv); + + priv->clkout_clk_hw.init = &init; + + ret = devm_clk_hw_register(dev, &priv->clkout_clk_hw); + kfree(init.name); + if (ret) + return dev_err_probe(dev, ret, "Cannot register clock HW\n"); + + ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, + &priv->clkout_clk_hw); + if (ret) + return dev_err_probe(dev, ret, + "Cannot add OF clock provider\n"); + + return 0; +} + +static int ub953_add_i2c_adapter(struct ub953_data *priv) +{ + struct device *dev = &priv->client->dev; + struct fwnode_handle *i2c_handle; + int ret; + + i2c_handle = device_get_named_child_node(dev, "i2c"); + if (!i2c_handle) + return 0; + + ret = i2c_atr_add_adapter(priv->plat_data->atr, priv->plat_data->port, + i2c_handle); + + fwnode_handle_put(i2c_handle); + + if (ret) + return ret; + + 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_node *np = priv->client->dev.of_node; + struct device *dev = &priv->client->dev; + struct device_node *ep_np; + int ret; + + if (!np) { + dev_err(dev, "OF: no device tree node!\n"); + return -ENOENT; + } + + ep_np = of_graph_get_endpoint_by_regs(np, 0, 0); + if (!ep_np) { + dev_err(dev, "OF: no endpoint\n"); + return -ENOENT; + } + + ret = of_property_count_u32_elems(ep_np, "data-lanes"); + + of_node_put(ep_np); + + if (ret <= 0) { + dev_err(dev, "OF: failed to parse data-lanes: %d\n", ret); + return ret; + } + + if (ret != 1 && ret != 2 && ret != 4) { + dev_err(dev, "OF: bad number of data-lanes: %d\n", ret); + return -EINVAL; + } + + priv->num_data_lanes = ret; + + return 0; +} + +static int ub953_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ub953_data *priv; + int ret; + u8 v; + bool mode_override; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->client = client; + + priv->hw_data = of_device_get_match_data(dev); + if (!priv->hw_data) + return -ENODEV; + + priv->plat_data = dev_get_platdata(&client->dev); + if (!priv->plat_data) { + dev_err(dev, "Platform data missing\n"); + return -ENODEV; + } + + mutex_init(&priv->reg_lock); + + /* + * Initialize to invalid values so that the first reg writes will + * configure the target. + */ + priv->current_indirect_target = 0xff; + + priv->regmap = devm_regmap_init_i2c(client, &ub953_regmap_config); + if (IS_ERR(priv->regmap)) { + dev_err(dev, "Failed to init regmap\n"); + ret = PTR_ERR(priv->regmap); + goto err_mutex_destroy; + } + + ret = ub953_parse_dt(priv); + if (ret) + goto err_mutex_destroy; + + ret = ub953_read(priv, UB953_REG_MODE_SEL, &v); + if (ret) + goto err_mutex_destroy; + + if (!(v & UB953_REG_MODE_SEL_MODE_DONE)) { + dev_err(dev, "Mode value not stabilized\n"); + ret = -ENODEV; + goto err_mutex_destroy; + } + + 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: + dev_err(dev, "Illegal mode in mode register\n"); + ret = -ENODEV; + goto err_mutex_destroy; + } + + dev_dbg(dev, "mode from %s: %#x\n", mode_override ? "reg" : "strap", + priv->mode); + + if (priv->mode != UB953_MODE_SYNC) { + dev_err(dev, "Only synchronous mode supported\n"); + ret = -ENODEV; + goto err_mutex_destroy; + } + + ret = ub953_read(priv, UB953_REG_REV_MASK_ID, &v); + if (ret) { + dev_err(dev, "Failed to read revision: %d", ret); + goto err_mutex_destroy; + } + + dev_info(dev, "Found %s rev/mask %#04x\n", priv->hw_data->model, v); + + ret = ub953_read(priv, UB953_REG_GENERAL_CFG, &v); + if (ret) + goto err_mutex_destroy; + + dev_dbg(dev, "i2c strap setting %s V\n", + (v & UB953_REG_GENERAL_CFG_I2C_STRAP_MODE) ? "1.8" : "3.3"); + + ret = ub953_i2c_master_init(priv); + if (ret) { + dev_err(dev, "i2c init failed: %d\n", ret); + goto err_mutex_destroy; + } + + ret = ub953_gpiochip_probe(priv); + if (ret) { + dev_err(dev, "Failed to init gpiochip\n"); + goto err_mutex_destroy; + } + + ub953_write(priv, UB953_REG_GENERAL_CFG, + UB953_REG_GENERAL_CFG_CONT_CLK | + ((priv->num_data_lanes - 1) << UB953_REG_GENERAL_CFG_CSI_LANE_SEL_SHIFT) | + UB953_REG_GENERAL_CFG_CRC_TX_GEN_ENABLE); + + ret = ub953_register_clkout(priv); + if (ret) { + dev_err(dev, "Failed to register clkout\n"); + goto err_gpiochip_remove; + } + + v4l2_i2c_subdev_init(&priv->sd, priv->client, &ub953_subdev_ops); + + v4l2_ctrl_handler_init(&priv->ctrl_handler, + ARRAY_SIZE(ub953_tpg_qmenu) - 1); + priv->sd.ctrl_handler = &priv->ctrl_handler; + + v4l2_ctrl_new_std_menu_items(&priv->ctrl_handler, &ub953_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ub953_tpg_qmenu) - 1, 0, 0, + ub953_tpg_qmenu); + + if (priv->ctrl_handler.error) { + ret = priv->ctrl_handler.error; + goto err_gpiochip_remove; + } + + priv->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS | + V4L2_SUBDEV_FL_STREAMS; + priv->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + priv->sd.entity.ops = &ub953_entity_ops; + + priv->pads[0].flags = MEDIA_PAD_FL_SINK; + priv->pads[1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->sd.entity, 2, priv->pads); + if (ret) { + dev_err(dev, "Failed to init pads\n"); + goto err_remove_ctrls; + } + + priv->tx_ep_np = of_graph_get_endpoint_by_regs(dev->of_node, 1, 0); + priv->sd.fwnode = of_fwnode_handle(priv->tx_ep_np); + + priv->sd.state_lock = priv->sd.ctrl_handler->lock; + + ret = v4l2_subdev_init_finalize(&priv->sd); + if (ret) + goto err_entity_cleanup; + + ret = ub953_v4l2_notifier_register(priv); + if (ret) { + dev_err(dev, "v4l2 subdev notifier register failed: %d\n", ret); + goto err_free_state; + } + + ret = v4l2_async_register_subdev(&priv->sd); + if (ret) { + dev_err(dev, "v4l2_async_register_subdev error: %d\n", ret); + goto err_unreg_notif; + } + + ret = ub953_add_i2c_adapter(priv); + if (ret) { + dev_err(dev, "failed to add remote i2c adapter\n"); + goto err_unreg_async_subdev; + } + + return 0; + +err_unreg_async_subdev: + v4l2_async_unregister_subdev(&priv->sd); +err_unreg_notif: + ub953_v4l2_notifier_unregister(priv); +err_free_state: + v4l2_subdev_cleanup(&priv->sd); +err_entity_cleanup: + if (priv->tx_ep_np) + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); +err_remove_ctrls: + v4l2_ctrl_handler_free(&priv->ctrl_handler); +err_gpiochip_remove: + ub953_gpiochip_remove(priv); +err_mutex_destroy: + mutex_destroy(&priv->reg_lock); + + return ret; +} + +static void ub953_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct ub953_data *priv = sd_to_ub953(sd); + + i2c_atr_del_adapter(priv->plat_data->atr, + priv->plat_data->port); + + v4l2_async_unregister_subdev(&priv->sd); + + ub953_v4l2_notifier_unregister(priv); + + v4l2_subdev_cleanup(&priv->sd); + + of_node_put(priv->tx_ep_np); + + media_entity_cleanup(&priv->sd.entity); + + v4l2_ctrl_handler_free(&priv->ctrl_handler); + + ub953_gpiochip_remove(priv); + mutex_destroy(&priv->reg_lock); +} + +static const struct ub953_hw_data ds90ub953_hw = { + .model = "ub953", +}; + +static const struct ub953_hw_data ds90ub971_hw = { + .model = "ub971", + .ub971 = true, +}; + +static const struct i2c_device_id ub953_id[] = { + { "ds90ub953-q1", 0 }, + { "ds90ub971-q1", 0 }, + {} +}; +MODULE_DEVICE_TABLE(i2c, ub953_id); + +#ifdef CONFIG_OF +static const struct of_device_id ub953_dt_ids[] = { + { .compatible = "ti,ds90ub953-q1", .data = &ds90ub953_hw }, + { .compatible = "ti,ds90ub971-q1", .data = &ds90ub971_hw }, + {} +}; +MODULE_DEVICE_TABLE(of, ub953_dt_ids); +#endif + +static struct i2c_driver ds90ub953_driver = { + .probe_new = ub953_probe, + .remove = ub953_remove, + .id_table = ub953_id, + .driver = { + .name = "ds90ub953", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ub953_dt_ids), + }, +}; + +module_i2c_driver(ds90ub953_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Texas Instruments DS90UB953 serializer driver"); +MODULE_AUTHOR("Luca Ceresoli "); +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_IMPORT_NS(I2C_ATR);