From nobody Sat Feb 7 15:12:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 251CEC77B7A for ; Thu, 25 May 2023 08:32:22 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S238906AbjEYIcT convert rfc822-to-8bit (ORCPT ); Thu, 25 May 2023 04:32:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56134 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239269AbjEYIcN (ORCPT ); Thu, 25 May 2023 04:32:13 -0400 Received: from ex01.ufhost.com (ex01.ufhost.com [61.152.239.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id C807F186; Thu, 25 May 2023 01:32:05 -0700 (PDT) Received: from EXMBX166.cuchost.com (unknown [175.102.18.54]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "EXMBX166", Issuer "EXMBX166" (not verified)) by ex01.ufhost.com (Postfix) with ESMTP id 9AF2F24E206; Thu, 25 May 2023 16:32:04 +0800 (CST) Received: from EXMBX173.cuchost.com (172.16.6.93) by EXMBX166.cuchost.com (172.16.6.76) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:04 +0800 Received: from xiaofei.localdomain (180.164.60.184) by EXMBX173.cuchost.com (172.16.6.93) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:03 +0800 From: Jack Zhu To: Mauro Carvalho Chehab , Robert Foss , Todor Tomov , , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , Philipp Zabel , "Laurent Pinchart" , Hans Verkuil , Eugen Hristev , Ezequiel Garcia CC: , , , , Subject: [PATCH v6 1/6] media: dt-bindings: Add JH7110 Camera Subsystem Date: Thu, 25 May 2023 16:31:57 +0800 Message-ID: <20230525083202.67933-2-jack.zhu@starfivetech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230525083202.67933-1-jack.zhu@starfivetech.com> References: <20230525083202.67933-1-jack.zhu@starfivetech.com> MIME-Version: 1.0 X-Originating-IP: [180.164.60.184] X-ClientProxiedBy: EXCAS066.cuchost.com (172.16.6.26) To EXMBX173.cuchost.com (172.16.6.93) X-YovoleRuleAgent: yovoleflag Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add the bindings documentation for Starfive JH7110 Camera Subsystem which is used for handing image sensor data. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Jack Zhu Reviewed-by: Laurent Pinchart --- .../bindings/media/starfive,jh7110-camss.yaml | 180 ++++++++++++++++++ MAINTAINERS | 7 + 2 files changed, 187 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/starfive,jh7110= -camss.yaml diff --git a/Documentation/devicetree/bindings/media/starfive,jh7110-camss.= yaml b/Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml new file mode 100644 index 000000000000..c66586d90fa2 --- /dev/null +++ b/Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml @@ -0,0 +1,180 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/starfive,jh7110-camss.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Starfive SoC CAMSS ISP + +maintainers: + - Jack Zhu + - Changhuang Liang + +description: + The Starfive CAMSS ISP is a Camera interface for Starfive JH7110 SoC. It + consists of a VIN controller (Video In Controller, a top-level control u= nit) + and an ISP. + +properties: + compatible: + const: starfive,jh7110-camss + + reg: + maxItems: 2 + + reg-names: + items: + - const: syscon + - const: isp + + clocks: + maxItems: 7 + + clock-names: + items: + - const: apb_func + - const: wrapper_clk_c + - const: dvp_inv + - const: axiwr + - const: mipi_rx0_pxl + - const: ispcore_2x + - const: isp_axi + + resets: + maxItems: 6 + + reset-names: + items: + - const: wrapper_p + - const: wrapper_c + - const: axird + - const: axiwr + - const: isp_top_n + - const: isp_top_axi + + power-domains: + items: + - description: JH7110 ISP Power Domain Switch Controller. + + interrupts: + maxItems: 4 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: Input port for receiving DVP data. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + bus-type: + enum: [5, 6] + + bus-width: + enum: [8, 10, 12] + + data-shift: + enum: [0, 2] + default: 0 + + hsync-active: + enum: [0, 1] + default: 1 + + vsync-active: + enum: [0, 1] + default: 1 + + required: + - bus-type + - bus-width + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: Input port for receiving CSI data. + + required: + - port@0 + - port@1 + +required: + - compatible + - reg + - reg-names + - clocks + - clock-names + - resets + - reset-names + - power-domains + - interrupts + - ports + +additionalProperties: false + +examples: + - | + isp@19840000 { + compatible =3D "starfive,jh7110-camss"; + reg =3D <0x19840000 0x10000>, + <0x19870000 0x30000>; + reg-names =3D "syscon", "isp"; + clocks =3D <&ispcrg 0>, + <&ispcrg 13>, + <&ispcrg 2>, + <&ispcrg 12>, + <&ispcrg 1>, + <&syscrg 51>, + <&syscrg 52>; + clock-names =3D "apb_func", + "wrapper_clk_c", + "dvp_inv", + "axiwr", + "mipi_rx0_pxl", + "ispcore_2x", + "isp_axi"; + resets =3D <&ispcrg 0>, + <&ispcrg 1>, + <&ispcrg 10>, + <&ispcrg 11>, + <&syscrg 41>, + <&syscrg 42>; + reset-names =3D "wrapper_p", + "wrapper_c", + "axird", + "axiwr", + "isp_top_n", + "isp_top_axi"; + power-domains =3D <&pwrc 5>; + interrupts =3D <92>, <87>, <88>, <90>; + + ports { + #address-cells =3D <1>; + #size-cells =3D <0>; + port@0 { + reg =3D <0>; + vin_from_sc2235: endpoint { + remote-endpoint =3D <&sc2235_to_vin>; + bus-type =3D <5>; + bus-width =3D <8>; + data-shift =3D <2>; + hsync-active =3D <1>; + vsync-active =3D <0>; + pclk-sample =3D <1>; + }; + }; + + port@1 { + reg =3D <1>; + vin_from_csi2rx: endpoint { + remote-endpoint =3D <&csi2rx_to_vin>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 869e1c7fc869..9ef5d0e5dff0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20070,6 +20070,13 @@ M: Ion Badulescu S: Odd Fixes F: drivers/net/ethernet/adaptec/starfire* =20 +STARFIVE CAMERA SUBSYSTEM DRIVER +M: Jack Zhu +M: Changhuang Liang +L: linux-media@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml + STARFIVE DEVICETREES M: Emil Renner Berthing S: Maintained --=20 2.34.1 From nobody Sat Feb 7 15:12:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C0487C7EE2E for ; Thu, 25 May 2023 08:32:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240157AbjEYIck convert rfc822-to-8bit (ORCPT ); Thu, 25 May 2023 04:32:40 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56178 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239950AbjEYIcP (ORCPT ); Thu, 25 May 2023 04:32:15 -0400 Received: from fd01.gateway.ufhost.com (fd01.gateway.ufhost.com [61.152.239.71]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 6C0F31A1; Thu, 25 May 2023 01:32:11 -0700 (PDT) Received: from EXMBX165.cuchost.com (unknown [175.102.18.54]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "EXMBX165", Issuer "EXMBX165" (not verified)) by fd01.gateway.ufhost.com (Postfix) with ESMTP id DD45083E4; Thu, 25 May 2023 16:32:04 +0800 (CST) Received: from EXMBX173.cuchost.com (172.16.6.93) by EXMBX165.cuchost.com (172.16.6.75) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:04 +0800 Received: from xiaofei.localdomain (180.164.60.184) by EXMBX173.cuchost.com (172.16.6.93) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:04 +0800 From: Jack Zhu To: Mauro Carvalho Chehab , Robert Foss , Todor Tomov , , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , Philipp Zabel , "Laurent Pinchart" , Hans Verkuil , Eugen Hristev , Ezequiel Garcia CC: , , , , Subject: [PATCH v6 2/6] media: admin-guide: Add starfive_camss.rst for Starfive Camera Subsystem Date: Thu, 25 May 2023 16:31:58 +0800 Message-ID: <20230525083202.67933-3-jack.zhu@starfivetech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230525083202.67933-1-jack.zhu@starfivetech.com> References: <20230525083202.67933-1-jack.zhu@starfivetech.com> MIME-Version: 1.0 X-Originating-IP: [180.164.60.184] X-ClientProxiedBy: EXCAS066.cuchost.com (172.16.6.26) To EXMBX173.cuchost.com (172.16.6.93) X-YovoleRuleAgent: yovoleflag Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add starfive_camss.rst file that documents the Starfive Camera Subsystem driver which is used for handing image sensor data. Signed-off-by: Jack Zhu --- .../admin-guide/media/starfive_camss.rst | 57 +++++++++++++++++++ .../media/starfive_camss_graph.dot | 16 ++++++ .../admin-guide/media/v4l-drivers.rst | 1 + MAINTAINERS | 1 + 4 files changed, 75 insertions(+) create mode 100644 Documentation/admin-guide/media/starfive_camss.rst create mode 100644 Documentation/admin-guide/media/starfive_camss_graph.dot diff --git a/Documentation/admin-guide/media/starfive_camss.rst b/Documenta= tion/admin-guide/media/starfive_camss.rst new file mode 100644 index 000000000000..a6378849384f --- /dev/null +++ b/Documentation/admin-guide/media/starfive_camss.rst @@ -0,0 +1,57 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D +Starfive Camera Subsystem driver +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D + +Introduction +------------ + +This file documents the driver for the Starfive Camera Subsystem found on +Starfive JH7110 SoC. The driver is located under drivers/media/platform/ +starfive. + +The driver implements V4L2, Media controller and v4l2_subdev interfaces. +Camera sensor using V4L2 subdev interface in the kernel is supported. + +The driver has been successfully used on the Gstreamer 1.18.5 with +v4l2src plugin. + + +Starfive Camera Subsystem hardware +---------------------------------- + +The Starfive Camera Subsystem hardware consists of: + +- MIPI DPHY Receiver: receives mipi data from a MIPI camera sensor. +- MIPI CSIRx Controller: is responsible for handling and decoding CSI2 pro= tocol + based camera sensor data stream. +- ISP: handles the image data streams from the MIPI CSIRx Controller. +- VIN(Video In): a top-level module, is responsible for controlling power + and clocks to other modules, dumps the input data to memory or transfers= the + input data to ISP. + + +Topology +-------- + +The media controller pipeline graph is as follows: + +.. _starfive_camss_graph: + +.. kernel-figure:: starfive_camss_graph.dot + :alt: starfive_camss_graph.dot + :align: center + +The driver has 2 video devices: + +- stf_vin0_wr_video0: capture device for images directly from the VIN modu= le. +- stf_vin0_isp0_video1: capture device for images without scaling. + +The driver has 3 subdevices: + +- stf_isp0: is responsible for all the isp operations. +- stf_vin0_wr: used to dump RAW images to memory. +- stf_vin0_isp0: used to capture images for the stf_vin0_isp0_video1 devic= e. diff --git a/Documentation/admin-guide/media/starfive_camss_graph.dot b/Doc= umentation/admin-guide/media/starfive_camss_graph.dot new file mode 100644 index 000000000000..850be912cdeb --- /dev/null +++ b/Documentation/admin-guide/media/starfive_camss_graph.dot @@ -0,0 +1,16 @@ +digraph board { + rankdir=3DTB + n00000001 [label=3D"{{ 0} | stf_isp0\n/dev/v4l-subdev0 | { = 1}}", shape=3DMrecord, style=3Dfilled, fillcolor=3Dgreen] + n00000001:port1 -> n0000000d:port0 + n00000004 [label=3D"{{ 0} | stf_vin0_wr\n/dev/v4l-subdev1 | { 1}}", shape=3DMrecord, style=3Dfilled, fillcolor=3Dgreen] + n00000004:port1 -> n00000007 [style=3Dbold] + n00000007 [label=3D"stf_vin0_wr_video0\n/dev/video0", shape=3Dbox, style= =3Dfilled, fillcolor=3Dyellow] + n0000000d [label=3D"{{ 0} | stf_vin0_isp0\n/dev/v4l-subdev2 | { 1}}", shape=3DMrecord, style=3Dfilled, fillcolor=3Dgreen] + n0000000d:port1 -> n00000010 [style=3Dbold] + n00000010 [label=3D"stf_vin0_isp0_video1\n/dev/video1", shape=3Dbox, styl= e=3Dfilled, fillcolor=3Dyellow] + n00000018 [label=3D"{{ 0} | cdns_csi2rx.19800000.csi-bridge\n | {<= port1> 1 | 2 | 3 | 4}}", shape=3DMrecord, style=3Df= illed, fillcolor=3Dgreen] + n00000018:port1 -> n00000004:port0 [style=3Ddashed] + n00000018:port1 -> n00000001:port0 + n00000028 [label=3D"{{} | imx219 6-0010\n | { 0}}", shape=3DMrecor= d, style=3Dfilled, fillcolor=3Dgreen] + n00000028:port0 -> n00000018:port0 [style=3Dbold] +} diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentatio= n/admin-guide/media/v4l-drivers.rst index 1c41f87c3917..2274fd29c1d7 100644 --- a/Documentation/admin-guide/media/v4l-drivers.rst +++ b/Documentation/admin-guide/media/v4l-drivers.rst @@ -27,6 +27,7 @@ Video4Linux (V4L) driver-specific documentation si470x si4713 si476x + starfive_camss vimc visl vivid diff --git a/MAINTAINERS b/MAINTAINERS index 9ef5d0e5dff0..71291dc58671 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20075,6 +20075,7 @@ M: Jack Zhu M: Changhuang Liang L: linux-media@vger.kernel.org S: Maintained +F: Documentation/admin-guide/media/starfive_camss.rst F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml =20 STARFIVE DEVICETREES --=20 2.34.1 From nobody Sat Feb 7 15:12:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id B20A2C7EE37 for ; Thu, 25 May 2023 08:32:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240094AbjEYIcX convert rfc822-to-8bit (ORCPT ); Thu, 25 May 2023 04:32:23 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56154 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239698AbjEYIcN (ORCPT ); Thu, 25 May 2023 04:32:13 -0400 Received: from fd01.gateway.ufhost.com (fd01.gateway.ufhost.com [61.152.239.71]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 616ED195; Thu, 25 May 2023 01:32:07 -0700 (PDT) Received: from EXMBX166.cuchost.com (unknown [175.102.18.54]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "EXMBX166", Issuer "EXMBX166" (not verified)) by fd01.gateway.ufhost.com (Postfix) with ESMTP id 2A41C83E7; Thu, 25 May 2023 16:32:05 +0800 (CST) Received: from EXMBX173.cuchost.com (172.16.6.93) by EXMBX166.cuchost.com (172.16.6.76) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:05 +0800 Received: from xiaofei.localdomain (180.164.60.184) by EXMBX173.cuchost.com (172.16.6.93) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:04 +0800 From: Jack Zhu To: Mauro Carvalho Chehab , Robert Foss , Todor Tomov , , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , Philipp Zabel , "Laurent Pinchart" , Hans Verkuil , Eugen Hristev , Ezequiel Garcia CC: , , , , Subject: [PATCH v6 3/6] media: starfive: Add basic driver Date: Thu, 25 May 2023 16:31:59 +0800 Message-ID: <20230525083202.67933-4-jack.zhu@starfivetech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230525083202.67933-1-jack.zhu@starfivetech.com> References: <20230525083202.67933-1-jack.zhu@starfivetech.com> MIME-Version: 1.0 X-Originating-IP: [180.164.60.184] X-ClientProxiedBy: EXCAS066.cuchost.com (172.16.6.26) To EXMBX173.cuchost.com (172.16.6.93) X-YovoleRuleAgent: yovoleflag Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add basic platform driver for StarFive Camera Subsystem. Reviewed-by: Bryan O'Donoghue Signed-off-by: Jack Zhu --- MAINTAINERS | 1 + drivers/media/platform/Kconfig | 1 + drivers/media/platform/Makefile | 1 + drivers/media/platform/starfive/Kconfig | 19 + drivers/media/platform/starfive/Makefile | 9 + drivers/media/platform/starfive/stf_camss.c | 372 +++++++++++++++++++ drivers/media/platform/starfive/stf_camss.h | 153 ++++++++ drivers/media/platform/starfive/stf_common.h | 18 + 8 files changed, 574 insertions(+) create mode 100644 drivers/media/platform/starfive/Kconfig create mode 100644 drivers/media/platform/starfive/Makefile create mode 100644 drivers/media/platform/starfive/stf_camss.c create mode 100644 drivers/media/platform/starfive/stf_camss.h create mode 100644 drivers/media/platform/starfive/stf_common.h diff --git a/MAINTAINERS b/MAINTAINERS index 71291dc58671..33a752569942 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20077,6 +20077,7 @@ L: linux-media@vger.kernel.org S: Maintained F: Documentation/admin-guide/media/starfive_camss.rst F: Documentation/devicetree/bindings/media/starfive,jh7110-camss.yaml +F: drivers/media/platform/starfive/ =20 STARFIVE DEVICETREES M: Emil Renner Berthing diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig index ee579916f874..627eaa0ab3ee 100644 --- a/drivers/media/platform/Kconfig +++ b/drivers/media/platform/Kconfig @@ -80,6 +80,7 @@ source "drivers/media/platform/renesas/Kconfig" source "drivers/media/platform/rockchip/Kconfig" source "drivers/media/platform/samsung/Kconfig" source "drivers/media/platform/st/Kconfig" +source "drivers/media/platform/starfive/Kconfig" source "drivers/media/platform/sunxi/Kconfig" source "drivers/media/platform/ti/Kconfig" source "drivers/media/platform/verisilicon/Kconfig" diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makef= ile index 5453bb868e67..5a038498a370 100644 --- a/drivers/media/platform/Makefile +++ b/drivers/media/platform/Makefile @@ -23,6 +23,7 @@ obj-y +=3D renesas/ obj-y +=3D rockchip/ obj-y +=3D samsung/ obj-y +=3D st/ +obj-y +=3D starfive/ obj-y +=3D sunxi/ obj-y +=3D ti/ obj-y +=3D verisilicon/ diff --git a/drivers/media/platform/starfive/Kconfig b/drivers/media/platfo= rm/starfive/Kconfig new file mode 100644 index 000000000000..1a2293475bd2 --- /dev/null +++ b/drivers/media/platform/starfive/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Starfive media platform drivers" + +config VIDEO_STARFIVE_CAMSS + tristate "Starfive Camera Subsystem driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on DMA_CMA + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Enable this to support for the Starfive Camera subsystem + found on Starfive JH7110 SoC. + + To compile this driver as a module, choose M here: the + module will be called stf-camss. diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platf= orm/starfive/Makefile new file mode 100644 index 000000000000..796775fa52f4 --- /dev/null +++ b/drivers/media/platform/starfive/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for StarFive camera subsystem driver. +# + +starfive-camss-objs +=3D \ + stf_camss.o + +obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) +=3D starfive-camss.o \ diff --git a/drivers/media/platform/starfive/stf_camss.c b/drivers/media/pl= atform/starfive/stf_camss.c new file mode 100644 index 000000000000..cb2b62d49416 --- /dev/null +++ b/drivers/media/platform/starfive/stf_camss.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_camss.c + * + * Starfive Camera Subsystem driver + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "stf_camss.h" + +static const char * const stfcamss_clocks[] =3D { + "clk_apb_func", + "clk_wrapper_clk_c", + "clk_dvp_inv", + "clk_axiwr", + "clk_mipi_rx0_pxl", + "clk_ispcore_2x", + "clk_isp_axi", +}; + +static const char * const stfcamss_resets[] =3D { + "rst_wrapper_p", + "rst_wrapper_c", + "rst_axird", + "rst_axiwr", + "rst_isp_top_n", + "rst_isp_top_axi", +}; + +static int stfcamss_get_mem_res(struct platform_device *pdev, + struct stfcamss *stfcamss) +{ + stfcamss->syscon_base =3D + devm_platform_ioremap_resource_byname(pdev, "syscon"); + if (IS_ERR(stfcamss->syscon_base)) + return PTR_ERR(stfcamss->syscon_base); + + stfcamss->isp_base =3D + devm_platform_ioremap_resource_byname(pdev, "isp"); + if (IS_ERR(stfcamss->isp_base)) + return PTR_ERR(stfcamss->isp_base); + + return 0; +} + +/* + * stfcamss_of_parse_endpoint_node - Parse port endpoint node + * @dev: Device + * @node: Device node to be parsed + * @csd: Parsed data from port endpoint node + * + * Return 0 on success or a negative error code on failure + */ +static int stfcamss_of_parse_endpoint_node(struct device *dev, + struct device_node *node, + struct stfcamss_async_subdev *csd) +{ + struct v4l2_fwnode_endpoint vep =3D { { 0 } }; + + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); + dev_dbg(dev, "vep.base.port =3D 0x%x, id =3D 0x%x\n", + vep.base.port, vep.base.id); + + csd->port =3D vep.base.port; + + return 0; +} + +/* + * stfcamss_of_parse_ports - Parse ports node + * @stfcamss: STFCAMSS device + * + * Return number of "port" nodes found in "ports" node + */ +static int stfcamss_of_parse_ports(struct stfcamss *stfcamss) +{ + struct device *dev =3D stfcamss->dev; + struct device_node *node =3D NULL; + struct device_node *remote =3D NULL; + int ret, num_subdevs =3D 0; + + for_each_endpoint_of_node(dev->of_node, node) { + struct stfcamss_async_subdev *csd; + + if (!of_device_is_available(node)) + continue; + + remote =3D of_graph_get_remote_port_parent(node); + if (!remote) { + dev_err(dev, "Cannot get remote parent\n"); + ret =3D -EINVAL; + goto err_cleanup; + } + + csd =3D v4l2_async_nf_add_fwnode(&stfcamss->notifier, + of_fwnode_handle(remote), + struct stfcamss_async_subdev); + of_node_put(remote); + if (IS_ERR(csd)) { + ret =3D PTR_ERR(csd); + goto err_cleanup; + } + + ret =3D stfcamss_of_parse_endpoint_node(dev, node, csd); + if (ret < 0) + goto err_cleanup; + + num_subdevs++; + } + + return num_subdevs; + +err_cleanup: + of_node_put(node); + return ret; +} + +static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *asyn= c, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct stfcamss *stfcamss =3D + container_of(async, struct stfcamss, notifier); + struct host_data *host_data =3D &stfcamss->host_data; + struct media_entity *source; + int i, j; + + source =3D &subdev->entity; + + for (i =3D 0; i < source->num_pads; i++) { + if (source->pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + + if (i =3D=3D source->num_pads) { + dev_err(stfcamss->dev, "No source pad in external entity\n"); + return -EINVAL; + } + + for (j =3D 0; host_data->host_entity[j] && (j < HOST_ENTITY_MAX); j++) { + struct media_entity *input; + int ret; + + input =3D host_data->host_entity[j]; + + ret =3D media_create_pad_link( + source, + i, + input, + STF_PAD_SINK, + source->function =3D=3D MEDIA_ENT_F_CAM_SENSOR ? + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED : + 0); + if (ret < 0) { + dev_err(stfcamss->dev, + "Failed to link %s->%s entities: %d\n", + source->name, input->name, ret); + return ret; + } + } + + return 0; +} + +static int stfcamss_subdev_notifier_complete(struct v4l2_async_notifier *n= tf) +{ + struct stfcamss *stfcamss =3D + container_of(ntf, struct stfcamss, notifier); + + return v4l2_device_register_subdev_nodes(&stfcamss->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations +stfcamss_subdev_notifier_ops =3D { + .bound =3D stfcamss_subdev_notifier_bound, + .complete =3D stfcamss_subdev_notifier_complete, +}; + +static const struct media_device_ops stfcamss_media_ops =3D { + .link_notify =3D v4l2_pipeline_link_notify, +}; + +static void stfcamss_mc_init(struct platform_device *pdev, + struct stfcamss *stfcamss) +{ + stfcamss->media_dev.dev =3D stfcamss->dev; + strscpy(stfcamss->media_dev.model, "Starfive Camera Subsystem", + sizeof(stfcamss->media_dev.model)); + snprintf(stfcamss->media_dev.bus_info, + sizeof(stfcamss->media_dev.bus_info), + "%s:%s", dev_bus_name(&pdev->dev), pdev->name); + stfcamss->media_dev.hw_revision =3D 0x01; + stfcamss->media_dev.ops =3D &stfcamss_media_ops; + media_device_init(&stfcamss->media_dev); + + stfcamss->v4l2_dev.mdev =3D &stfcamss->media_dev; +} + +/* + * stfcamss_probe - Probe STFCAMSS platform device + * @pdev: Pointer to STFCAMSS platform device + * + * Return 0 on success or a negative error code on failure + */ +static int stfcamss_probe(struct platform_device *pdev) +{ + struct stfcamss *stfcamss; + struct device *dev =3D &pdev->dev; + int ret =3D 0, i, num_subdevs; + + stfcamss =3D devm_kzalloc(dev, sizeof(*stfcamss), GFP_KERNEL); + if (!stfcamss) + return -ENOMEM; + + for (i =3D 0; i < ARRAY_SIZE(stfcamss->irq); ++i) { + stfcamss->irq[i] =3D platform_get_irq(pdev, i); + if (stfcamss->irq[i] < 0) + return dev_err_probe(&pdev->dev, stfcamss->irq[i], + "Failed to get clock%d", i); + } + + stfcamss->nclks =3D ARRAY_SIZE(stfcamss->sys_clk); + for (i =3D 0; i < stfcamss->nclks; ++i) + stfcamss->sys_clk[i].id =3D stfcamss_clocks[i]; + ret =3D devm_clk_bulk_get(dev, stfcamss->nclks, stfcamss->sys_clk); + if (ret) { + dev_err(dev, "Failed to get clk controls\n"); + return ret; + } + + stfcamss->nrsts =3D ARRAY_SIZE(stfcamss->sys_rst); + for (i =3D 0; i < stfcamss->nrsts; ++i) + stfcamss->sys_rst[i].id =3D stfcamss_resets[i]; + ret =3D devm_reset_control_bulk_get_shared(dev, stfcamss->nrsts, + stfcamss->sys_rst); + if (ret) { + dev_err(dev, "Failed to get reset controls\n"); + return ret; + } + + ret =3D stfcamss_get_mem_res(pdev, stfcamss); + if (ret) { + dev_err(dev, "Could not map registers\n"); + return ret; + } + + stfcamss->dev =3D dev; + platform_set_drvdata(pdev, stfcamss); + + v4l2_async_nf_init(&stfcamss->notifier); + + num_subdevs =3D stfcamss_of_parse_ports(stfcamss); + if (num_subdevs < 0) { + dev_err(dev, "Failed to find subdevices\n"); + return -ENODEV; + } + + stfcamss_mc_init(pdev, stfcamss); + + ret =3D v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev); + if (ret < 0) { + dev_err(dev, "Failed to register V4L2 device: %d\n", ret); + goto err_cleanup_notifier; + } + + ret =3D media_device_register(&stfcamss->media_dev); + if (ret) { + dev_err(dev, "Failed to register media device: %d\n", ret); + goto err_unregister_device; + } + + stfcamss->notifier.ops =3D &stfcamss_subdev_notifier_ops; + ret =3D v4l2_async_nf_register(&stfcamss->v4l2_dev, &stfcamss->notifier); + if (ret) { + dev_err(dev, "Failed to register async subdev nodes: %d\n", + ret); + goto err_unregister_media_dev; + } + + pm_runtime_enable(dev); + + return 0; + +err_unregister_media_dev: + media_device_unregister(&stfcamss->media_dev); +err_unregister_device: + v4l2_device_unregister(&stfcamss->v4l2_dev); +err_cleanup_notifier: + v4l2_async_nf_cleanup(&stfcamss->notifier); + return ret; +} + +/* + * stfcamss_remove - Remove STFCAMSS platform device + * @pdev: Pointer to STFCAMSS platform device + * + * Always returns 0. + */ +static int stfcamss_remove(struct platform_device *pdev) +{ + struct stfcamss *stfcamss =3D platform_get_drvdata(pdev); + + v4l2_device_unregister(&stfcamss->v4l2_dev); + media_device_cleanup(&stfcamss->media_dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id stfcamss_of_match[] =3D { + { .compatible =3D "starfive,jh7110-camss" }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, stfcamss_of_match); + +static int __maybe_unused stfcamss_runtime_suspend(struct device *dev) +{ + struct stfcamss *stfcamss =3D dev_get_drvdata(dev); + + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); + reset_control_assert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); + + return 0; +} + +static int __maybe_unused stfcamss_runtime_resume(struct device *dev) +{ + struct stfcamss *stfcamss =3D dev_get_drvdata(dev); + + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISPCORE_2X].clk); + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_ISP_AXI].clk); + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_N].rstc); + reset_control_deassert(stfcamss->sys_rst[STF_RST_ISP_TOP_AXI].rstc); + + return 0; +} + +static const struct dev_pm_ops stfcamss_pm_ops =3D { + SET_RUNTIME_PM_OPS(stfcamss_runtime_suspend, + stfcamss_runtime_resume, + NULL) +}; + +static struct platform_driver stfcamss_driver =3D { + .probe =3D stfcamss_probe, + .remove =3D stfcamss_remove, + .driver =3D { + .name =3D DRV_NAME, + .pm =3D &stfcamss_pm_ops, + .of_match_table =3D of_match_ptr(stfcamss_of_match), + }, +}; + +module_platform_driver(stfcamss_driver); + +MODULE_AUTHOR("StarFive Corporation"); +MODULE_DESCRIPTION("StarFive Camera Subsystem driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/starfive/stf_camss.h b/drivers/media/pl= atform/starfive/stf_camss.h new file mode 100644 index 000000000000..8f39cd65950c --- /dev/null +++ b/drivers/media/platform/starfive/stf_camss.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_camss.h + * + * Starfive Camera Subsystem driver + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_CAMSS_H +#define STF_CAMSS_H + +#include +#include +#include +#include + +#include "stf_common.h" + +#define DRV_NAME "starfive-camss" +#define STF_DVP_NAME "stf_dvp" +#define STF_CSI_NAME "cdns_csi2rx" +#define STF_ISP_NAME "stf_isp" +#define STF_VIN_NAME "stf_vin" + +#define STF_PAD_SINK 0 +#define STF_PAD_SRC 1 +#define STF_PADS_NUM 2 + +enum port_num { + PORT_NUMBER_DVP_SENSOR =3D 0, + PORT_NUMBER_CSI2RX +}; + +enum stf_clk { + STF_CLK_APB_FUNC =3D 0, + STF_CLK_WRAPPER_CLK_C, + STF_CLK_DVP_INV, + STF_CLK_AXIWR, + STF_CLK_MIPI_RX0_PXL, + STF_CLK_ISPCORE_2X, + STF_CLK_ISP_AXI, + STF_CLK_NUM +}; + +enum stf_rst { + STF_RST_WRAPPER_P =3D 0, + STF_RST_WRAPPER_C, + STF_RST_AXIRD, + STF_RST_AXIWR, + STF_RST_ISP_TOP_N, + STF_RST_ISP_TOP_AXI, + STF_RST_NUM +}; + +enum stf_irq { + STF_IRQ_VINWR =3D 0, + STF_IRQ_ISP, + STF_IRQ_ISPCSIL, + STF_IRQ_NUM +}; + +#define HOST_ENTITY_MAX 2 + +struct host_data { + struct media_entity *host_entity[HOST_ENTITY_MAX]; +}; + +struct stfcamss { + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct media_pipeline pipe; + struct device *dev; + struct v4l2_async_notifier notifier; + struct host_data host_data; + void __iomem *syscon_base; + void __iomem *isp_base; + int irq[STF_IRQ_NUM]; + struct clk_bulk_data sys_clk[STF_CLK_NUM]; + int nclks; + struct reset_control_bulk_data sys_rst[STF_RST_NUM]; + int nrsts; +}; + +struct stfcamss_async_subdev { + struct v4l2_async_subdev asd; /* must be first */ + enum port_num port; +}; + +static inline u32 stf_isp_reg_read(struct stfcamss *stfcamss, u32 reg) +{ + return ioread32(stfcamss->isp_base + reg); +} + +static inline void stf_isp_reg_write(struct stfcamss *stfcamss, + u32 reg, u32 val) +{ + iowrite32(val, stfcamss->isp_base + reg); +} + +static inline void stf_isp_reg_write_delay(struct stfcamss *stfcamss, + u32 reg, u32 val, u32 delay) +{ + iowrite32(val, stfcamss->isp_base + reg); + usleep_range(1000 * delay, 1000 * delay + 100); +} + +static inline void stf_isp_reg_set_bit(struct stfcamss *stfcamss, + u32 reg, u32 mask, u32 val) +{ + u32 value; + + value =3D ioread32(stfcamss->isp_base + reg) & ~mask; + val &=3D mask; + val |=3D value; + iowrite32(val, stfcamss->isp_base + reg); +} + +static inline void stf_isp_reg_set(struct stfcamss *stfcamss, u32 reg, u32= mask) +{ + iowrite32(ioread32(stfcamss->isp_base + reg) | mask, + stfcamss->isp_base + reg); +} + +static inline u32 stf_syscon_reg_read(struct stfcamss *stfcamss, u32 reg) +{ + return ioread32(stfcamss->syscon_base + reg); +} + +static inline void stf_syscon_reg_write(struct stfcamss *stfcamss, + u32 reg, u32 val) +{ + iowrite32(val, stfcamss->syscon_base + reg); +} + +static inline void stf_syscon_reg_set_bit(struct stfcamss *stfcamss, + u32 reg, u32 bit_mask) +{ + u32 value; + + value =3D ioread32(stfcamss->syscon_base + reg); + iowrite32(value | bit_mask, stfcamss->syscon_base + reg); +} + +static inline void stf_syscon_reg_clear_bit(struct stfcamss *stfcamss, + u32 reg, u32 bit_mask) +{ + u32 value; + + value =3D ioread32(stfcamss->syscon_base + reg); + iowrite32(value & ~bit_mask, stfcamss->syscon_base + reg); +} +#endif /* STF_CAMSS_H */ diff --git a/drivers/media/platform/starfive/stf_common.h b/drivers/media/p= latform/starfive/stf_common.h new file mode 100644 index 000000000000..e04fca2d9cd4 --- /dev/null +++ b/drivers/media/platform/starfive/stf_common.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_common.h + * + * StarFive Camera Subsystem - Common definitions + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_COMMON_H +#define STF_COMMON_H + +enum stf_subdev_type { + STF_SUBDEV_TYPE_VIN, + STF_SUBDEV_TYPE_ISP, +}; + +#endif /* STF_COMMON_H */ --=20 2.34.1 From nobody Sat Feb 7 15:12:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C05CAC7EE32 for ; Thu, 25 May 2023 08:32:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S239993AbjEYIcQ convert rfc822-to-8bit (ORCPT ); Thu, 25 May 2023 04:32:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56150 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239810AbjEYIcN (ORCPT ); Thu, 25 May 2023 04:32:13 -0400 Received: from ex01.ufhost.com (ex01.ufhost.com [61.152.239.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id B206B90; Thu, 25 May 2023 01:32:06 -0700 (PDT) Received: from EXMBX165.cuchost.com (unknown [175.102.18.54]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "EXMBX165", Issuer "EXMBX165" (not verified)) by ex01.ufhost.com (Postfix) with ESMTP id 7563124E21B; Thu, 25 May 2023 16:32:05 +0800 (CST) Received: from EXMBX173.cuchost.com (172.16.6.93) by EXMBX165.cuchost.com (172.16.6.75) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:05 +0800 Received: from xiaofei.localdomain (180.164.60.184) by EXMBX173.cuchost.com (172.16.6.93) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:04 +0800 From: Jack Zhu To: Mauro Carvalho Chehab , Robert Foss , Todor Tomov , , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , Philipp Zabel , "Laurent Pinchart" , Hans Verkuil , Eugen Hristev , Ezequiel Garcia CC: , , , , Subject: [PATCH v6 4/6] media: starfive: Add video driver Date: Thu, 25 May 2023 16:32:00 +0800 Message-ID: <20230525083202.67933-5-jack.zhu@starfivetech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230525083202.67933-1-jack.zhu@starfivetech.com> References: <20230525083202.67933-1-jack.zhu@starfivetech.com> MIME-Version: 1.0 X-Originating-IP: [180.164.60.184] X-ClientProxiedBy: EXCAS066.cuchost.com (172.16.6.26) To EXMBX173.cuchost.com (172.16.6.93) X-YovoleRuleAgent: yovoleflag Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add video driver for StarFive Camera Subsystem. Signed-off-by: Jack Zhu --- drivers/media/platform/starfive/Makefile | 3 +- drivers/media/platform/starfive/stf_video.c | 864 ++++++++++++++++++++ drivers/media/platform/starfive/stf_video.h | 95 +++ 3 files changed, 961 insertions(+), 1 deletion(-) create mode 100644 drivers/media/platform/starfive/stf_video.c create mode 100644 drivers/media/platform/starfive/stf_video.h diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platf= orm/starfive/Makefile index 796775fa52f4..d7a42b3a938c 100644 --- a/drivers/media/platform/starfive/Makefile +++ b/drivers/media/platform/starfive/Makefile @@ -4,6 +4,7 @@ # =20 starfive-camss-objs +=3D \ - stf_camss.o + stf_camss.o \ + stf_video.o =20 obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) +=3D starfive-camss.o \ diff --git a/drivers/media/platform/starfive/stf_video.c b/drivers/media/pl= atform/starfive/stf_video.c new file mode 100644 index 000000000000..f027c7308dfb --- /dev/null +++ b/drivers/media/platform/starfive/stf_video.c @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_video.c + * + * StarFive Camera Subsystem - V4L2 device node + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#include +#include +#include +#include +#include + +#include "stf_camss.h" +#include "stf_video.h" + +static const struct stfcamss_format_info formats_pix_wr[] =3D { + { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, +}; + +static const struct stfcamss_format_info formats_pix_isp[] =3D { + { MEDIA_BUS_FMT_Y12_1X12, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, +}; + +/* -----------------------------------------------------------------------= ------ + * Helper functions + */ + +static int video_find_format(u32 code, u32 pixelformat, + const struct stfcamss_format_info *formats, + unsigned int nformats) +{ + int i; + + for (i =3D 0; i < nformats; i++) { + if (formats[i].code =3D=3D code && + formats[i].pixelformat =3D=3D pixelformat) + return i; + } + + for (i =3D 0; i < nformats; i++) + if (formats[i].code =3D=3D code) + return i; + + for (i =3D 0; i < nformats; i++) + if (formats[i].pixelformat =3D=3D pixelformat) + return i; + + return -EINVAL; +} + +static int __video_try_fmt(struct stfcamss_video *video, struct v4l2_forma= t *f) +{ + struct v4l2_pix_format *pix; + const struct stfcamss_format_info *fi; + u32 width, height; + u32 bpl; + int i; + + pix =3D &f->fmt.pix; + + for (i =3D 0; i < video->nformats; i++) + if (pix->pixelformat =3D=3D video->formats[i].pixelformat) + break; + + if (i =3D=3D video->nformats) + i =3D 0; /* default format */ + + fi =3D &video->formats[i]; + width =3D pix->width; + height =3D pix->height; + + memset(pix, 0, sizeof(*pix)); + + pix->pixelformat =3D fi->pixelformat; + pix->width =3D clamp_t(u32, width, STFCAMSS_FRAME_MIN_WIDTH, + STFCAMSS_FRAME_MAX_WIDTH); + pix->height =3D clamp_t(u32, height, STFCAMSS_FRAME_MIN_HEIGHT, + STFCAMSS_FRAME_MAX_HEIGHT); + bpl =3D pix->width / fi->hsub[0].numerator * + fi->hsub[0].denominator * fi->bpp[0] / 8; + bpl =3D ALIGN(bpl, video->bpl_alignment); + pix->bytesperline =3D bpl; + pix->sizeimage =3D pix->height / fi->vsub[0].numerator * + fi->vsub[0].denominator * bpl; + + pix->field =3D V4L2_FIELD_NONE; + pix->colorspace =3D V4L2_COLORSPACE_SRGB; + pix->flags =3D 0; + pix->ycbcr_enc =3D + V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + pix->quantization =3D V4L2_MAP_QUANTIZATION_DEFAULT(true, + pix->colorspace, + pix->ycbcr_enc); + pix->xfer_func =3D V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + + return 0; +} + +static int stf_video_init_format(struct stfcamss_video *video) +{ + int ret; + struct v4l2_format format =3D { + .type =3D video->type, + .fmt.pix =3D { + .width =3D 1920, + .height =3D 1080, + .pixelformat =3D V4L2_PIX_FMT_RGB565, + }, + }; + + ret =3D __video_try_fmt(video, &format); + + if (ret < 0) + return ret; + + video->active_fmt =3D format; + + return 0; +} + +/* -----------------------------------------------------------------------= ------ + * Video queue operations + */ + +static int video_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct stfcamss_video *video =3D vb2_get_drv_priv(q); + const struct v4l2_pix_format *format =3D &video->active_fmt.fmt.pix; + + if (*num_planes) { + if (*num_planes !=3D 1) + return -EINVAL; + + if (sizes[0] < format->sizeimage) + return -EINVAL; + } + + *num_planes =3D 1; + sizes[0] =3D format->sizeimage; + if (!sizes[0]) + dev_err(video->stfcamss->dev, + "%s: error size is zero!!!\n", __func__); + + dev_dbg(video->stfcamss->dev, "planes =3D %d, size =3D %d\n", + *num_planes, sizes[0]); + + return 0; +} + +static int video_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct stfcamss_video *video =3D vb2_get_drv_priv(vb->vb2_queue); + struct stfcamss_buffer *buffer =3D + container_of(vbuf, struct stfcamss_buffer, vb); + const struct v4l2_pix_format *fmt =3D &video->active_fmt.fmt.pix; + dma_addr_t *paddr; + + buffer->sizeimage =3D 0; + + paddr =3D vb2_plane_cookie(vb, 0); + buffer->sizeimage =3D vb2_plane_size(vb, 0); + buffer->addr[0] =3D *paddr; + if (fmt->pixelformat =3D=3D V4L2_PIX_FMT_NV12 || + fmt->pixelformat =3D=3D V4L2_PIX_FMT_NV21 || + fmt->pixelformat =3D=3D V4L2_PIX_FMT_NV16 || + fmt->pixelformat =3D=3D V4L2_PIX_FMT_NV61) + buffer->addr[1] =3D + buffer->addr[0] + fmt->bytesperline * fmt->height; + + return 0; +} + +static int video_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct stfcamss_video *video =3D vb2_get_drv_priv(vb->vb2_queue); + const struct v4l2_pix_format *fmt =3D &video->active_fmt.fmt.pix; + + if (fmt->sizeimage > vb2_plane_size(vb, 0)) { + dev_err(video->stfcamss->dev, + "sizeimage =3D %d, plane size =3D %d\n", + fmt->sizeimage, (unsigned int)vb2_plane_size(vb, 0)); + return -EINVAL; + } + vb2_set_plane_payload(vb, 0, fmt->sizeimage); + + vbuf->field =3D V4L2_FIELD_NONE; + + return 0; +} + +static void video_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf =3D to_vb2_v4l2_buffer(vb); + struct stfcamss_video *video =3D vb2_get_drv_priv(vb->vb2_queue); + struct stfcamss_buffer *buffer =3D + container_of(vbuf, struct stfcamss_buffer, vb); + + video->ops->queue_buffer(video, buffer); +} + +/* + * video_mbus_to_pix - Convert v4l2_mbus_framefmt to v4l2_pix_format + * @mbus: v4l2_mbus_framefmt format (input) + * @pix: v4l2_pix_format_mplane format (output) + * @f: a pointer to formats array element to be used for the conversion + * @alignment: bytesperline alignment value + * + * Fill the output pix structure with information from the input mbus form= at. + * + * Return 0 on success or a negative error code otherwise + */ +static int video_mbus_to_pix(const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format *pix, + const struct stfcamss_format_info *f, + unsigned int alignment) +{ + u32 bytesperline; + + memset(pix, 0, sizeof(*pix)); + v4l2_fill_pix_format(pix, mbus); + pix->pixelformat =3D f->pixelformat; + bytesperline =3D pix->width / f->hsub[0].numerator * + f->hsub[0].denominator * f->bpp[0] / 8; + bytesperline =3D ALIGN(bytesperline, alignment); + pix->bytesperline =3D bytesperline; + pix->sizeimage =3D pix->height / f->vsub[0].numerator * + f->vsub[0].denominator * bytesperline; + return 0; +} + +static struct v4l2_subdev *video_remote_subdev(struct stfcamss_video *vide= o, + u32 *pad) +{ + struct media_pad *remote; + + remote =3D media_pad_remote_pad_first(&video->pad); + + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + if (pad) + *pad =3D remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int video_get_subdev_format(struct stfcamss_video *video, + struct v4l2_format *format) +{ + struct v4l2_pix_format *pix =3D &video->active_fmt.fmt.pix; + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pixelformat; + u32 pad; + int ret; + + subdev =3D video_remote_subdev(video, &pad); + if (!subdev) + return -EPIPE; + + fmt.pad =3D pad; + fmt.which =3D V4L2_SUBDEV_FORMAT_ACTIVE; + + ret =3D v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + pixelformat =3D pix->pixelformat; + ret =3D video_find_format(fmt.format.code, pixelformat, + video->formats, video->nformats); + if (ret < 0) + return ret; + + format->type =3D video->type; + + return video_mbus_to_pix(&fmt.format, &format->fmt.pix, + &video->formats[ret], video->bpl_alignment); +} + +static int video_check_format(struct stfcamss_video *video) +{ + struct v4l2_pix_format *pix =3D &video->active_fmt.fmt.pix; + struct v4l2_format format; + struct v4l2_pix_format *sd_pix =3D &format.fmt.pix; + int ret; + + sd_pix->pixelformat =3D pix->pixelformat; + ret =3D video_get_subdev_format(video, &format); + if (ret < 0) + return ret; + + if (pix->pixelformat !=3D sd_pix->pixelformat || + pix->height > sd_pix->height || + pix->width > sd_pix->width || + pix->field !=3D format.fmt.pix.field) { + dev_err(video->stfcamss->dev, + "%s, not match:\n" + "0x%x 0x%x\n0x%x 0x%x\n0x%x 0x%x\n", + __func__, + pix->pixelformat, sd_pix->pixelformat, + pix->height, sd_pix->height, + pix->field, format.fmt.pix.field); + return -EPIPE; + } + + return 0; +} + +static int video_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct stfcamss_video *video =3D vb2_get_drv_priv(q); + struct video_device *vdev =3D &video->vdev; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + int ret; + + ret =3D video_device_pipeline_start(vdev, &video->stfcamss->pipe); + if (ret < 0) { + dev_err(video->stfcamss->dev, + "Failed to media_pipeline_start: %d\n", ret); + return ret; + } + + ret =3D video_check_format(video); + if (ret < 0) + goto error; + entity =3D &vdev->entity; + while (1) { + pad =3D &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad =3D media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity =3D pad->entity; + subdev =3D media_entity_to_v4l2_subdev(entity); + + ret =3D v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret < 0 && ret !=3D -ENOIOCTLCMD) + goto error; + } + return 0; + +error: + video_device_pipeline_stop(vdev); + video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void video_stop_streaming(struct vb2_queue *q) +{ + struct stfcamss_video *video =3D vb2_get_drv_priv(q); + struct video_device *vdev =3D &video->vdev; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + + entity =3D &vdev->entity; + while (1) { + pad =3D &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad =3D media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity =3D pad->entity; + subdev =3D media_entity_to_v4l2_subdev(entity); + + v4l2_subdev_call(subdev, video, s_stream, 0); + } + + video_device_pipeline_stop(vdev); + video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops stf_video_vb2_q_ops =3D { + .queue_setup =3D video_queue_setup, + .wait_prepare =3D vb2_ops_wait_prepare, + .wait_finish =3D vb2_ops_wait_finish, + .buf_init =3D video_buf_init, + .buf_prepare =3D video_buf_prepare, + .buf_queue =3D video_buf_queue, + .start_streaming =3D video_start_streaming, + .stop_streaming =3D video_stop_streaming, +}; + +/* -----------------------------------------------------------------------= ------ + * V4L2 ioctls + */ + +static int video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct stfcamss_video *video =3D video_drvdata(file); + + strscpy(cap->driver, "stf camss", sizeof(cap->driver)); + strscpy(cap->card, "Starfive Camera Subsystem", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + dev_name(video->stfcamss->dev)); + return 0; +} + +static int video_get_pfmt_by_index(struct stfcamss_video *video, int ndx) +{ + int i, j, k; + + /* find index "i" of "k"th unique pixelformat in formats array */ + k =3D -1; + for (i =3D 0; i < video->nformats; i++) { + for (j =3D 0; j < i; j++) { + if (video->formats[i].pixelformat =3D=3D + video->formats[j].pixelformat) + break; + } + + if (j =3D=3D i) + k++; + + if (k =3D=3D ndx) + return i; + } + + return -EINVAL; +} + +static int video_get_pfmt_by_mcode(struct stfcamss_video *video, u32 mcode) +{ + int i; + + for (i =3D 0; i < video->nformats; i++) { + if (video->formats[i].code =3D=3D mcode) + return i; + } + + return -EINVAL; +} + +static int video_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc= *f) +{ + struct stfcamss_video *video =3D video_drvdata(file); + int i; + + if (f->type !=3D video->type) + return -EINVAL; + if (f->index >=3D video->nformats) + return -EINVAL; + + if (f->mbus_code) { + /* Each entry in formats[] table has unique mbus_code */ + if (f->index > 0) + return -EINVAL; + + i =3D video_get_pfmt_by_mcode(video, f->mbus_code); + } else { + i =3D video_get_pfmt_by_index(video, f->index); + } + + if (i < 0) + return -EINVAL; + + f->pixelformat =3D video->formats[i].pixelformat; + + return 0; +} + +static int video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct stfcamss_video *video =3D video_drvdata(file); + int i; + + if (fsize->index) + return -EINVAL; + + for (i =3D 0; i < video->nformats; i++) { + if (video->formats[i].pixelformat =3D=3D fsize->pixel_format) + break; + } + + if (i =3D=3D video->nformats) + return -EINVAL; + + fsize->type =3D V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width =3D STFCAMSS_FRAME_MIN_WIDTH; + fsize->stepwise.max_width =3D STFCAMSS_FRAME_MAX_WIDTH; + fsize->stepwise.min_height =3D STFCAMSS_FRAME_MIN_HEIGHT; + fsize->stepwise.max_height =3D STFCAMSS_FRAME_MAX_HEIGHT; + fsize->stepwise.step_width =3D 1; + fsize->stepwise.step_height =3D 1; + + return 0; +} + +static int video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct stfcamss_video *video =3D video_drvdata(file); + + *f =3D video->active_fmt; + + return 0; +} + +static int video_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct stfcamss_video *video =3D video_drvdata(file); + int ret; + + if (vb2_is_busy(&video->vb2_q)) + return -EBUSY; + + ret =3D __video_try_fmt(video, f); + if (ret < 0) + return ret; + + video->active_fmt =3D *f; + + return 0; +} + +static int video_try_fmt(struct file *file, void *fh, struct v4l2_format *= f) +{ + struct stfcamss_video *video =3D video_drvdata(file); + + return __video_try_fmt(video, f); +} + +static int video_enum_input(struct file *file, void *fh, + struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strscpy(input->name, "camera", sizeof(input->name)); + input->type =3D V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int video_g_input(struct file *file, void *fh, unsigned int *input) +{ + *input =3D 0; + + return 0; +} + +static int video_s_input(struct file *file, void *fh, unsigned int input) +{ + return input =3D=3D 0 ? 0 : -EINVAL; +} + +static int video_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *p) +{ + struct stfcamss_video *video =3D video_drvdata(file); + struct video_device *vdev =3D &video->vdev; + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret, is_support =3D 0; + + entity =3D &vdev->entity; + while (1) { + pad =3D &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad =3D media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity =3D pad->entity; + subdev =3D media_entity_to_v4l2_subdev(entity); + + ret =3D v4l2_g_parm_cap(vdev, subdev, p); + if (ret < 0 && ret !=3D -ENOIOCTLCMD) + break; + if (!ret) + is_support =3D 1; + } + + return is_support ? 0 : ret; +} + +static int video_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *p) +{ + struct stfcamss_video *video =3D video_drvdata(file); + struct video_device *vdev =3D &video->vdev; + struct media_entity *entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + struct v4l2_streamparm tmp_p; + int ret, is_support =3D 0; + + entity =3D &vdev->entity; + while (1) { + pad =3D &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad =3D media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity =3D pad->entity; + subdev =3D media_entity_to_v4l2_subdev(entity); + + tmp_p =3D *p; + ret =3D v4l2_s_parm_cap(vdev, subdev, &tmp_p); + if (ret < 0 && ret !=3D -ENOIOCTLCMD) + break; + if (!ret) { + is_support =3D 1; + *p =3D tmp_p; + } + } + + return is_support ? 0 : ret; +} + +static const struct v4l2_ioctl_ops stf_vid_vin_ioctl_ops =3D { + .vidioc_querycap =3D video_querycap, + .vidioc_enum_fmt_vid_cap =3D video_enum_fmt, + .vidioc_enum_fmt_vid_out =3D video_enum_fmt, + .vidioc_enum_framesizes =3D video_enum_framesizes, + .vidioc_g_fmt_vid_cap =3D video_g_fmt, + .vidioc_s_fmt_vid_cap =3D video_s_fmt, + .vidioc_try_fmt_vid_cap =3D video_try_fmt, + .vidioc_g_fmt_vid_out =3D video_g_fmt, + .vidioc_s_fmt_vid_out =3D video_s_fmt, + .vidioc_try_fmt_vid_out =3D video_try_fmt, + .vidioc_reqbufs =3D vb2_ioctl_reqbufs, + .vidioc_querybuf =3D vb2_ioctl_querybuf, + .vidioc_qbuf =3D vb2_ioctl_qbuf, + .vidioc_expbuf =3D vb2_ioctl_expbuf, + .vidioc_dqbuf =3D vb2_ioctl_dqbuf, + .vidioc_create_bufs =3D vb2_ioctl_create_bufs, + .vidioc_prepare_buf =3D vb2_ioctl_prepare_buf, + .vidioc_streamon =3D vb2_ioctl_streamon, + .vidioc_streamoff =3D vb2_ioctl_streamoff, + .vidioc_enum_input =3D video_enum_input, + .vidioc_g_input =3D video_g_input, + .vidioc_s_input =3D video_s_input, + .vidioc_g_parm =3D video_g_parm, + .vidioc_s_parm =3D video_s_parm, +}; + +static const struct v4l2_ioctl_ops stf_vid_isp_ioctl_ops =3D { + .vidioc_querycap =3D video_querycap, + .vidioc_enum_fmt_vid_cap =3D video_enum_fmt, + .vidioc_enum_fmt_vid_out =3D video_enum_fmt, + .vidioc_enum_framesizes =3D video_enum_framesizes, + .vidioc_g_fmt_vid_cap =3D video_g_fmt, + .vidioc_s_fmt_vid_cap =3D video_s_fmt, + .vidioc_try_fmt_vid_cap =3D video_try_fmt, + .vidioc_g_fmt_vid_out =3D video_g_fmt, + .vidioc_s_fmt_vid_out =3D video_s_fmt, + .vidioc_try_fmt_vid_out =3D video_try_fmt, + .vidioc_reqbufs =3D vb2_ioctl_reqbufs, + .vidioc_querybuf =3D vb2_ioctl_querybuf, + .vidioc_qbuf =3D vb2_ioctl_qbuf, + .vidioc_expbuf =3D vb2_ioctl_expbuf, + .vidioc_dqbuf =3D vb2_ioctl_dqbuf, + .vidioc_create_bufs =3D vb2_ioctl_create_bufs, + .vidioc_prepare_buf =3D vb2_ioctl_prepare_buf, + .vidioc_streamon =3D vb2_ioctl_streamon, + .vidioc_streamoff =3D vb2_ioctl_streamoff, + .vidioc_enum_input =3D video_enum_input, + .vidioc_g_input =3D video_g_input, + .vidioc_s_input =3D video_s_input, + .vidioc_g_parm =3D video_g_parm, + .vidioc_s_parm =3D video_s_parm, +}; + +/* -----------------------------------------------------------------------= ------ + * V4L2 file operations + */ + +static int video_open(struct file *file) +{ + struct video_device *vdev =3D video_devdata(file); + struct stfcamss_video *video =3D video_drvdata(file); + struct v4l2_fh *vfh; + int ret; + + mutex_lock(&video->lock); + + vfh =3D kzalloc(sizeof(*vfh), GFP_KERNEL); + if (!vfh) { + ret =3D -ENOMEM; + goto error_alloc; + } + + v4l2_fh_init(vfh, vdev); + v4l2_fh_add(vfh); + + file->private_data =3D vfh; + + ret =3D v4l2_pipeline_pm_get(&vdev->entity); + if (ret < 0) { + dev_err(video->stfcamss->dev, + "Failed to power up pipeline: %d\n", ret); + goto error_pm_use; + } + mutex_unlock(&video->lock); + + return 0; + +error_pm_use: + v4l2_fh_release(file); +error_alloc: + mutex_unlock(&video->lock); + return ret; +} + +static int video_release(struct file *file) +{ + struct video_device *vdev =3D video_devdata(file); + + vb2_fop_release(file); + v4l2_pipeline_pm_put(&vdev->entity); + file->private_data =3D NULL; + + return 0; +} + +static const struct v4l2_file_operations stf_vid_fops =3D { + .owner =3D THIS_MODULE, + .unlocked_ioctl =3D video_ioctl2, + .open =3D video_open, + .release =3D video_release, + .poll =3D vb2_fop_poll, + .mmap =3D vb2_fop_mmap, + .read =3D vb2_fop_read, +}; + +/* -----------------------------------------------------------------------= ------ + * STFCAMSS video core + */ + +static void stf_video_release(struct video_device *vdev) +{ + struct stfcamss_video *video =3D video_get_drvdata(vdev); + + media_entity_cleanup(&vdev->entity); + + mutex_destroy(&video->q_lock); + mutex_destroy(&video->lock); +} + +int stf_video_register(struct stfcamss_video *video, + struct v4l2_device *v4l2_dev, const char *name) +{ + struct video_device *vdev; + struct vb2_queue *q; + struct media_pad *pad =3D &video->pad; + int ret; + + vdev =3D &video->vdev; + + mutex_init(&video->q_lock); + + q =3D &video->vb2_q; + q->drv_priv =3D video; + q->mem_ops =3D &vb2_dma_contig_memops; + q->ops =3D &stf_video_vb2_q_ops; + q->type =3D video->type; + q->io_modes =3D VB2_DMABUF | VB2_MMAP | VB2_READ; + q->timestamp_flags =3D V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->buf_struct_size =3D sizeof(struct stfcamss_buffer); + q->dev =3D video->stfcamss->dev; + q->lock =3D &video->q_lock; + q->min_buffers_needed =3D STFCAMSS_MIN_BUFFERS; + ret =3D vb2_queue_init(q); + if (ret < 0) { + dev_err(video->stfcamss->dev, + "Failed to init vb2 queue: %d\n", ret); + goto err_vb2_init; + } + + pad->flags =3D MEDIA_PAD_FL_SINK; + ret =3D media_entity_pads_init(&vdev->entity, 1, pad); + if (ret < 0) { + dev_err(video->stfcamss->dev, + "Failed to init video entity: %d\n", ret); + goto err_vb2_init; + } + + mutex_init(&video->lock); + + if (video->id =3D=3D STF_V_LINE_WR) { + video->formats =3D formats_pix_wr; + video->nformats =3D ARRAY_SIZE(formats_pix_wr); + video->bpl_alignment =3D STFCAMSS_FRAME_WIDTH_ALIGN_8; + vdev->ioctl_ops =3D &stf_vid_vin_ioctl_ops; + } else { + video->formats =3D formats_pix_isp; + video->nformats =3D ARRAY_SIZE(formats_pix_isp); + video->bpl_alignment =3D STFCAMSS_FRAME_WIDTH_ALIGN_8; + vdev->ioctl_ops =3D &stf_vid_isp_ioctl_ops; + } + + ret =3D stf_video_init_format(video); + if (ret < 0) { + dev_err(video->stfcamss->dev, + "Failed to init format: %d\n", ret); + goto err_vid_init_format; + } + + vdev->fops =3D &stf_vid_fops; + vdev->device_caps =3D V4L2_CAP_VIDEO_CAPTURE; + vdev->vfl_dir =3D VFL_DIR_RX; + vdev->device_caps |=3D V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + vdev->release =3D stf_video_release; + vdev->v4l2_dev =3D v4l2_dev; + vdev->queue =3D &video->vb2_q; + vdev->lock =3D &video->lock; + strscpy(vdev->name, name, sizeof(vdev->name)); + + ret =3D video_register_device(vdev, VFL_TYPE_VIDEO, video->id); + if (ret < 0) { + dev_err(video->stfcamss->dev, + "Failed to register video device: %d\n", ret); + goto err_vid_reg; + } + + video_set_drvdata(vdev, video); + return 0; + +err_vid_reg: +err_vid_init_format: + media_entity_cleanup(&vdev->entity); + mutex_destroy(&video->lock); +err_vb2_init: + mutex_destroy(&video->q_lock); + return ret; +} + +void stf_video_unregister(struct stfcamss_video *video) +{ + vb2_video_unregister_device(&video->vdev); +} diff --git a/drivers/media/platform/starfive/stf_video.h b/drivers/media/pl= atform/starfive/stf_video.h new file mode 100644 index 000000000000..63a9a7706a03 --- /dev/null +++ b/drivers/media/platform/starfive/stf_video.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_video.h + * + * StarFive Camera Subsystem - V4L2 device node + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_VIDEO_H +#define STF_VIDEO_H + +#include +#include +#include +#include +#include +#include + +#define STFCAMSS_FRAME_MIN_WIDTH 64 +#define STFCAMSS_FRAME_MAX_WIDTH 1920 +#define STFCAMSS_FRAME_MIN_HEIGHT 64 +#define STFCAMSS_FRAME_MAX_HEIGHT 1080 +#define STFCAMSS_FRAME_WIDTH_ALIGN_8 8 +#define STFCAMSS_FRAME_WIDTH_ALIGN_128 128 +#define STFCAMSS_MIN_BUFFERS 2 + +#define STFCAMSS_MAX_ENTITY_NAME_LEN 27 + +enum stf_v_line_id { + STF_V_LINE_WR =3D 0, + STF_V_LINE_ISP, + STF_V_LINE_MAX, +}; + +struct stfcamss_buffer { + struct vb2_v4l2_buffer vb; + dma_addr_t addr[3]; + struct list_head queue; + int sizeimage; +}; + +struct fract { + u8 numerator; + u8 denominator; +}; + +/* + * struct stfcamss_format_info - ISP media bus format information + * @code: V4L2 media bus format code + * @pixelformat: V4L2 pixel format FCC identifier + * @planes: Number of planes + * @hsub: Horizontal subsampling (for each plane) + * @vsub: Vertical subsampling (for each plane) + * @bpp: Bits per pixel when stored in memory (for each plane) + */ +struct stfcamss_format_info { + u32 code; + u32 pixelformat; + u8 planes; + struct fract hsub[3]; + struct fract vsub[3]; + u8 bpp[3]; +}; + +struct stfcamss_video { + struct stfcamss *stfcamss; + u8 id; + struct vb2_queue vb2_q; + struct video_device vdev; + struct media_pad pad; + struct media_pipeline pipe; + struct v4l2_format active_fmt; + enum v4l2_buf_type type; + const struct stfcamss_video_ops *ops; + struct mutex lock; /* serialize device access */ + struct mutex q_lock; /* protects the queue */ + unsigned int bpl_alignment; + const struct stfcamss_format_info *formats; + unsigned int nformats; +}; + +struct stfcamss_video_ops { + int (*queue_buffer)(struct stfcamss_video *vid, + struct stfcamss_buffer *buf); + int (*flush_buffers)(struct stfcamss_video *vid, + enum vb2_buffer_state state); +}; + +int stf_video_register(struct stfcamss_video *video, + struct v4l2_device *v4l2_dev, const char *name); + +void stf_video_unregister(struct stfcamss_video *video); + +#endif /* STF_VIDEO_H */ --=20 2.34.1 From nobody Sat Feb 7 15:12:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 53989C77B7A for ; Thu, 25 May 2023 08:32:39 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240086AbjEYIcg convert rfc822-to-8bit (ORCPT ); Thu, 25 May 2023 04:32:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56170 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239912AbjEYIcO (ORCPT ); Thu, 25 May 2023 04:32:14 -0400 Received: from ex01.ufhost.com (ex01.ufhost.com [61.152.239.75]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id EBFFB194; Thu, 25 May 2023 01:32:06 -0700 (PDT) Received: from EXMBX166.cuchost.com (unknown [175.102.18.54]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "EXMBX166", Issuer "EXMBX166" (not verified)) by ex01.ufhost.com (Postfix) with ESMTP id C2F5A24E26C; Thu, 25 May 2023 16:32:05 +0800 (CST) Received: from EXMBX173.cuchost.com (172.16.6.93) by EXMBX166.cuchost.com (172.16.6.76) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:05 +0800 Received: from xiaofei.localdomain (180.164.60.184) by EXMBX173.cuchost.com (172.16.6.93) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:04 +0800 From: Jack Zhu To: Mauro Carvalho Chehab , Robert Foss , Todor Tomov , , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , Philipp Zabel , "Laurent Pinchart" , Hans Verkuil , Eugen Hristev , Ezequiel Garcia CC: , , , , Subject: [PATCH v6 5/6] media: starfive: Add ISP driver Date: Thu, 25 May 2023 16:32:01 +0800 Message-ID: <20230525083202.67933-6-jack.zhu@starfivetech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230525083202.67933-1-jack.zhu@starfivetech.com> References: <20230525083202.67933-1-jack.zhu@starfivetech.com> MIME-Version: 1.0 X-Originating-IP: [180.164.60.184] X-ClientProxiedBy: EXCAS066.cuchost.com (172.16.6.26) To EXMBX173.cuchost.com (172.16.6.93) X-YovoleRuleAgent: yovoleflag Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add ISP driver for StarFive Camera Subsystem. Signed-off-by: Jack Zhu --- drivers/media/platform/starfive/Makefile | 2 + drivers/media/platform/starfive/stf_camss.c | 71 ++- drivers/media/platform/starfive/stf_camss.h | 2 + drivers/media/platform/starfive/stf_isp.c | 550 ++++++++++++++++++ drivers/media/platform/starfive/stf_isp.h | 483 +++++++++++++++ .../media/platform/starfive/stf_isp_hw_ops.c | 468 +++++++++++++++ 6 files changed, 1574 insertions(+), 2 deletions(-) create mode 100644 drivers/media/platform/starfive/stf_isp.c create mode 100644 drivers/media/platform/starfive/stf_isp.h create mode 100644 drivers/media/platform/starfive/stf_isp_hw_ops.c diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platf= orm/starfive/Makefile index d7a42b3a938c..60a3018f4994 100644 --- a/drivers/media/platform/starfive/Makefile +++ b/drivers/media/platform/starfive/Makefile @@ -5,6 +5,8 @@ =20 starfive-camss-objs +=3D \ stf_camss.o \ + stf_isp.o \ + stf_isp_hw_ops.o \ stf_video.o =20 obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) +=3D starfive-camss.o \ diff --git a/drivers/media/platform/starfive/stf_camss.c b/drivers/media/pl= atform/starfive/stf_camss.c index cb2b62d49416..0d8406ba6a40 100644 --- a/drivers/media/platform/starfive/stf_camss.c +++ b/drivers/media/platform/starfive/stf_camss.c @@ -126,16 +126,66 @@ static int stfcamss_of_parse_ports(struct stfcamss *s= tfcamss) return ret; } =20 +/* + * stfcamss_init_subdevices - Initialize subdev structures and resources + * @stfcamss: STFCAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static int stfcamss_init_subdevices(struct stfcamss *stfcamss) +{ + int ret; + + ret =3D stf_isp_subdev_init(stfcamss); + if (ret < 0) { + dev_err(stfcamss->dev, "Failed to init isp subdev: %d\n", ret); + return ret; + } + + return ret; +} + +static int stfcamss_register_subdevices(struct stfcamss *stfcamss) +{ + int ret; + struct stf_isp_dev *isp_dev =3D &stfcamss->isp_dev; + + ret =3D stf_isp_register(isp_dev, &stfcamss->v4l2_dev); + if (ret < 0) { + dev_err(stfcamss->dev, + "Failed to register stf isp%d entity: %d\n", 0, ret); + return ret; + } + + return ret; +} + +static void stfcamss_unregister_subdevices(struct stfcamss *stfcamss) +{ + stf_isp_unregister(&stfcamss->isp_dev); +} + static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *asyn= c, struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) { struct stfcamss *stfcamss =3D container_of(async, struct stfcamss, notifier); + struct stfcamss_async_subdev *csd =3D + container_of(asd, struct stfcamss_async_subdev, asd); + enum port_num port =3D csd->port; + struct stf_isp_dev *isp_dev =3D &stfcamss->isp_dev; struct host_data *host_data =3D &stfcamss->host_data; struct media_entity *source; int i, j; =20 + if (port =3D=3D PORT_NUMBER_CSI2RX) { + host_data->host_entity[1] =3D &isp_dev->subdev.entity; + } else if (port =3D=3D PORT_NUMBER_DVP_SENSOR) { + dev_err(stfcamss->dev, "Not support DVP sensor\n"); + return -EPERM; + } + source =3D &subdev->entity; =20 for (i =3D 0; i < source->num_pads; i++) { @@ -266,12 +316,18 @@ static int stfcamss_probe(struct platform_device *pde= v) return -ENODEV; } =20 + ret =3D stfcamss_init_subdevices(stfcamss); + if (ret < 0) { + dev_err(dev, "Failed to init subdevice: %d\n", ret); + goto err_cleanup_notifier; + } + stfcamss_mc_init(pdev, stfcamss); =20 ret =3D v4l2_device_register(stfcamss->dev, &stfcamss->v4l2_dev); if (ret < 0) { dev_err(dev, "Failed to register V4L2 device: %d\n", ret); - goto err_cleanup_notifier; + goto err_cleanup_media_device; } =20 ret =3D media_device_register(&stfcamss->media_dev); @@ -280,22 +336,32 @@ static int stfcamss_probe(struct platform_device *pde= v) goto err_unregister_device; } =20 + ret =3D stfcamss_register_subdevices(stfcamss); + if (ret < 0) { + dev_err(dev, "Failed to register subdevice: %d\n", ret); + goto err_unregister_media_dev; + } + stfcamss->notifier.ops =3D &stfcamss_subdev_notifier_ops; ret =3D v4l2_async_nf_register(&stfcamss->v4l2_dev, &stfcamss->notifier); if (ret) { dev_err(dev, "Failed to register async subdev nodes: %d\n", ret); - goto err_unregister_media_dev; + goto err_unregister_subdevs; } =20 pm_runtime_enable(dev); =20 return 0; =20 +err_unregister_subdevs: + stfcamss_unregister_subdevices(stfcamss); err_unregister_media_dev: media_device_unregister(&stfcamss->media_dev); err_unregister_device: v4l2_device_unregister(&stfcamss->v4l2_dev); +err_cleanup_media_device: + media_device_cleanup(&stfcamss->media_dev); err_cleanup_notifier: v4l2_async_nf_cleanup(&stfcamss->notifier); return ret; @@ -311,6 +377,7 @@ static int stfcamss_remove(struct platform_device *pdev) { struct stfcamss *stfcamss =3D platform_get_drvdata(pdev); =20 + stfcamss_unregister_subdevices(stfcamss); v4l2_device_unregister(&stfcamss->v4l2_dev); media_device_cleanup(&stfcamss->media_dev); pm_runtime_disable(&pdev->dev); diff --git a/drivers/media/platform/starfive/stf_camss.h b/drivers/media/pl= atform/starfive/stf_camss.h index 8f39cd65950c..c9d94be92ea8 100644 --- a/drivers/media/platform/starfive/stf_camss.h +++ b/drivers/media/platform/starfive/stf_camss.h @@ -16,6 +16,7 @@ #include =20 #include "stf_common.h" +#include "stf_isp.h" =20 #define DRV_NAME "starfive-camss" #define STF_DVP_NAME "stf_dvp" @@ -71,6 +72,7 @@ struct stfcamss { struct media_device media_dev; struct media_pipeline pipe; struct device *dev; + struct stf_isp_dev isp_dev; struct v4l2_async_notifier notifier; struct host_data host_data; void __iomem *syscon_base; diff --git a/drivers/media/platform/starfive/stf_isp.c b/drivers/media/plat= form/starfive/stf_isp.c new file mode 100644 index 000000000000..874e06efc45d --- /dev/null +++ b/drivers/media/platform/starfive/stf_isp.c @@ -0,0 +1,550 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_isp.c + * + * StarFive Camera Subsystem - ISP Module + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ +#include +#include + +#include "stf_camss.h" + +#define SINK_FORMATS_INDEX 0 +#define UO_FORMATS_INDEX 1 + +static int isp_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel); + +static const struct isp_format isp_formats_sink[] =3D { + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, +}; + +static const struct isp_format isp_formats_uo[] =3D { + { MEDIA_BUS_FMT_Y12_1X12, 8 }, +}; + +static const struct isp_format_table isp_formats_st7110[] =3D { + { isp_formats_sink, ARRAY_SIZE(isp_formats_sink) }, + { isp_formats_uo, ARRAY_SIZE(isp_formats_uo) }, +}; + +int stf_isp_subdev_init(struct stfcamss *stfcamss) +{ + struct stf_isp_dev *isp_dev =3D &stfcamss->isp_dev; + + isp_dev->sdev_type =3D STF_SUBDEV_TYPE_ISP; + isp_dev->stfcamss =3D stfcamss; + isp_dev->formats =3D isp_formats_st7110; + isp_dev->nformats =3D ARRAY_SIZE(isp_formats_st7110); + + mutex_init(&isp_dev->stream_lock); + mutex_init(&isp_dev->power_lock); + return 0; +} + +static int isp_set_power(struct v4l2_subdev *sd, int on) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + + mutex_lock(&isp_dev->power_lock); + if (on) { + if (isp_dev->power_count =3D=3D 0) + dev_dbg(isp_dev->stfcamss->dev, "turn on isp\n"); + isp_dev->power_count++; + } else { + if (isp_dev->power_count =3D=3D 0) + goto exit; + isp_dev->power_count--; + } +exit: + mutex_unlock(&isp_dev->power_lock); + + return 0; +} + +static struct v4l2_mbus_framefmt * +__isp_get_format(struct stf_isp_dev *isp_dev, + struct v4l2_subdev_state *state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which =3D=3D V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&isp_dev->subdev, state, pad); + + return &isp_dev->fmt[pad]; +} + +static int isp_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + int ret =3D 0; + struct v4l2_mbus_framefmt *fmt; + struct v4l2_event src_ch =3D { 0 }; + + fmt =3D __isp_get_format(isp_dev, NULL, STF_ISP_PAD_SINK, + V4L2_SUBDEV_FORMAT_ACTIVE); + mutex_lock(&isp_dev->stream_lock); + if (enable) { + if (isp_dev->stream_count =3D=3D 0) { + stf_isp_clk_enable(isp_dev); + stf_isp_reset(isp_dev); + stf_isp_init_cfg(isp_dev); + stf_isp_settings(isp_dev, isp_dev->rect, fmt->code); + stf_isp_stream_set(isp_dev); + } + isp_dev->stream_count++; + } else { + if (isp_dev->stream_count =3D=3D 0) + goto exit; + + if (isp_dev->stream_count =3D=3D 1) + stf_isp_clk_disable(isp_dev); + + isp_dev->stream_count--; + } + src_ch.type =3D V4L2_EVENT_SOURCE_CHANGE, + src_ch.u.src_change.changes =3D isp_dev->stream_count, + + v4l2_subdev_notify_event(sd, &src_ch); +exit: + mutex_unlock(&isp_dev->stream_lock); + + return ret; +} + +static void isp_try_format(struct stf_isp_dev *isp_dev, + struct v4l2_subdev_state *state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + const struct isp_format_table *formats; + struct stf_isp_crop *rect; + int i; + + switch (pad) { + case STF_ISP_PAD_SINK: + /* Set format on sink pad */ + formats =3D &isp_dev->formats[SINK_FORMATS_INDEX]; + rect =3D &isp_dev->rect[SINK_FORMATS_INDEX]; + fmt->width =3D clamp_t(u32, + fmt->width, STFCAMSS_FRAME_MIN_WIDTH, + STFCAMSS_FRAME_MAX_WIDTH); + fmt->height =3D clamp_t(u32, + fmt->height, STFCAMSS_FRAME_MIN_HEIGHT, + STFCAMSS_FRAME_MAX_HEIGHT); + fmt->height &=3D ~0x1; + fmt->field =3D V4L2_FIELD_NONE; + fmt->colorspace =3D V4L2_COLORSPACE_SRGB; + fmt->flags =3D 0; + break; + + case STF_ISP_PAD_SRC: + formats =3D &isp_dev->formats[UO_FORMATS_INDEX]; + rect =3D &isp_dev->rect[UO_FORMATS_INDEX]; + *fmt =3D *__isp_get_format(isp_dev, state, + STF_ISP_PAD_SINK, which); + break; + } + + for (i =3D 0; i < formats->nfmts; i++) { + if (fmt->code =3D=3D formats->fmts[i].code) + break; + } + + if (i >=3D formats->nfmts) { + fmt->code =3D formats->fmts[0].code; + rect->bpp =3D formats->fmts[0].bpp; + } else { + rect->bpp =3D formats->fmts[i].bpp; + } +} + +static int isp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + const struct isp_format_table *formats; + + if (code->index >=3D isp_dev->nformats) + return -EINVAL; + if (code->pad =3D=3D STF_ISP_PAD_SINK) { + formats =3D &isp_dev->formats[SINK_FORMATS_INDEX]; + code->code =3D formats->fmts[code->index].code; + } else { + struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt =3D __isp_get_format(isp_dev, state, STF_ISP_PAD_SINK, + code->which); + + code->code =3D sink_fmt->code; + if (!code->code) + return -EINVAL; + } + code->flags =3D 0; + + return 0; +} + +static int isp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index !=3D 0) + return -EINVAL; + + format.code =3D fse->code; + format.width =3D 1; + format.height =3D 1; + isp_try_format(isp_dev, state, fse->pad, &format, fse->which); + fse->min_width =3D format.width; + fse->min_height =3D format.height; + + if (format.code !=3D fse->code) + return -EINVAL; + + format.code =3D fse->code; + format.width =3D -1; + format.height =3D -1; + isp_try_format(isp_dev, state, fse->pad, &format, fse->which); + fse->max_width =3D format.width; + fse->max_height =3D format.height; + + return 0; +} + +static int isp_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format =3D __isp_get_format(isp_dev, state, fmt->pad, fmt->which); + if (!format) + return -EINVAL; + + fmt->format =3D *format; + + return 0; +} + +static int isp_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format =3D __isp_get_format(isp_dev, state, fmt->pad, fmt->which); + if (!format) + return -EINVAL; + + mutex_lock(&isp_dev->stream_lock); + + isp_try_format(isp_dev, state, fmt->pad, &fmt->format, fmt->which); + *format =3D fmt->format; + + mutex_unlock(&isp_dev->stream_lock); + + /* Propagate to in crop */ + if (fmt->pad =3D=3D STF_ISP_PAD_SINK) { + struct v4l2_subdev_selection sel =3D { 0 }; + int ret; + + /* Reset sink pad compose selection */ + sel.which =3D fmt->which; + sel.pad =3D STF_ISP_PAD_SINK; + sel.target =3D V4L2_SEL_TGT_CROP; + sel.r.width =3D fmt->format.width; + sel.r.height =3D fmt->format.height; + ret =3D isp_set_selection(sd, state, &sel); + if (ret < 0) + return ret; + } + + return 0; +} + +static struct v4l2_rect * +__isp_get_crop(struct stf_isp_dev *isp_dev, + struct v4l2_subdev_state *state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which =3D=3D V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&isp_dev->subdev, state, + STF_ISP_PAD_SINK); + + return &isp_dev->rect[pad].rect; +} + +static void isp_try_crop(struct stf_isp_dev *isp_dev, + struct v4l2_subdev_state *state, + struct v4l2_rect *rect, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt =3D __isp_get_format(isp_dev, state, STF_ISP_PAD_SINK, which); + + if (rect->width > fmt->width) + rect->width =3D fmt->width; + + if (rect->width + rect->left > fmt->width) + rect->left =3D fmt->width - rect->width; + + if (rect->height > fmt->height) + rect->height =3D fmt->height; + + if (rect->height + rect->top > fmt->height) + rect->top =3D fmt->height - rect->height; + + if (rect->width < STFCAMSS_FRAME_MIN_WIDTH) { + rect->left =3D 0; + rect->width =3D STFCAMSS_FRAME_MAX_WIDTH; + } + + if (rect->height < STFCAMSS_FRAME_MIN_HEIGHT) { + rect->top =3D 0; + rect->height =3D STFCAMSS_FRAME_MAX_HEIGHT; + } + rect->height &=3D ~0x1; +} + +static int isp_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + struct v4l2_subdev_format fmt =3D { 0 }; + struct v4l2_rect *rect; + int ret; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + if (sel->pad =3D=3D STF_ISP_PAD_SINK) { + fmt.pad =3D sel->pad; + fmt.which =3D sel->which; + ret =3D isp_get_format(sd, state, &fmt); + if (ret < 0) + return ret; + + sel->r.left =3D 0; + sel->r.top =3D 0; + sel->r.width =3D fmt.format.width; + sel->r.height =3D fmt.format.height; + } else if (sel->pad =3D=3D STF_ISP_PAD_SRC) { + rect =3D __isp_get_crop(isp_dev, state, + sel->pad, sel->which); + sel->r =3D *rect; + } + break; + + case V4L2_SEL_TGT_CROP: + rect =3D __isp_get_crop(isp_dev, state, sel->pad, sel->which); + if (!rect) + return -EINVAL; + + sel->r =3D *rect; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int isp_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct stf_isp_dev *isp_dev =3D v4l2_get_subdevdata(sd); + struct v4l2_rect *rect; + int ret =3D 0; + + if (sel->target =3D=3D V4L2_SEL_TGT_CROP && + sel->pad =3D=3D STF_ISP_PAD_SINK) { + struct v4l2_subdev_selection crop =3D { 0 }; + + rect =3D __isp_get_crop(isp_dev, state, sel->pad, sel->which); + if (!rect) + return -EINVAL; + + mutex_lock(&isp_dev->stream_lock); + isp_try_crop(isp_dev, state, &sel->r, sel->which); + *rect =3D sel->r; + mutex_unlock(&isp_dev->stream_lock); + + /* Reset source crop selection */ + crop.which =3D sel->which; + crop.pad =3D STF_ISP_PAD_SRC; + crop.target =3D V4L2_SEL_TGT_CROP; + crop.r =3D *rect; + ret =3D isp_set_selection(sd, state, &crop); + } else if (sel->target =3D=3D V4L2_SEL_TGT_CROP && + sel->pad =3D=3D STF_ISP_PAD_SRC) { + struct v4l2_subdev_format fmt =3D { 0 }; + + rect =3D __isp_get_crop(isp_dev, state, sel->pad, sel->which); + if (!rect) + return -EINVAL; + + mutex_lock(&isp_dev->stream_lock); + isp_try_crop(isp_dev, state, &sel->r, sel->which); + *rect =3D sel->r; + mutex_unlock(&isp_dev->stream_lock); + + /* Reset source pad format width and height */ + fmt.which =3D sel->which; + fmt.pad =3D STF_ISP_PAD_SRC; + fmt.format.width =3D rect->width; + fmt.format.height =3D rect->height; + ret =3D isp_set_format(sd, state, &fmt); + if (ret < 0) + return ret; + } + + dev_dbg(isp_dev->stfcamss->dev, "pad: %d sel(%d,%d)/%dx%d\n", + sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); + + return 0; +} + +static int isp_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format =3D { + .pad =3D STF_ISP_PAD_SINK, + .which =3D + fh ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE, + .format =3D { + .code =3D MEDIA_BUS_FMT_RGB565_2X8_LE, + .width =3D 1920, + .height =3D 1080 + } + }; + + return isp_set_format(sd, fh ? fh->state : NULL, &format); +} + +static int isp_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) + if (media_pad_remote_pad_first(local)) + return -EBUSY; + return 0; +} + +static int stf_isp_subscribe_event(struct v4l2_subdev *sd, + struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subdev_subscribe(sd, fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_core_ops isp_core_ops =3D { + .s_power =3D isp_set_power, + .subscribe_event =3D stf_isp_subscribe_event, + .unsubscribe_event =3D v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops isp_video_ops =3D { + .s_stream =3D isp_set_stream, +}; + +static const struct v4l2_subdev_pad_ops isp_pad_ops =3D { + .enum_mbus_code =3D isp_enum_mbus_code, + .enum_frame_size =3D isp_enum_frame_size, + .get_fmt =3D isp_get_format, + .set_fmt =3D isp_set_format, + .get_selection =3D isp_get_selection, + .set_selection =3D isp_set_selection, +}; + +static const struct v4l2_subdev_ops isp_v4l2_ops =3D { + .core =3D &isp_core_ops, + .video =3D &isp_video_ops, + .pad =3D &isp_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops isp_v4l2_internal_ops =3D { + .open =3D isp_init_formats, +}; + +static const struct media_entity_operations isp_media_ops =3D { + .link_setup =3D isp_link_setup, + .link_validate =3D v4l2_subdev_link_validate, +}; + +int stf_isp_register(struct stf_isp_dev *isp_dev, struct v4l2_device *v4l2= _dev) +{ + struct v4l2_subdev *sd =3D &isp_dev->subdev; + struct media_pad *pads =3D isp_dev->pads; + int ret; + + v4l2_subdev_init(sd, &isp_v4l2_ops); + sd->internal_ops =3D &isp_v4l2_internal_ops; + sd->flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", STF_ISP_NAME, 0); + v4l2_set_subdevdata(sd, isp_dev); + + ret =3D isp_init_formats(sd, NULL); + if (ret < 0) { + dev_err(isp_dev->stfcamss->dev, "Failed to init format: %d\n", + ret); + return ret; + } + + pads[STF_ISP_PAD_SINK].flags =3D MEDIA_PAD_FL_SINK; + pads[STF_ISP_PAD_SRC].flags =3D MEDIA_PAD_FL_SOURCE; + + sd->entity.function =3D MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops =3D &isp_media_ops; + ret =3D media_entity_pads_init(&sd->entity, STF_ISP_PAD_MAX, pads); + if (ret < 0) { + dev_err(isp_dev->stfcamss->dev, + "Failed to init media entity: %d\n", ret); + return ret; + } + + ret =3D v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(isp_dev->stfcamss->dev, + "Failed to register subdev: %d\n", ret); + goto err_sreg; + } + + return 0; + +err_sreg: + media_entity_cleanup(&sd->entity); + return ret; +} + +int stf_isp_unregister(struct stf_isp_dev *isp_dev) +{ + v4l2_device_unregister_subdev(&isp_dev->subdev); + media_entity_cleanup(&isp_dev->subdev.entity); + mutex_destroy(&isp_dev->stream_lock); + mutex_destroy(&isp_dev->power_lock); + return 0; +} diff --git a/drivers/media/platform/starfive/stf_isp.h b/drivers/media/plat= form/starfive/stf_isp.h new file mode 100644 index 000000000000..45bf20af020d --- /dev/null +++ b/drivers/media/platform/starfive/stf_isp.h @@ -0,0 +1,483 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_isp.h + * + * StarFive Camera Subsystem - ISP Module + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_ISP_H +#define STF_ISP_H + +#include +#include + +#include "stf_video.h" + +#define ISP_RAW_DATA_BITS 12 +#define SCALER_RATIO_MAX 1 +#define STF_ISP_REG_OFFSET_MAX 0x0fff +#define STF_ISP_REG_DELAY_MAX 100 + +/* isp registers */ +#define ISP_REG_CSI_INPUT_EN_AND_STATUS 0x000 +#define CSI_SCD_ERR BIT(6) +#define CSI_ITU656_ERR BIT(4) +#define CSI_ITU656_F BIT(3) +#define CSI_SCD_DONE BIT(2) +#define CSI_BUSY_S BIT(1) +#define CSI_EN_S BIT(0) + +#define ISP_REG_CSIINTS 0x008 +#define CSI_INTS(n) ((n) << 16) +#define CSI_SHA_M(n) ((n) << 0) +#define CSI_INTS_MASK GENMASK(17, 16) + +#define ISP_REG_CSI_MODULE_CFG 0x010 +#define CSI_DUMP_EN BIT(19) +#define CSI_VS_EN BIT(18) +#define CSI_SC_EN BIT(17) +#define CSI_OBA_EN BIT(16) +#define CSI_AWB_EN BIT(7) +#define CSI_LCCF_EN BIT(6) +#define CSI_OECFHM_EN BIT(5) +#define CSI_OECF_EN BIT(4) +#define CSI_LCBQ_EN BIT(3) +#define CSI_OBC_EN BIT(2) +#define CSI_DEC_EN BIT(1) +#define CSI_DC_EN BIT(0) + +#define ISP_REG_SENSOR 0x014 +#define DVP_SYNC_POL(n) ((n) << 2) +#define ITU656_EN(n) ((n) << 1) +#define IMAGER_SEL(n) ((n) << 0) + +#define ISP_REG_RAW_FORMAT_CFG 0x018 +#define SMY13(n) ((n) << 14) +#define SMY12(n) ((n) << 12) +#define SMY11(n) ((n) << 10) +#define SMY10(n) ((n) << 8) +#define SMY3(n) ((n) << 6) +#define SMY2(n) ((n) << 4) +#define SMY1(n) ((n) << 2) +#define SMY0(n) ((n) << 0) + +#define ISP_REG_PIC_CAPTURE_START_CFG 0x01c +#define VSTART_CAP(n) ((n) << 16) +#define HSTART_CAP(n) ((n) << 0) + +#define ISP_REG_PIC_CAPTURE_END_CFG 0x020 +#define VEND_CAP(n) ((n) << 16) +#define HEND_CAP(n) ((n) << 0) + +#define ISP_REG_DUMP_CFG_0 0x024 +#define ISP_REG_DUMP_CFG_1 0x028 +#define DUMP_ID(n) ((n) << 24) +#define DUMP_SHT(n) ((n) << 20) +#define DUMP_BURST_LEN(n) ((n) << 16) +#define DUMP_SD(n) ((n) << 0) +#define DUMP_BURST_LEN_MASK GENMASK(17, 16) +#define DUMP_SD_MASK GENMASK(15, 0) + +#define ISP_REG_DEC_CFG 0x030 +#define DEC_V_KEEP(n) ((n) << 24) +#define DEC_V_PERIOD(n) ((n) << 16) +#define DEC_H_KEEP(n) ((n) << 8) +#define DEC_H_PERIOD(n) ((n) << 0) + +#define ISP_REG_OBC_CFG 0x034 +#define OBC_W_H(y) ((y) << 4) +#define OBC_W_W(x) ((x) << 0) + +#define ISP_REG_DC_CFG_1 0x044 +#define DC_AXI_ID(n) ((n) << 0) + +#define ISP_REG_LCCF_CFG_0 0x050 +#define Y_DISTANCE(y) ((y) << 16) +#define X_DISTANCE(x) ((x) << 0) + +#define ISP_REG_LCCF_CFG_1 0x058 +#define LCCF_MAX_DIS(n) ((n) << 0) + +#define ISP_REG_LCBQ_CFG_0 0x074 +#define H_LCBQ(y) ((y) << 12) +#define W_LCBQ(x) ((x) << 8) + +#define ISP_REG_LCBQ_CFG_1 0x07c +#define Y_COOR(y) ((y) << 16) +#define X_COOR(x) ((x) << 0) + +#define ISP_REG_LCCF_CFG_2 0x0e0 +#define ISP_REG_LCCF_CFG_3 0x0e4 +#define ISP_REG_LCCF_CFG_4 0x0e8 +#define ISP_REG_LCCF_CFG_5 0x0ec +#define LCCF_F2_PAR(n) ((n) << 16) +#define LCCF_F1_PAR(n) ((n) << 0) + +#define ISP_REG_OECF_X0_CFG0 0x100 +#define ISP_REG_OECF_X0_CFG1 0x104 +#define ISP_REG_OECF_X0_CFG2 0x108 +#define ISP_REG_OECF_X0_CFG3 0x10c +#define ISP_REG_OECF_X0_CFG4 0x110 +#define ISP_REG_OECF_X0_CFG5 0x114 +#define ISP_REG_OECF_X0_CFG6 0x118 +#define ISP_REG_OECF_X0_CFG7 0x11c + +#define ISP_REG_OECF_Y3_CFG0 0x1e0 +#define ISP_REG_OECF_Y3_CFG1 0x1e4 +#define ISP_REG_OECF_Y3_CFG2 0x1e8 +#define ISP_REG_OECF_Y3_CFG3 0x1ec +#define ISP_REG_OECF_Y3_CFG4 0x1f0 +#define ISP_REG_OECF_Y3_CFG5 0x1f4 +#define ISP_REG_OECF_Y3_CFG6 0x1f8 +#define ISP_REG_OECF_Y3_CFG7 0x1fc + +#define ISP_REG_OECF_S0_CFG0 0x200 +#define ISP_REG_OECF_S3_CFG7 0x27c +#define OCEF_PAR_H(n) ((n) << 16) +#define OCEF_PAR_L(n) ((n) << 0) + +#define ISP_REG_AWB_X0_CFG_0 0x280 +#define ISP_REG_AWB_X0_CFG_1 0x284 +#define ISP_REG_AWB_X1_CFG_0 0x288 +#define ISP_REG_AWB_X1_CFG_1 0x28c +#define ISP_REG_AWB_X2_CFG_0 0x290 +#define ISP_REG_AWB_X2_CFG_1 0x294 +#define ISP_REG_AWB_X3_CFG_0 0x298 +#define ISP_REG_AWB_X3_CFG_1 0x29c +#define AWB_X_SYMBOL_H(n) ((n) << 16) +#define AWB_X_SYMBOL_L(n) ((n) << 0) + +#define ISP_REG_AWB_Y0_CFG_0 0x2a0 +#define ISP_REG_AWB_Y0_CFG_1 0x2a4 +#define ISP_REG_AWB_Y1_CFG_0 0x2a8 +#define ISP_REG_AWB_Y1_CFG_1 0x2ac +#define ISP_REG_AWB_Y2_CFG_0 0x2b0 +#define ISP_REG_AWB_Y2_CFG_1 0x2b4 +#define ISP_REG_AWB_Y3_CFG_0 0x2b8 +#define ISP_REG_AWB_Y3_CFG_1 0x2bc +#define AWB_Y_SYMBOL_H(n) ((n) << 16) +#define AWB_Y_SYMBOL_L(n) ((n) << 0) + +#define ISP_REG_AWB_S0_CFG_0 0x2c0 +#define ISP_REG_AWB_S0_CFG_1 0x2c4 +#define ISP_REG_AWB_S1_CFG_0 0x2c8 +#define ISP_REG_AWB_S1_CFG_1 0x2cc +#define ISP_REG_AWB_S2_CFG_0 0x2d0 +#define ISP_REG_AWB_S2_CFG_1 0x2d4 +#define ISP_REG_AWB_S3_CFG_0 0x2d8 +#define ISP_REG_AWB_S3_CFG_1 0x2dc +#define AWB_S_SYMBOL_H(n) ((n) << 16) +#define AWB_S_SYMBOL_L(n) ((n) << 0) + +#define ISP_REG_OBCG_CFG_0 0x2e0 +#define ISP_REG_OBCG_CFG_1 0x2e4 +#define ISP_REG_OBCG_CFG_2 0x2e8 +#define ISP_REG_OBCG_CFG_3 0x2ec +#define ISP_REG_OBCO_CFG_0 0x2f0 +#define ISP_REG_OBCO_CFG_1 0x2f4 +#define ISP_REG_OBCO_CFG_2 0x2f8 +#define ISP_REG_OBCO_CFG_3 0x2fc +#define GAIN_D_POINT(x) ((x) << 24) +#define GAIN_C_POINT(x) ((x) << 16) +#define GAIN_B_POINT(x) ((x) << 8) +#define GAIN_A_POINT(x) ((x) << 0) +#define OFFSET_D_POINT(x) ((x) << 24) +#define OFFSET_C_POINT(x) ((x) << 16) +#define OFFSET_B_POINT(x) ((x) << 8) +#define OFFSET_A_POINT(x) ((x) << 0) + +#define ISP_REG_ISP_CTRL_0 0xa00 +#define ISPC_SCFEINT BIT(27) +#define ISPC_VSFWINT BIT(26) +#define ISPC_VSINT BIT(25) +#define ISPC_INTS BIT(24) +#define ISPC_ENUO BIT(20) +#define ISPC_ENLS BIT(17) +#define ISPC_ENSS1 BIT(12) +#define ISPC_ENSS0 BIT(11) +#define ISPC_RST BIT(1) +#define ISPC_EN BIT(0) +#define ISPC_RST_MASK BIT(1) + +#define ISP_REG_ISP_CTRL_1 0xa08 +#define CTRL_SAT(n) ((n) << 28) +#define CTRL_DBC BIT(22) +#define CTRL_CTC BIT(21) +#define CTRL_YHIST BIT(20) +#define CTRL_YCURVE BIT(19) +#define CTRL_CTM BIT(18) +#define CTRL_BIYUV BIT(17) +#define CTRL_SCE BIT(8) +#define CTRL_EE BIT(7) +#define CTRL_CCE BIT(5) +#define CTRL_RGE BIT(4) +#define CTRL_CME BIT(3) +#define CTRL_AE BIT(2) +#define CTRL_CE BIT(1) +#define CTRL_SAT_MASK GENMASK(31, 28) + +#define ISP_REG_PIPELINE_XY_SIZE 0xa0c +#define H_ACT_CAP(n) ((n) << 16) +#define W_ACT_CAP(n) ((n) << 0) + +#define ISP_REG_ICTC 0xa10 +#define GF_MODE(n) ((n) << 30) +#define MAXGT(n) ((n) << 16) +#define MINGT(n) ((n) << 0) + +#define ISP_REG_IDBC 0xa14 +#define BADGT(n) ((n) << 16) +#define BADXT(n) ((n) << 0) + +#define ISP_REG_ICFAM 0xa1c +#define CROSS_COV(n) ((n) << 4) +#define HV_W(n) ((n) << 0) + +#define ISP_REG_CS_GAIN 0xa30 +#define CMAD(n) ((n) << 16) +#define CMAB(n) ((n) << 0) + +#define ISP_REG_CS_THRESHOLD 0xa34 +#define CMD(n) ((n) << 16) +#define CMB(n) ((n) << 0) + +#define ISP_REG_CS_OFFSET 0xa38 +#define VOFF(n) ((n) << 16) +#define UOFF(n) ((n) << 0) + +#define ISP_REG_CS_HUE_F 0xa3c +#define SIN(n) ((n) << 16) +#define COS(n) ((n) << 0) + +#define ISP_REG_CS_SCALE 0xa40 +#define CMSF(n) ((n) << 0) + +#define ISP_REG_IESHD 0xa50 +#define SHAD_UP_M BIT(1) +#define SHAD_UP_EN BIT(0) + +#define ISP_REG_YADJ0 0xa54 +#define YOIR(n) ((n) << 16) +#define YIMIN(n) ((n) << 0) + +#define ISP_REG_YADJ1 0xa58 +#define YOMAX(n) ((n) << 16) +#define YOMIN(n) ((n) << 0) + +#define ISP_REG_Y_PLANE_START_ADDR 0xa80 +#define ISP_REG_UV_PLANE_START_ADDR 0xa84 + +#define ISP_REG_STRIDE 0xa88 +#define IMG_STR(n) ((n) << 0) + +#define ISP_REG_ITIIWSR 0xb20 +#define ITI_HSIZE(n) ((n) << 16) +#define ITI_WSIZE(n) ((n) << 0) + +#define ISP_REG_ITIDWLSR 0xb24 +#define ITI_WSTRIDE(n) ((n) << 0) + +#define ISP_REG_ITIPDFR 0xb38 +#define ITI_PACKAGE_FMT(n) ((n) << 0) + +#define ISP_REG_ITIDRLSR 0xb3C +#define ITI_STRIDE_L(n) ((n) << 0) + +#define ISP_REG_DNYUV_YSWR0 0xc00 +#define ISP_REG_DNYUV_YSWR1 0xc04 +#define ISP_REG_DNYUV_CSWR0 0xc08 +#define ISP_REG_DNYUV_CSWR1 0xc0c +#define YUVSW5(n) ((n) << 20) +#define YUVSW4(n) ((n) << 16) +#define YUVSW3(n) ((n) << 12) +#define YUVSW2(n) ((n) << 8) +#define YUVSW1(n) ((n) << 4) +#define YUVSW0(n) ((n) << 0) + +#define ISP_REG_DNYUV_YDR0 0xc10 +#define ISP_REG_DNYUV_YDR1 0xc14 +#define ISP_REG_DNYUV_YDR2 0xc18 +#define ISP_REG_DNYUV_CDR0 0xc1c +#define ISP_REG_DNYUV_CDR1 0xc20 +#define ISP_REG_DNYUV_CDR2 0xc24 +#define CURVE_D_H(n) ((n) << 16) +#define CURVE_D_L(n) ((n) << 0) + +#define ISP_REG_ICAMD_0 0xc40 +#define DNRM_F(n) ((n) << 16) +#define ISP_REG_ICAMD_12 0xc70 +#define ISP_REG_ICAMD_20 0xc90 +#define ISP_REG_ICAMD_24 0xca0 +#define ISP_REG_ICAMD_25 0xca4 +#define CCM_M_DAT(n) ((n) << 0) + +#define ISP_REG_GAMMA_VAL0 0xe00 +#define ISP_REG_GAMMA_VAL1 0xe04 +#define ISP_REG_GAMMA_VAL2 0xe08 +#define ISP_REG_GAMMA_VAL3 0xe0c +#define ISP_REG_GAMMA_VAL4 0xe10 +#define ISP_REG_GAMMA_VAL5 0xe14 +#define ISP_REG_GAMMA_VAL6 0xe18 +#define ISP_REG_GAMMA_VAL7 0xe1c +#define ISP_REG_GAMMA_VAL8 0xe20 +#define ISP_REG_GAMMA_VAL9 0xe24 +#define ISP_REG_GAMMA_VAL10 0xe28 +#define ISP_REG_GAMMA_VAL11 0xe2c +#define ISP_REG_GAMMA_VAL12 0xe30 +#define ISP_REG_GAMMA_VAL13 0xe34 +#define ISP_REG_GAMMA_VAL14 0xe38 +#define GAMMA_S_VAL(n) ((n) << 16) +#define GAMMA_VAL(n) ((n) << 0) + +#define ISP_REG_R2Y_0 0xe40 +#define ISP_REG_R2Y_1 0xe44 +#define ISP_REG_R2Y_2 0xe48 +#define ISP_REG_R2Y_3 0xe4c +#define ISP_REG_R2Y_4 0xe50 +#define ISP_REG_R2Y_5 0xe54 +#define ISP_REG_R2Y_6 0xe58 +#define ISP_REG_R2Y_7 0xe5c +#define ISP_REG_R2Y_8 0xe60 +#define CSC_M(n) ((n) << 0) + +#define ISP_REG_SHARPEN0 0xe80 +#define ISP_REG_SHARPEN1 0xe84 +#define ISP_REG_SHARPEN2 0xe88 +#define ISP_REG_SHARPEN3 0xe8c +#define ISP_REG_SHARPEN4 0xe90 +#define ISP_REG_SHARPEN5 0xe94 +#define ISP_REG_SHARPEN6 0xe98 +#define ISP_REG_SHARPEN7 0xe9c +#define ISP_REG_SHARPEN8 0xea0 +#define ISP_REG_SHARPEN9 0xea4 +#define ISP_REG_SHARPEN10 0xea8 +#define ISP_REG_SHARPEN11 0xeac +#define ISP_REG_SHARPEN12 0xeb0 +#define ISP_REG_SHARPEN13 0xeb4 +#define ISP_REG_SHARPEN14 0xeb8 +#define S_DELTA(n) ((n) << 16) +#define S_WEIGHT(n) ((n) << 8) + +#define ISP_REG_SHARPEN_FS0 0xebc +#define ISP_REG_SHARPEN_FS1 0xec0 +#define ISP_REG_SHARPEN_FS2 0xec4 +#define ISP_REG_SHARPEN_FS3 0xec8 +#define ISP_REG_SHARPEN_FS4 0xecc +#define ISP_REG_SHARPEN_FS5 0xed0 +#define S_FACTOR(n) ((n) << 24) +#define S_SLOPE(n) ((n) << 0) + +#define ISP_REG_SHARPEN_WN 0xed4 +#define PDIRF(n) ((n) << 28) +#define NDIRF(n) ((n) << 24) +#define WSUM(n) ((n) << 0) + +#define ISP_REG_IUVS1 0xed8 +#define UVDIFF2(n) ((n) << 16) +#define UVDIFF1(n) ((n) << 0) + +#define ISP_REG_IUVS2 0xedc +#define UVF(n) ((n) << 24) +#define UVSLOPE(n) ((n) << 0) + +#define ISP_REG_IUVCKS1 0xee0 +#define UVCKDIFF2(n) ((n) << 16) +#define UVCKDIFF1(n) ((n) << 0) + +#define ISP_REG_IUVCKS2 0xee4 +#define UVCKSLOPE(n) ((n) << 0) + +#define ISP_REG_ISHRPET 0xee8 +#define TH(n) ((n) << 8) +#define EN(n) ((n) << 0) + +#define ISP_REG_YCURVE_0 0xf00 +#define ISP_REG_YCURVE_63 0xffc +#define L_PARAM(n) ((n) << 0) + +#define IMAGE_MAX_WIDTH 1920 +#define IMAGE_MAX_HEIGH 1080 + +/* The output line of ISP */ +enum isp_line_id { + STF_ISP_LINE_INVALID =3D -1, + STF_ISP_LINE_SRC =3D 1, + STF_ISP_LINE_MAX =3D STF_ISP_LINE_SRC +}; + +/* pad id for media framework */ +enum isp_pad_id { + STF_ISP_PAD_SINK =3D 0, + STF_ISP_PAD_SRC, + STF_ISP_PAD_MAX +}; + +enum { + EN_INT_NONE =3D 0, + EN_INT_ISP_DONE =3D (0x1 << 24), + EN_INT_CSI_DONE =3D (0x1 << 25), + EN_INT_SC_DONE =3D (0x1 << 26), + EN_INT_LINE_INT =3D (0x1 << 27), + EN_INT_ALL =3D (0xF << 24), +}; + +enum { + INTERFACE_DVP =3D 0, + INTERFACE_CSI, +}; + +struct isp_format { + u32 code; + u8 bpp; +}; + +struct isp_format_table { + const struct isp_format *fmts; + int nfmts; +}; + +struct regval_t { + u32 addr; + u32 val; + u32 delay_ms; +}; + +struct reg_table { + const struct regval_t *regval; + int regval_num; +}; + +struct stf_isp_crop { + struct v4l2_rect rect; + u32 bpp; +}; + +struct stf_isp_dev { + enum stf_subdev_type sdev_type; /* This member must be first */ + struct stfcamss *stfcamss; + struct v4l2_subdev subdev; + struct media_pad pads[STF_ISP_PAD_MAX]; + struct v4l2_mbus_framefmt fmt[STF_ISP_PAD_MAX]; + struct stf_isp_crop rect[STF_ISP_PAD_MAX]; + const struct isp_format_table *formats; + unsigned int nformats; + struct mutex power_lock; /* serialize power control*/ + int power_count; + struct mutex stream_lock; /* serialize stream control */ + int stream_count; +}; + +int stf_isp_clk_enable(struct stf_isp_dev *isp_dev); +int stf_isp_clk_disable(struct stf_isp_dev *isp_dev); +int stf_isp_reset(struct stf_isp_dev *isp_dev); +void stf_isp_init_cfg(struct stf_isp_dev *isp_dev); +void stf_isp_settings(struct stf_isp_dev *isp_dev, + struct stf_isp_crop *crop_array, u32 mcode); +void stf_isp_stream_set(struct stf_isp_dev *isp_dev); +int stf_isp_subdev_init(struct stfcamss *stfcamss); +int stf_isp_register(struct stf_isp_dev *isp_dev, struct v4l2_device *v4l2= _dev); +int stf_isp_unregister(struct stf_isp_dev *isp_dev); + +#endif /* STF_ISP_H */ diff --git a/drivers/media/platform/starfive/stf_isp_hw_ops.c b/drivers/med= ia/platform/starfive/stf_isp_hw_ops.c new file mode 100644 index 000000000000..2088b6bd0d61 --- /dev/null +++ b/drivers/media/platform/starfive/stf_isp_hw_ops.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_isp_hw_ops.c + * + * Register interface file for StarFive ISP driver + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + * + */ + +#include "stf_camss.h" + +static void stf_isp_config_obc(struct stfcamss *stfcamss) +{ + u32 reg_val, reg_add; + + stf_isp_reg_write(stfcamss, ISP_REG_OBC_CFG, OBC_W_H(11) | OBC_W_W(11)); + + reg_val =3D GAIN_D_POINT(0x40) | GAIN_C_POINT(0x40) | + GAIN_B_POINT(0x40) | GAIN_A_POINT(0x40); + for (reg_add =3D ISP_REG_OBCG_CFG_0; reg_add <=3D ISP_REG_OBCG_CFG_3;) { + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + } + + reg_val =3D OFFSET_D_POINT(0) | OFFSET_C_POINT(0) | + OFFSET_B_POINT(0) | OFFSET_A_POINT(0); + for (reg_add =3D ISP_REG_OBCO_CFG_0; reg_add <=3D ISP_REG_OBCO_CFG_3;) { + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + } +} + +static void stf_isp_config_oecf(struct stfcamss *stfcamss) +{ + u32 reg_add, par_val; + u16 par_h, par_l; + + par_h =3D 0x10; par_l =3D 0; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG0; reg_add <=3D ISP_REG_OECF_Y3_CFG0;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x40; par_l =3D 0x20; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG1; reg_add <=3D ISP_REG_OECF_Y3_CFG1;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x80; par_l =3D 0x60; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG2; reg_add <=3D ISP_REG_OECF_Y3_CFG2;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0xc0; par_l =3D 0xa0; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG3; reg_add <=3D ISP_REG_OECF_Y3_CFG3;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x100; par_l =3D 0xe0; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG4; reg_add <=3D ISP_REG_OECF_Y3_CFG4;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x200; par_l =3D 0x180; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG5; reg_add <=3D ISP_REG_OECF_Y3_CFG5;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x300; par_l =3D 0x280; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG6; reg_add <=3D ISP_REG_OECF_Y3_CFG6;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x3fe; par_l =3D 0x380; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_X0_CFG7; reg_add <=3D ISP_REG_OECF_Y3_CFG7;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 0x20; + } + + par_h =3D 0x80; par_l =3D 0x80; + par_val =3D OCEF_PAR_H(par_h) | OCEF_PAR_L(par_l); + for (reg_add =3D ISP_REG_OECF_S0_CFG0; reg_add <=3D ISP_REG_OECF_S3_CFG7;= ) { + stf_isp_reg_write(stfcamss, reg_add, par_val); + reg_add +=3D 4; + } +} + +static void stf_isp_config_lccf(struct stfcamss *stfcamss) +{ + u32 reg_add; + + stf_isp_reg_write(stfcamss, ISP_REG_LCCF_CFG_0, + Y_DISTANCE(0x21C) | X_DISTANCE(0x3C0)); + stf_isp_reg_write(stfcamss, ISP_REG_LCCF_CFG_1, LCCF_MAX_DIS(0xb)); + + for (reg_add =3D ISP_REG_LCCF_CFG_2; reg_add <=3D ISP_REG_LCCF_CFG_5;) { + stf_isp_reg_write(stfcamss, reg_add, + LCCF_F2_PAR(0x0) | LCCF_F1_PAR(0x0)); + reg_add +=3D 4; + } +} + +static void stf_isp_config_awb(struct stfcamss *stfcamss) +{ + u32 reg_val, reg_add; + u16 symbol_h, symbol_l; + + symbol_h =3D 0x0; symbol_l =3D 0x0; + reg_val =3D AWB_X_SYMBOL_H(symbol_h) | AWB_X_SYMBOL_L(symbol_l); + + for (reg_add =3D ISP_REG_AWB_X0_CFG_0; reg_add <=3D ISP_REG_AWB_X3_CFG_1;= ) { + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + } + + symbol_h =3D 0x0, symbol_l =3D 0x0; + reg_val =3D AWB_Y_SYMBOL_H(symbol_h) | AWB_Y_SYMBOL_L(symbol_l); + + for (reg_add =3D ISP_REG_AWB_Y0_CFG_0; reg_add <=3D ISP_REG_AWB_Y3_CFG_1;= ) { + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + } + + symbol_h =3D 0x80, symbol_l =3D 0x80; + reg_val =3D AWB_S_SYMBOL_H(symbol_h) | AWB_S_SYMBOL_L(symbol_l); + + for (reg_add =3D ISP_REG_AWB_S0_CFG_0; reg_add <=3D ISP_REG_AWB_S3_CFG_1;= ) { + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + } +} + +static void stf_isp_config_grgb(struct stfcamss *stfcamss) +{ + stf_isp_reg_write(stfcamss, ISP_REG_ICTC, + GF_MODE(1) | MAXGT(0x140) | MINGT(0x40)); + stf_isp_reg_write(stfcamss, ISP_REG_IDBC, BADGT(0x200) | BADXT(0x200)); +} + +static void stf_isp_config_cfa(struct stfcamss *stfcamss) +{ + stf_isp_reg_write(stfcamss, ISP_REG_RAW_FORMAT_CFG, + SMY13(0) | SMY12(1) | SMY11(0) | SMY10(1) | SMY3(2) | + SMY2(3) | SMY1(2) | SMY0(3)); + stf_isp_reg_write(stfcamss, ISP_REG_ICFAM, CROSS_COV(3) | HV_W(2)); +} + +static void stf_isp_config_ccm(struct stfcamss *stfcamss) +{ + u32 reg_add; + + stf_isp_reg_write(stfcamss, ISP_REG_ICAMD_0, DNRM_F(6) | CCM_M_DAT(0)); + + for (reg_add =3D ISP_REG_ICAMD_12; reg_add <=3D ISP_REG_ICAMD_20;) { + stf_isp_reg_write(stfcamss, reg_add, CCM_M_DAT(0x80)); + reg_add +=3D 0x10; + } + + stf_isp_reg_write(stfcamss, ISP_REG_ICAMD_24, CCM_M_DAT(0x700)); + stf_isp_reg_write(stfcamss, ISP_REG_ICAMD_25, CCM_M_DAT(0x200)); +} + +static void stf_isp_config_gamma(struct stfcamss *stfcamss) +{ + u32 reg_val, reg_add; + u16 gamma_slope_v, gamma_v; + + gamma_slope_v =3D 0x2400; gamma_v =3D 0x0; + reg_val =3D GAMMA_S_VAL(gamma_slope_v) | GAMMA_VAL(gamma_v); + stf_isp_reg_write(stfcamss, ISP_REG_GAMMA_VAL0, reg_val); + + gamma_slope_v =3D 0x800; gamma_v =3D 0x20; + for (reg_add =3D ISP_REG_GAMMA_VAL1; reg_add <=3D ISP_REG_GAMMA_VAL7;) { + reg_val =3D GAMMA_S_VAL(gamma_slope_v) | GAMMA_VAL(gamma_v); + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + gamma_v +=3D 0x20; + } + + gamma_v =3D 0x100; + for (reg_add =3D ISP_REG_GAMMA_VAL8; reg_add <=3D ISP_REG_GAMMA_VAL13;) { + reg_val =3D GAMMA_S_VAL(gamma_slope_v) | GAMMA_VAL(gamma_v); + stf_isp_reg_write(stfcamss, reg_add, reg_val); + reg_add +=3D 4; + gamma_v +=3D 0x80; + } + + gamma_v =3D 0x3fe; + reg_val =3D GAMMA_S_VAL(gamma_slope_v) | GAMMA_VAL(gamma_v); + stf_isp_reg_write(stfcamss, ISP_REG_GAMMA_VAL14, reg_val); +} + +static void stf_isp_config_r2y(struct stfcamss *stfcamss) +{ + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_0, CSC_M(0x4C)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_1, CSC_M(0x97)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_2, CSC_M(0x1d)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_3, CSC_M(0x1d5)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_4, CSC_M(0x1ac)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_5, CSC_M(0x80)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_6, CSC_M(0x80)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_7, CSC_M(0x194)); + stf_isp_reg_write(stfcamss, ISP_REG_R2Y_8, CSC_M(0x1ec)); +} + +static void stf_isp_config_y_curve(struct stfcamss *stfcamss) +{ + u32 reg_add; + u16 y_curve; + + y_curve =3D 0x0; + for (reg_add =3D ISP_REG_YCURVE_0; reg_add <=3D ISP_REG_YCURVE_63;) { + stf_isp_reg_write(stfcamss, reg_add, y_curve); + reg_add +=3D 4; + y_curve +=3D 0x10; + } +} + +static void stf_isp_config_sharpen(struct stfcamss *sc) +{ + u32 reg_add; + + stf_isp_reg_write(sc, ISP_REG_SHARPEN0, S_DELTA(0x7) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN1, S_DELTA(0x18) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN2, S_DELTA(0x80) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN3, S_DELTA(0x100) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN4, S_DELTA(0x10) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN5, S_DELTA(0x60) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN6, S_DELTA(0x100) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN7, S_DELTA(0x190) | S_WEIGHT(0xf)); + stf_isp_reg_write(sc, ISP_REG_SHARPEN8, S_DELTA(0x0) | S_WEIGHT(0xf)); + + for (reg_add =3D ISP_REG_SHARPEN9; reg_add <=3D ISP_REG_SHARPEN14;) { + stf_isp_reg_write(sc, reg_add, S_WEIGHT(0xf)); + reg_add +=3D 4; + } + + for (reg_add =3D ISP_REG_SHARPEN_FS0; reg_add <=3D ISP_REG_SHARPEN_FS5;) { + stf_isp_reg_write(sc, reg_add, S_FACTOR(0x10) | S_SLOPE(0x0)); + reg_add +=3D 4; + } + + stf_isp_reg_write(sc, ISP_REG_SHARPEN_WN, + PDIRF(0x8) | NDIRF(0x8) | WSUM(0xd7c)); + stf_isp_reg_write(sc, ISP_REG_IUVS1, UVDIFF2(0xC0) | UVDIFF1(0x40)); + stf_isp_reg_write(sc, ISP_REG_IUVS2, UVF(0xff) | UVSLOPE(0x0)); + stf_isp_reg_write(sc, ISP_REG_IUVCKS1, + UVCKDIFF2(0xa0) | UVCKDIFF1(0x40)); +} + +static void stf_isp_config_dnyuv(struct stfcamss *stfcamss) +{ + u32 reg_val; + + reg_val =3D YUVSW5(7) | YUVSW4(7) | YUVSW3(7) | YUVSW2(7) | + YUVSW1(7) | YUVSW0(7); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_YSWR0, reg_val); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_CSWR0, reg_val); + + reg_val =3D YUVSW3(7) | YUVSW2(7) | YUVSW1(7) | YUVSW0(7); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_YSWR1, reg_val); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_CSWR1, reg_val); + + reg_val =3D CURVE_D_H(0x60) | CURVE_D_L(0x40); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_YDR0, reg_val); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_CDR0, reg_val); + + reg_val =3D CURVE_D_H(0xd8) | CURVE_D_L(0x90); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_YDR1, reg_val); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_CDR1, reg_val); + + reg_val =3D CURVE_D_H(0x1e6) | CURVE_D_L(0x144); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_YDR2, reg_val); + stf_isp_reg_write(stfcamss, ISP_REG_DNYUV_CDR2, reg_val); +} + +static void stf_isp_config_sat(struct stfcamss *stfcamss) +{ + stf_isp_reg_write(stfcamss, ISP_REG_CS_GAIN, CMAD(0x0) | CMAB(0x100)); + stf_isp_reg_write(stfcamss, ISP_REG_CS_THRESHOLD, CMD(0x1f) | CMB(0x1)); + stf_isp_reg_write(stfcamss, ISP_REG_CS_OFFSET, VOFF(0x0) | UOFF(0x0)); + stf_isp_reg_write(stfcamss, ISP_REG_CS_HUE_F, SIN(0x0) | COS(0x100)); + stf_isp_reg_write(stfcamss, ISP_REG_CS_SCALE, CMSF(0x8)); + stf_isp_reg_write(stfcamss, ISP_REG_YADJ0, YOIR(0x401) | YIMIN(0x1)); + stf_isp_reg_write(stfcamss, ISP_REG_YADJ1, YOMAX(0x3ff) | YOMIN(0x1)); +} + +int stf_isp_clk_enable(struct stf_isp_dev *isp_dev) +{ + struct stfcamss *stfcamss =3D isp_dev->stfcamss; + + clk_prepare_enable(stfcamss->sys_clk[STF_CLK_WRAPPER_CLK_C].clk); + reset_control_deassert(stfcamss->sys_rst[STF_RST_WRAPPER_C].rstc); + reset_control_deassert(stfcamss->sys_rst[STF_RST_WRAPPER_P].rstc); + + return 0; +} + +int stf_isp_clk_disable(struct stf_isp_dev *isp_dev) +{ + struct stfcamss *stfcamss =3D isp_dev->stfcamss; + + reset_control_assert(stfcamss->sys_rst[STF_RST_WRAPPER_C].rstc); + reset_control_assert(stfcamss->sys_rst[STF_RST_WRAPPER_P].rstc); + clk_disable_unprepare(stfcamss->sys_clk[STF_CLK_WRAPPER_CLK_C].clk); + + return 0; +} + +int stf_isp_reset(struct stf_isp_dev *isp_dev) +{ + stf_isp_reg_set_bit(isp_dev->stfcamss, ISP_REG_ISP_CTRL_0, + ISPC_RST_MASK, ISPC_RST); + stf_isp_reg_set_bit(isp_dev->stfcamss, ISP_REG_ISP_CTRL_0, + ISPC_RST_MASK, 0); + + return 0; +} + +void stf_isp_init_cfg(struct stf_isp_dev *isp_dev) +{ + stf_isp_reg_write(isp_dev->stfcamss, ISP_REG_DC_CFG_1, DC_AXI_ID(0x0)); + stf_isp_reg_write(isp_dev->stfcamss, ISP_REG_DEC_CFG, + DEC_V_KEEP(0x0) | + DEC_V_PERIOD(0x0) | + DEC_H_KEEP(0x0) | + DEC_H_PERIOD(0x0)); + + stf_isp_config_obc(isp_dev->stfcamss); + stf_isp_config_oecf(isp_dev->stfcamss); + stf_isp_config_lccf(isp_dev->stfcamss); + stf_isp_config_awb(isp_dev->stfcamss); + stf_isp_config_grgb(isp_dev->stfcamss); + stf_isp_config_cfa(isp_dev->stfcamss); + stf_isp_config_ccm(isp_dev->stfcamss); + stf_isp_config_gamma(isp_dev->stfcamss); + stf_isp_config_r2y(isp_dev->stfcamss); + stf_isp_config_y_curve(isp_dev->stfcamss); + stf_isp_config_sharpen(isp_dev->stfcamss); + stf_isp_config_dnyuv(isp_dev->stfcamss); + stf_isp_config_sat(isp_dev->stfcamss); + + stf_isp_reg_write(isp_dev->stfcamss, ISP_REG_CSI_MODULE_CFG, + CSI_DUMP_EN | CSI_SC_EN | CSI_AWB_EN | + CSI_LCCF_EN | CSI_OECF_EN | CSI_OBC_EN | CSI_DEC_EN); + stf_isp_reg_write(isp_dev->stfcamss, ISP_REG_ISP_CTRL_1, + CTRL_SAT(1) | CTRL_DBC | CTRL_CTC | CTRL_YHIST | + CTRL_YCURVE | CTRL_BIYUV | CTRL_SCE | CTRL_EE | + CTRL_CCE | CTRL_RGE | CTRL_CME | CTRL_AE | CTRL_CE); +} + +static void stf_isp_config_crop(struct stfcamss *stfcamss, + struct stf_isp_crop *crop) +{ + struct v4l2_rect *rect =3D &crop[STF_ISP_PAD_SRC].rect; + u32 bpp =3D crop[STF_ISP_PAD_SRC].bpp; + u32 val; + + val =3D VSTART_CAP(rect->top) | HSTART_CAP(rect->left); + stf_isp_reg_write(stfcamss, ISP_REG_PIC_CAPTURE_START_CFG, val); + + val =3D VEND_CAP(rect->height + rect->top - 1) | + HEND_CAP(rect->width + rect->left - 1); + stf_isp_reg_write(stfcamss, ISP_REG_PIC_CAPTURE_END_CFG, val); + + val =3D H_ACT_CAP(rect->height) | W_ACT_CAP(rect->width); + stf_isp_reg_write(stfcamss, ISP_REG_PIPELINE_XY_SIZE, val); + + val =3D ALIGN(rect->width * bpp / 8, STFCAMSS_FRAME_WIDTH_ALIGN_8); + stf_isp_reg_write(stfcamss, ISP_REG_STRIDE, val); +} + +static void stf_isp_config_raw_fmt(struct stfcamss *stfcamss, u32 mcode) +{ + u32 val, val1; + + switch (mcode) { + case MEDIA_BUS_FMT_SRGGB10_1X10: + case MEDIA_BUS_FMT_SRGGB8_1X8: + /* 3 2 3 2 1 0 1 0 B Gb B Gb Gr R Gr R */ + val =3D SMY13(3) | SMY12(2) | SMY11(3) | SMY10(2) | + SMY3(1) | SMY2(0) | SMY1(1) | SMY0(0); + val1 =3D CTRL_SAT(0x0); + break; + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SGRBG8_1X8: + /* 2 3 2 3 0 1 0 1, Gb B Gb B R Gr R Gr */ + val =3D SMY13(2) | SMY12(3) | SMY11(2) | SMY10(3) | + SMY3(0) | SMY2(1) | SMY1(0) | SMY0(1); + val1 =3D CTRL_SAT(0x2); + break; + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGBRG8_1X8: + /* 1 0 1 0 3 2 3 2, Gr R Gr R B Gb B Gb */ + val =3D SMY13(1) | SMY12(0) | SMY11(1) | SMY10(0) | + SMY3(3) | SMY2(2) | SMY1(3) | SMY0(2); + val1 =3D CTRL_SAT(0x3); + break; + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SBGGR8_1X8: + /* 0 1 0 1 2 3 2 3 R Gr R Gr Gb B Gb B */ + val =3D SMY13(0) | SMY12(1) | SMY11(0) | SMY10(1) | + SMY3(2) | SMY2(3) | SMY1(2) | SMY0(3); + val1 =3D CTRL_SAT(0x1); + break; + default: + val =3D SMY13(0) | SMY12(1) | SMY11(0) | SMY10(1) | + SMY3(2) | SMY2(3) | SMY1(2) | SMY0(3); + val1 =3D CTRL_SAT(0x1); + break; + } + stf_isp_reg_write(stfcamss, ISP_REG_RAW_FORMAT_CFG, val); + stf_isp_reg_set_bit(stfcamss, ISP_REG_ISP_CTRL_1, CTRL_SAT_MASK, val1); +} + +void stf_isp_settings(struct stf_isp_dev *isp_dev, + struct stf_isp_crop *crop, u32 mcode) +{ + struct stfcamss *stfcamss =3D isp_dev->stfcamss; + + stf_isp_config_crop(stfcamss, crop); + stf_isp_config_raw_fmt(stfcamss, mcode); + + stf_isp_reg_set_bit(stfcamss, ISP_REG_DUMP_CFG_1, + DUMP_BURST_LEN_MASK | DUMP_SD_MASK, + DUMP_BURST_LEN(3)); + + stf_isp_reg_write(stfcamss, ISP_REG_ITIIWSR, + ITI_HSIZE(IMAGE_MAX_HEIGH) | + ITI_WSIZE(IMAGE_MAX_WIDTH)); + stf_isp_reg_write(stfcamss, ISP_REG_ITIDWLSR, ITI_WSTRIDE(0x960)); + stf_isp_reg_write(stfcamss, ISP_REG_ITIDRLSR, ITI_STRIDE_L(0x960)); + stf_isp_reg_write(stfcamss, ISP_REG_SENSOR, 0x1); +} + +void stf_isp_stream_set(struct stf_isp_dev *isp_dev) +{ + struct stfcamss *stfcamss =3D isp_dev->stfcamss; + + stf_isp_reg_write_delay(stfcamss, ISP_REG_ISP_CTRL_0, + ISPC_ENUO | ISPC_ENLS | ISPC_RST, 10); + stf_isp_reg_write_delay(stfcamss, ISP_REG_ISP_CTRL_0, + ISPC_ENUO | ISPC_ENLS, 10); + stf_isp_reg_write(stfcamss, ISP_REG_IESHD, SHAD_UP_M); + stf_isp_reg_write_delay(stfcamss, ISP_REG_ISP_CTRL_0, + ISPC_ENUO | ISPC_ENLS | ISPC_EN, 10); + stf_isp_reg_write_delay(stfcamss, ISP_REG_CSIINTS, + CSI_INTS(1) | CSI_SHA_M(4), 10); + stf_isp_reg_write_delay(stfcamss, ISP_REG_CSIINTS, + CSI_INTS(2) | CSI_SHA_M(4), 10); + stf_isp_reg_write_delay(stfcamss, ISP_REG_CSI_INPUT_EN_AND_STATUS, + CSI_EN_S, 10); +} --=20 2.34.1 From nobody Sat Feb 7 15:12:06 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C85B4C77B7A for ; Thu, 25 May 2023 08:32:48 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S240163AbjEYIcp convert rfc822-to-8bit (ORCPT ); Thu, 25 May 2023 04:32:45 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:56180 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S239989AbjEYIcQ (ORCPT ); Thu, 25 May 2023 04:32:16 -0400 Received: from fd01.gateway.ufhost.com (fd01.gateway.ufhost.com [61.152.239.71]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 61B53197; Thu, 25 May 2023 01:32:07 -0700 (PDT) Received: from EXMBX165.cuchost.com (unknown [175.102.18.54]) (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits)) (Client CN "EXMBX165", Issuer "EXMBX165" (not verified)) by fd01.gateway.ufhost.com (Postfix) with ESMTP id 16A19831C; Thu, 25 May 2023 16:32:06 +0800 (CST) Received: from EXMBX173.cuchost.com (172.16.6.93) by EXMBX165.cuchost.com (172.16.6.75) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:06 +0800 Received: from xiaofei.localdomain (180.164.60.184) by EXMBX173.cuchost.com (172.16.6.93) with Microsoft SMTP Server (TLS) id 15.0.1497.42; Thu, 25 May 2023 16:32:05 +0800 From: Jack Zhu To: Mauro Carvalho Chehab , Robert Foss , Todor Tomov , , Rob Herring , "Krzysztof Kozlowski" , Conor Dooley , Philipp Zabel , "Laurent Pinchart" , Hans Verkuil , Eugen Hristev , Ezequiel Garcia CC: , , , , Subject: [PATCH v6 6/6] media: starfive: Add VIN driver Date: Thu, 25 May 2023 16:32:02 +0800 Message-ID: <20230525083202.67933-7-jack.zhu@starfivetech.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20230525083202.67933-1-jack.zhu@starfivetech.com> References: <20230525083202.67933-1-jack.zhu@starfivetech.com> MIME-Version: 1.0 X-Originating-IP: [180.164.60.184] X-ClientProxiedBy: EXCAS066.cuchost.com (172.16.6.26) To EXMBX173.cuchost.com (172.16.6.93) X-YovoleRuleAgent: yovoleflag Content-Transfer-Encoding: quoted-printable Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Type: text/plain; charset="utf-8" Add Video In Controller driver for StarFive Camera Subsystem. Signed-off-by: Jack Zhu --- drivers/media/platform/starfive/Makefile | 4 +- drivers/media/platform/starfive/stf_camss.c | 39 +- drivers/media/platform/starfive/stf_camss.h | 2 + drivers/media/platform/starfive/stf_vin.c | 1134 +++++++++++++++++ drivers/media/platform/starfive/stf_vin.h | 180 +++ .../media/platform/starfive/stf_vin_hw_ops.c | 241 ++++ 6 files changed, 1598 insertions(+), 2 deletions(-) create mode 100644 drivers/media/platform/starfive/stf_vin.c create mode 100644 drivers/media/platform/starfive/stf_vin.h create mode 100644 drivers/media/platform/starfive/stf_vin_hw_ops.c diff --git a/drivers/media/platform/starfive/Makefile b/drivers/media/platf= orm/starfive/Makefile index 60a3018f4994..bf497dd24bbb 100644 --- a/drivers/media/platform/starfive/Makefile +++ b/drivers/media/platform/starfive/Makefile @@ -7,6 +7,8 @@ starfive-camss-objs +=3D \ stf_camss.o \ stf_isp.o \ stf_isp_hw_ops.o \ - stf_video.o + stf_video.o \ + stf_vin.o \ + stf_vin_hw_ops.o =20 obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) +=3D starfive-camss.o \ diff --git a/drivers/media/platform/starfive/stf_camss.c b/drivers/media/pl= atform/starfive/stf_camss.c index 0d8406ba6a40..266de75f55b2 100644 --- a/drivers/media/platform/starfive/stf_camss.c +++ b/drivers/media/platform/starfive/stf_camss.c @@ -142,27 +142,61 @@ static int stfcamss_init_subdevices(struct stfcamss *= stfcamss) return ret; } =20 + ret =3D stf_vin_subdev_init(stfcamss); + if (ret < 0) { + dev_err(stfcamss->dev, "Failed to init vin subdev: %d\n", ret); + return ret; + } return ret; } =20 static int stfcamss_register_subdevices(struct stfcamss *stfcamss) { int ret; + struct stf_vin_dev *vin_dev =3D &stfcamss->vin_dev; struct stf_isp_dev *isp_dev =3D &stfcamss->isp_dev; =20 ret =3D stf_isp_register(isp_dev, &stfcamss->v4l2_dev); if (ret < 0) { dev_err(stfcamss->dev, "Failed to register stf isp%d entity: %d\n", 0, ret); - return ret; + goto err_reg_isp; + } + + ret =3D stf_vin_register(vin_dev, &stfcamss->v4l2_dev); + if (ret < 0) { + dev_err(stfcamss->dev, + "Failed to register vin entity: %d\n", ret); + goto err_reg_vin; } =20 + ret =3D media_create_pad_link(&isp_dev->subdev.entity, + STF_ISP_PAD_SRC, + &vin_dev->line[VIN_LINE_ISP].subdev.entity, + STF_VIN_PAD_SINK, + 0); + if (ret < 0) { + dev_err(stfcamss->dev, + "Failed to link %s->%s entities: %d\n", + isp_dev->subdev.entity.name, + vin_dev->line[VIN_LINE_ISP].subdev.entity.name, ret); + goto err_link; + } + + return ret; + +err_link: + stf_vin_unregister(&stfcamss->vin_dev); +err_reg_vin: + stf_isp_unregister(&stfcamss->isp_dev); +err_reg_isp: return ret; } =20 static void stfcamss_unregister_subdevices(struct stfcamss *stfcamss) { stf_isp_unregister(&stfcamss->isp_dev); + stf_vin_unregister(&stfcamss->vin_dev); } =20 static int stfcamss_subdev_notifier_bound(struct v4l2_async_notifier *asyn= c, @@ -175,11 +209,14 @@ static int stfcamss_subdev_notifier_bound(struct v4l2= _async_notifier *async, container_of(asd, struct stfcamss_async_subdev, asd); enum port_num port =3D csd->port; struct stf_isp_dev *isp_dev =3D &stfcamss->isp_dev; + struct stf_vin_dev *vin_dev =3D &stfcamss->vin_dev; struct host_data *host_data =3D &stfcamss->host_data; struct media_entity *source; int i, j; =20 if (port =3D=3D PORT_NUMBER_CSI2RX) { + host_data->host_entity[0] =3D + &vin_dev->line[VIN_LINE_WR].subdev.entity; host_data->host_entity[1] =3D &isp_dev->subdev.entity; } else if (port =3D=3D PORT_NUMBER_DVP_SENSOR) { dev_err(stfcamss->dev, "Not support DVP sensor\n"); diff --git a/drivers/media/platform/starfive/stf_camss.h b/drivers/media/pl= atform/starfive/stf_camss.h index c9d94be92ea8..688bb3b7b6d0 100644 --- a/drivers/media/platform/starfive/stf_camss.h +++ b/drivers/media/platform/starfive/stf_camss.h @@ -17,6 +17,7 @@ =20 #include "stf_common.h" #include "stf_isp.h" +#include "stf_vin.h" =20 #define DRV_NAME "starfive-camss" #define STF_DVP_NAME "stf_dvp" @@ -72,6 +73,7 @@ struct stfcamss { struct media_device media_dev; struct media_pipeline pipe; struct device *dev; + struct stf_vin_dev vin_dev; struct stf_isp_dev isp_dev; struct v4l2_async_notifier notifier; struct host_data host_data; diff --git a/drivers/media/platform/starfive/stf_vin.c b/drivers/media/plat= form/starfive/stf_vin.c new file mode 100644 index 000000000000..21b0afa4ca3a --- /dev/null +++ b/drivers/media/platform/starfive/stf_vin.c @@ -0,0 +1,1134 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_vin.c + * + * StarFive Camera Subsystem - VIN Module + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ +#include + +#include "stf_camss.h" + +#define vin_line_array(ptr_line) \ + ((const struct vin_line (*)[]) &(ptr_line)[-((ptr_line)->id)]) + +#define line_to_vin_dev(ptr_line) \ + container_of(vin_line_array(ptr_line), struct stf_vin_dev, line) + +#define VIN_FRAME_DROP_MAX_VAL 90 +#define VIN_FRAME_DROP_MIN_VAL 4 +#define VIN_FRAME_PER_SEC_MAX_VAL 90 + +/* ISP ctrl need 1 sec to let frames become stable. */ +#define VIN_FRAME_DROP_SEC_FOR_ISP_CTRL 1 + +static const struct vin_format vin_formats_wr[] =3D { + { MEDIA_BUS_FMT_SRGGB10_1X10, 10}, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10}, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10}, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10}, +}; + +static const struct vin_format vin_formats_uo[] =3D { + { MEDIA_BUS_FMT_Y12_1X12, 8}, +}; + +static const struct vin_format_table vin_formats_table[] =3D { + /* VIN_LINE_WR */ + { vin_formats_wr, ARRAY_SIZE(vin_formats_wr) }, + /* VIN_LINE_ISP */ + { vin_formats_uo, ARRAY_SIZE(vin_formats_uo) }, +}; + +static void vin_buffer_done(struct vin_line *line); +static void vin_change_buffer(struct vin_line *line); +static struct stfcamss_buffer *vin_buf_get_pending(struct vin_output *outp= ut); +static void vin_output_init_addrs(struct vin_line *line); +static void vin_init_outputs(struct vin_line *line); +static struct v4l2_mbus_framefmt * +__vin_get_format(struct vin_line *line, + struct v4l2_subdev_state *state, + unsigned int pad, + enum v4l2_subdev_format_whence which); + +static char *vin_get_line_subdevname(int line_id) +{ + char *name =3D NULL; + + switch (line_id) { + case VIN_LINE_WR: + name =3D "wr"; + break; + case VIN_LINE_ISP: + name =3D "isp0"; + break; + default: + name =3D "unknown"; + break; + } + return name; +} + +static enum isp_line_id vin_map_isp_line(enum vin_line_id line) +{ + enum isp_line_id line_id; + + if (line > VIN_LINE_WR && line < VIN_LINE_MAX) + line_id =3D STF_ISP_LINE_SRC; + else + line_id =3D STF_ISP_LINE_INVALID; + + return line_id; +} + +enum isp_pad_id stf_vin_map_isp_pad(enum vin_line_id line, enum isp_pad_id= def) +{ + enum isp_pad_id pad_id; + + if (line =3D=3D VIN_LINE_WR) + pad_id =3D STF_ISP_PAD_SINK; + else if ((line > VIN_LINE_WR) && (line < VIN_LINE_MAX)) + pad_id =3D (enum isp_pad_id)vin_map_isp_line(line); + else + pad_id =3D def; + + return pad_id; +} + +int stf_vin_subdev_init(struct stfcamss *stfcamss) +{ + struct device *dev =3D stfcamss->dev; + struct stf_vin_dev *vin_dev =3D &stfcamss->vin_dev; + int i, ret =3D 0; + + vin_dev->stfcamss =3D stfcamss; + + vin_dev->isr_ops =3D devm_kzalloc(dev, sizeof(*vin_dev->isr_ops), + GFP_KERNEL); + if (!vin_dev->isr_ops) + return -ENOMEM; + vin_dev->isr_ops->isr_buffer_done =3D vin_buffer_done; + vin_dev->isr_ops->isr_change_buffer =3D vin_change_buffer; + + atomic_set(&vin_dev->ref_count, 0); + + ret =3D devm_request_irq(dev, + stfcamss->irq[STF_IRQ_VINWR], + stf_vin_wr_irq_handler, + 0, "vin_axiwr_irq", vin_dev); + if (ret) { + dev_err(dev, "Failed to request irq\n"); + goto out; + } + + ret =3D devm_request_irq(dev, + stfcamss->irq[STF_IRQ_ISP], + stf_vin_isp_irq_handler, + 0, "vin_isp_irq", vin_dev); + if (ret) { + dev_err(dev, "Failed to request isp irq\n"); + goto out; + } + + ret =3D devm_request_irq(dev, + stfcamss->irq[STF_IRQ_ISPCSIL], + stf_vin_isp_irq_csiline_handler, + 0, "vin_isp_irq_csiline", vin_dev); + if (ret) { + dev_err(dev, "failed to request isp irq csiline\n"); + goto out; + } + + mutex_init(&vin_dev->power_lock); + vin_dev->power_count =3D 0; + + for (i =3D 0; i < STF_DUMMY_MODULE_NUMS; i++) { + struct dummy_buffer *dummy_buffer =3D &vin_dev->dummy_buffer[i]; + + mutex_init(&dummy_buffer->stream_lock); + dummy_buffer->nums =3D + i =3D=3D 0 ? VIN_DUMMY_BUFFER_NUMS : ISP_DUMMY_BUFFER_NUMS; + dummy_buffer->stream_count =3D 0; + dummy_buffer->buffer =3D + devm_kzalloc(dev, + dummy_buffer->nums * sizeof(struct vin_dummy_buffer), + GFP_KERNEL); + atomic_set(&dummy_buffer->frame_skip, 0); + } + + for (i =3D VIN_LINE_WR; i < STF_ISP_LINE_MAX + 1; i++) { + struct vin_line *l =3D &vin_dev->line[i]; + + l->video_out.type =3D V4L2_BUF_TYPE_VIDEO_CAPTURE; + l->video_out.stfcamss =3D stfcamss; + l->id =3D i; + l->sdev_type =3D STF_SUBDEV_TYPE_VIN; + l->formats =3D vin_formats_table[i].fmts; + l->nformats =3D vin_formats_table[i].nfmts; + spin_lock_init(&l->output_lock); + + mutex_init(&l->stream_lock); + l->stream_count =3D 0; + mutex_init(&l->power_lock); + l->power_count =3D 0; + } + + return 0; +out: + return ret; +} + +static enum link vin_get_link(struct media_entity *entity) +{ + struct v4l2_subdev *subdev; + struct media_pad *pad; + bool isp =3D false; + + while (1) { + pad =3D &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + return LINK_ERROR; + + pad =3D media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + return LINK_ERROR; + + entity =3D pad->entity; + subdev =3D media_entity_to_v4l2_subdev(entity); + + if (!strncmp(subdev->name, STF_CSI_NAME, + strlen(STF_CSI_NAME))) { + if (isp) + return LINK_CSI_TO_ISP; + else + return LINK_CSI_TO_WR; + } else if (!strncmp(subdev->name, STF_DVP_NAME, + strlen(STF_DVP_NAME))) { + if (isp) + return LINK_DVP_TO_ISP; + else + return LINK_DVP_TO_WR; + } else if (!strncmp(subdev->name, STF_ISP_NAME, + strlen(STF_ISP_NAME))) { + isp =3D true; + } else { + return LINK_ERROR; + } + } +} + +static int vin_set_power(struct v4l2_subdev *sd, int on) +{ + struct vin_line *line =3D v4l2_get_subdevdata(sd); + struct stf_vin_dev *vin_dev =3D line_to_vin_dev(line); + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + enum link link; + + mutex_lock(&line->power_lock); + if (on) { + if (line->power_count =3D=3D 0) + vin_init_outputs(line); + line->power_count++; + } else { + if (line->power_count =3D=3D 0) { + dev_err(stfcamss->dev, + "line power off on power_count =3D 0\n"); + goto exit_line; + } + line->power_count--; + } +exit_line: + mutex_unlock(&line->power_lock); + + mutex_lock(&vin_dev->power_lock); + link =3D vin_get_link(&sd->entity); + if (link =3D=3D LINK_ERROR) + goto exit; + + if (on) { + if (vin_dev->power_count =3D=3D 0) { + pm_runtime_get_sync(stfcamss->dev); + stf_vin_clk_enable(vin_dev, link); + } + vin_dev->power_count++; + } else { + if (vin_dev->power_count =3D=3D 0) { + dev_err(stfcamss->dev, + "vin_dev power off on power_count=3D0\n"); + goto exit; + } + if (vin_dev->power_count =3D=3D 1) { + stf_vin_clk_disable(vin_dev, link); + pm_runtime_put_sync(stfcamss->dev); + } + vin_dev->power_count--; + } +exit: + + mutex_unlock(&vin_dev->power_lock); + + return 0; +} + +static int vin_enable_output(struct vin_line *line) +{ + struct vin_output *output =3D &line->output; + unsigned long flags; + + spin_lock_irqsave(&line->output_lock, flags); + + output->state =3D VIN_OUTPUT_IDLE; + + output->buf[0] =3D vin_buf_get_pending(output); + + if (!output->buf[0] && output->buf[1]) { + output->buf[0] =3D output->buf[1]; + output->buf[1] =3D NULL; + } + + if (output->buf[0]) + output->state =3D VIN_OUTPUT_SINGLE; + + output->sequence =3D 0; + + vin_output_init_addrs(line); + spin_unlock_irqrestore(&line->output_lock, flags); + return 0; +} + +static int vin_disable_output(struct vin_line *line) +{ + struct vin_output *output =3D &line->output; + unsigned long flags; + + spin_lock_irqsave(&line->output_lock, flags); + + output->state =3D VIN_OUTPUT_OFF; + + spin_unlock_irqrestore(&line->output_lock, flags); + return 0; +} + +static u32 vin_line_to_dummy_module(struct vin_line *line) +{ + u32 dummy_module =3D 0; + + switch (line->id) { + case VIN_LINE_WR: + dummy_module =3D STF_DUMMY_VIN; + break; + case VIN_LINE_ISP: + dummy_module =3D STF_DUMMY_ISP; + break; + default: + dummy_module =3D STF_DUMMY_VIN; + break; + } + + return dummy_module; +} + +static int vin_alloc_dummy_buffer(struct stf_vin_dev *vin_dev, + struct v4l2_mbus_framefmt *fmt, + int dummy_module) +{ + struct device *dev =3D vin_dev->stfcamss->dev; + struct dummy_buffer *dummy_buffer =3D + &vin_dev->dummy_buffer[dummy_module]; + struct vin_dummy_buffer *buffer =3D NULL; + int ret =3D 0, i; + u32 aligns; + + for (i =3D 0; i < dummy_buffer->nums; i++) { + buffer =3D &vin_dev->dummy_buffer[dummy_module].buffer[i]; + buffer->width =3D fmt->width; + buffer->height =3D fmt->height; + buffer->mcode =3D fmt->code; + if (i =3D=3D STF_VIN_PAD_SINK) { + aligns =3D ALIGN(fmt->width * 4, + STFCAMSS_FRAME_WIDTH_ALIGN_8); + buffer->buffer_size =3D PAGE_ALIGN(aligns * fmt->height); + } else if (i =3D=3D STF_ISP_PAD_SRC) { + aligns =3D ALIGN(fmt->width, + STFCAMSS_FRAME_WIDTH_ALIGN_8); + buffer->buffer_size =3D + PAGE_ALIGN(aligns * fmt->height * 3 / 2); + } else { + continue; + } + + buffer->vaddr =3D dma_alloc_coherent(dev, + buffer->buffer_size, + &buffer->paddr[0], + GFP_DMA | GFP_KERNEL); + + if (buffer->vaddr) { + if (i =3D=3D STF_ISP_PAD_SRC) + buffer->paddr[1] =3D + (dma_addr_t)(buffer->paddr[0] + aligns * fmt->height); + else + dev_dbg(dev, "signal plane\n"); + } + } + + return ret; +} + +static void vin_free_dummy_buffer(struct stf_vin_dev *vin_dev, int dummy_m= odule) +{ + struct device *dev =3D vin_dev->stfcamss->dev; + struct dummy_buffer *dummy_buffer =3D + &vin_dev->dummy_buffer[dummy_module]; + struct vin_dummy_buffer *buffer =3D NULL; + int i; + + for (i =3D 0; i < dummy_buffer->nums; i++) { + buffer =3D &dummy_buffer->buffer[i]; + if (buffer->vaddr) + dma_free_coherent(dev, buffer->buffer_size, + buffer->vaddr, buffer->paddr[0]); + memset(buffer, 0, sizeof(struct vin_dummy_buffer)); + } +} + +static void vin_set_dummy_buffer(struct vin_line *line, u32 pad) +{ + struct stf_vin_dev *vin_dev =3D line_to_vin_dev(line); + int dummy_module =3D vin_line_to_dummy_module(line); + struct dummy_buffer *dummy_buffer =3D + &vin_dev->dummy_buffer[dummy_module]; + struct vin_dummy_buffer *buffer =3D NULL; + + switch (pad) { + case STF_VIN_PAD_SINK: + if (line->id =3D=3D VIN_LINE_WR) { + buffer =3D &dummy_buffer->buffer[STF_VIN_PAD_SINK]; + stf_vin_wr_set_ping_addr(vin_dev, buffer->paddr[0]); + stf_vin_wr_set_pong_addr(vin_dev, buffer->paddr[0]); + } else { + buffer =3D &dummy_buffer->buffer[STF_ISP_PAD_SRC]; + stf_vin_isp_set_yuv_addr(vin_dev, + buffer->paddr[0], + buffer->paddr[1]); + } + break; + case STF_ISP_PAD_SRC: + buffer =3D &dummy_buffer->buffer[STF_ISP_PAD_SRC]; + stf_vin_isp_set_yuv_addr(vin_dev, + buffer->paddr[0], + buffer->paddr[1]); + break; + default: + break; + } +} + +static int vin_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vin_line *line =3D v4l2_get_subdevdata(sd); + struct stf_vin_dev *vin_dev =3D line_to_vin_dev(line); + int dummy_module =3D vin_line_to_dummy_module(line); + struct dummy_buffer *dummy_buffer =3D + &vin_dev->dummy_buffer[dummy_module]; + struct v4l2_mbus_framefmt *fmt; + enum link link; + + fmt =3D __vin_get_format(line, NULL, + STF_VIN_PAD_SINK, V4L2_SUBDEV_FORMAT_ACTIVE); + mutex_lock(&dummy_buffer->stream_lock); + if (enable) { + if (dummy_buffer->stream_count =3D=3D 0) { + vin_alloc_dummy_buffer(vin_dev, fmt, dummy_module); + vin_set_dummy_buffer(line, STF_VIN_PAD_SINK); + atomic_set(&dummy_buffer->frame_skip, + VIN_FRAME_DROP_MIN_VAL + 30); + } + dummy_buffer->stream_count++; + } else { + if (dummy_buffer->stream_count =3D=3D 1) { + vin_free_dummy_buffer(vin_dev, dummy_module); + /* set buffer addr to zero */ + vin_set_dummy_buffer(line, STF_VIN_PAD_SINK); + } else { + vin_set_dummy_buffer(line, + stf_vin_map_isp_pad(line->id, STF_ISP_PAD_SINK)); + } + + dummy_buffer->stream_count--; + } + mutex_unlock(&dummy_buffer->stream_lock); + + mutex_lock(&line->stream_lock); + link =3D vin_get_link(&sd->entity); + if (link =3D=3D LINK_ERROR) + goto exit; + + if (enable) { + if (line->stream_count =3D=3D 0) { + stf_vin_stream_set(vin_dev, link); + if (line->id =3D=3D VIN_LINE_WR) { + stf_vin_wr_irq_enable(vin_dev, 1); + stf_vin_wr_stream_set(vin_dev); + } + } + line->stream_count++; + } else { + if (line->stream_count =3D=3D 1 && line->id =3D=3D VIN_LINE_WR) + stf_vin_wr_irq_enable(vin_dev, 0); + line->stream_count--; + } +exit: + mutex_unlock(&line->stream_lock); + + if (enable) + vin_enable_output(line); + else + vin_disable_output(line); + + return 0; +} + +static struct v4l2_mbus_framefmt * +__vin_get_format(struct vin_line *line, + struct v4l2_subdev_state *state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which =3D=3D V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&line->subdev, state, pad); + return &line->fmt[pad]; +} + +static void vin_try_format(struct vin_line *line, + struct v4l2_subdev_state *state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case STF_VIN_PAD_SINK: + /* Set format on sink pad */ + for (i =3D 0; i < line->nformats; i++) + if (fmt->code =3D=3D line->formats[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >=3D line->nformats) + fmt->code =3D line->formats[0].code; + + fmt->width =3D clamp_t(u32, fmt->width, + STFCAMSS_FRAME_MIN_WIDTH, + STFCAMSS_FRAME_MAX_WIDTH); + fmt->height =3D clamp_t(u32, fmt->height, + STFCAMSS_FRAME_MIN_HEIGHT, + STFCAMSS_FRAME_MAX_HEIGHT); + + fmt->field =3D V4L2_FIELD_NONE; + fmt->colorspace =3D V4L2_COLORSPACE_SRGB; + fmt->flags =3D 0; + break; + + case STF_VIN_PAD_SRC: + /* Set and return a format same as sink pad */ + *fmt =3D *__vin_get_format(line, state, STF_VIN_PAD_SINK, which); + break; + } + + fmt->colorspace =3D V4L2_COLORSPACE_SRGB; +} + +static int vin_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vin_line *line =3D v4l2_get_subdevdata(sd); + + if (code->index >=3D line->nformats) + return -EINVAL; + if (code->pad =3D=3D STF_VIN_PAD_SINK) { + code->code =3D line->formats[code->index].code; + } else { + struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt =3D __vin_get_format(line, state, STF_VIN_PAD_SINK, + code->which); + + code->code =3D sink_fmt->code; + if (!code->code) + return -EINVAL; + } + code->flags =3D 0; + + return 0; +} + +static int vin_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vin_line *line =3D v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index !=3D 0) + return -EINVAL; + + format.code =3D fse->code; + format.width =3D 1; + format.height =3D 1; + vin_try_format(line, state, fse->pad, &format, fse->which); + fse->min_width =3D format.width; + fse->min_height =3D format.height; + + if (format.code !=3D fse->code) + return -EINVAL; + + format.code =3D fse->code; + format.width =3D -1; + format.height =3D -1; + vin_try_format(line, state, fse->pad, &format, fse->which); + fse->max_width =3D format.width; + fse->max_height =3D format.height; + + return 0; +} + +static int vin_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct vin_line *line =3D v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format =3D __vin_get_format(line, state, fmt->pad, fmt->which); + if (!format) + return -EINVAL; + + fmt->format =3D *format; + + return 0; +} + +static int vin_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *fmt) +{ + struct vin_line *line =3D v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format =3D __vin_get_format(line, state, fmt->pad, fmt->which); + if (!format) + return -EINVAL; + + mutex_lock(&line->stream_lock); + if (line->stream_count) { + fmt->format =3D *format; + mutex_unlock(&line->stream_lock); + goto out; + } else { + vin_try_format(line, state, fmt->pad, &fmt->format, fmt->which); + *format =3D fmt->format; + } + mutex_unlock(&line->stream_lock); + + if (fmt->pad =3D=3D STF_VIN_PAD_SINK) { + /* Propagate the format from sink to source */ + format =3D __vin_get_format(line, state, STF_VIN_PAD_SRC, + fmt->which); + + *format =3D fmt->format; + vin_try_format(line, state, STF_VIN_PAD_SRC, format, + fmt->which); + } + +out: + return 0; +} + +static int vin_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format =3D { + .pad =3D STF_VIN_PAD_SINK, + .which =3D fh ? + V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE, + .format =3D { + .code =3D MEDIA_BUS_FMT_RGB565_2X8_LE, + .width =3D 1920, + .height =3D 1080 + } + }; + + return vin_set_format(sd, fh ? fh->state : NULL, &format); +} + +static void vin_output_init_addrs(struct vin_line *line) +{ + struct vin_output *output =3D &line->output; + struct stf_vin_dev *vin_dev =3D line_to_vin_dev(line); + dma_addr_t ping_addr; + dma_addr_t y_addr, uv_addr; + + output->active_buf =3D 0; + + if (output->buf[0]) { + ping_addr =3D output->buf[0]->addr[0]; + y_addr =3D output->buf[0]->addr[0]; + uv_addr =3D output->buf[0]->addr[1]; + } else { + return; + } + + switch (vin_map_isp_line(line->id)) { + case STF_ISP_LINE_SRC: + stf_vin_isp_set_yuv_addr(vin_dev, y_addr, uv_addr); + break; + default: + if (line->id =3D=3D VIN_LINE_WR) { + stf_vin_wr_set_ping_addr(vin_dev, ping_addr); + stf_vin_wr_set_pong_addr(vin_dev, ping_addr); + } + break; + } +} + +static void vin_init_outputs(struct vin_line *line) +{ + struct vin_output *output =3D &line->output; + + output->state =3D VIN_OUTPUT_OFF; + output->buf[0] =3D NULL; + output->buf[1] =3D NULL; + output->active_buf =3D 0; + INIT_LIST_HEAD(&output->pending_bufs); + INIT_LIST_HEAD(&output->ready_bufs); +} + +static void vin_buf_add_ready(struct vin_output *output, + struct stfcamss_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->queue); + list_add_tail(&buffer->queue, &output->ready_bufs); +} + +static struct stfcamss_buffer *vin_buf_get_ready(struct vin_output *output) +{ + struct stfcamss_buffer *buffer =3D NULL; + + if (!list_empty(&output->ready_bufs)) { + buffer =3D list_first_entry(&output->ready_bufs, + struct stfcamss_buffer, + queue); + list_del(&buffer->queue); + } + + return buffer; +} + +static void vin_buf_add_pending(struct vin_output *output, + struct stfcamss_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->queue); + list_add_tail(&buffer->queue, &output->pending_bufs); +} + +static struct stfcamss_buffer *vin_buf_get_pending(struct vin_output *outp= ut) +{ + struct stfcamss_buffer *buffer =3D NULL; + + if (!list_empty(&output->pending_bufs)) { + buffer =3D list_first_entry(&output->pending_bufs, + struct stfcamss_buffer, + queue); + list_del(&buffer->queue); + } + + return buffer; +} + +static void vin_buf_update_on_last(struct vin_line *line) +{ + struct vin_output *output =3D &line->output; + + switch (output->state) { + case VIN_OUTPUT_CONTINUOUS: + output->state =3D VIN_OUTPUT_SINGLE; + output->active_buf =3D !output->active_buf; + break; + case VIN_OUTPUT_SINGLE: + output->state =3D VIN_OUTPUT_STOPPING; + break; + default: + break; + } +} + +static void vin_buf_update_on_next(struct vin_line *line) +{ + struct vin_output *output =3D &line->output; + + switch (output->state) { + case VIN_OUTPUT_CONTINUOUS: + output->active_buf =3D !output->active_buf; + break; + case VIN_OUTPUT_SINGLE: + default: + break; + } +} + +static void vin_buf_update_on_new(struct vin_line *line, + struct vin_output *output, + struct stfcamss_buffer *new_buf) +{ + switch (output->state) { + case VIN_OUTPUT_SINGLE: + vin_buf_add_pending(output, new_buf); + break; + case VIN_OUTPUT_IDLE: + if (!output->buf[0]) { + output->buf[0] =3D new_buf; + vin_output_init_addrs(line); + output->state =3D VIN_OUTPUT_SINGLE; + } else { + vin_buf_add_pending(output, new_buf); + } + break; + case VIN_OUTPUT_STOPPING: + if (output->last_buffer) { + output->buf[output->active_buf] =3D output->last_buffer; + output->last_buffer =3D NULL; + } + + output->state =3D VIN_OUTPUT_SINGLE; + vin_buf_add_pending(output, new_buf); + break; + case VIN_OUTPUT_CONTINUOUS: + default: + vin_buf_add_pending(output, new_buf); + break; + } +} + +static void vin_buf_flush(struct vin_output *output, + enum vb2_buffer_state state) +{ + struct stfcamss_buffer *buf; + struct stfcamss_buffer *t; + + list_for_each_entry_safe(buf, t, &output->pending_bufs, queue) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->queue); + } + list_for_each_entry_safe(buf, t, &output->ready_bufs, queue) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->queue); + } +} + +static void vin_buffer_done(struct vin_line *line) +{ + struct stfcamss_buffer *ready_buf; + struct vin_output *output =3D &line->output; + unsigned long flags; + u64 ts =3D ktime_get_ns(); + + if (output->state =3D=3D VIN_OUTPUT_OFF || + output->state =3D=3D VIN_OUTPUT_RESERVED) + return; + + spin_lock_irqsave(&line->output_lock, flags); + + while ((ready_buf =3D vin_buf_get_ready(output))) { + ready_buf->vb.vb2_buf.timestamp =3D ts; + ready_buf->vb.sequence =3D output->sequence++; + + vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + spin_unlock_irqrestore(&line->output_lock, flags); +} + +static void vin_change_buffer(struct vin_line *line) +{ + struct stfcamss_buffer *ready_buf; + struct vin_output *output =3D &line->output; + struct stf_vin_dev *vin_dev =3D line_to_vin_dev(line); + dma_addr_t *new_addr; + unsigned long flags; + u32 active_index; + + if (output->state =3D=3D VIN_OUTPUT_OFF || + output->state =3D=3D VIN_OUTPUT_STOPPING || + output->state =3D=3D VIN_OUTPUT_RESERVED || + output->state =3D=3D VIN_OUTPUT_IDLE) + return; + + spin_lock_irqsave(&line->output_lock, flags); + + active_index =3D output->active_buf; + + ready_buf =3D output->buf[active_index]; + if (!ready_buf) { + dev_warn(vin_dev->stfcamss->dev, "Missing ready buf %d %d!\n", + active_index, output->state); + active_index =3D !active_index; + ready_buf =3D output->buf[active_index]; + if (!ready_buf) { + dev_err(vin_dev->stfcamss->dev, + "Missing ready buf2 %d %d!\n", + active_index, output->state); + goto out_unlock; + } + } + + /* Get next buffer */ + output->buf[active_index] =3D vin_buf_get_pending(output); + if (!output->buf[active_index]) { + /* No next buffer - set same address */ + new_addr =3D ready_buf->addr; + vin_buf_update_on_last(line); + } else { + new_addr =3D output->buf[active_index]->addr; + vin_buf_update_on_next(line); + } + + if (output->state =3D=3D VIN_OUTPUT_STOPPING) { + output->last_buffer =3D ready_buf; + } else { + switch (vin_map_isp_line(line->id)) { + case STF_ISP_LINE_SRC: + stf_vin_isp_set_yuv_addr(vin_dev, + new_addr[0], + new_addr[1]); + break; + default: + if (line->id =3D=3D VIN_LINE_WR) { + stf_vin_wr_set_ping_addr(vin_dev, new_addr[0]); + stf_vin_wr_set_pong_addr(vin_dev, new_addr[0]); + } + break; + } + + vin_buf_add_ready(output, ready_buf); + } + + spin_unlock_irqrestore(&line->output_lock, flags); + return; + +out_unlock: + spin_unlock_irqrestore(&line->output_lock, flags); +} + +static int vin_queue_buffer(struct stfcamss_video *vid, + struct stfcamss_buffer *buf) +{ + struct vin_line *line =3D container_of(vid, struct vin_line, video_out); + struct vin_output *output; + unsigned long flags; + + output =3D &line->output; + + spin_lock_irqsave(&line->output_lock, flags); + + vin_buf_update_on_new(line, output, buf); + + spin_unlock_irqrestore(&line->output_lock, flags); + + return 0; +} + +static int vin_flush_buffers(struct stfcamss_video *vid, + enum vb2_buffer_state state) +{ + struct vin_line *line =3D container_of(vid, struct vin_line, video_out); + struct vin_output *output =3D &line->output; + unsigned long flags; + + spin_lock_irqsave(&line->output_lock, flags); + + vin_buf_flush(output, state); + if (output->buf[0]) + vb2_buffer_done(&output->buf[0]->vb.vb2_buf, state); + + if (output->buf[1]) + vb2_buffer_done(&output->buf[1]->vb.vb2_buf, state); + + if (output->last_buffer) { + vb2_buffer_done(&output->last_buffer->vb.vb2_buf, state); + output->last_buffer =3D NULL; + } + output->buf[0] =3D NULL; + output->buf[1] =3D NULL; + + spin_unlock_irqrestore(&line->output_lock, flags); + return 0; +} + +static int vin_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) + if (media_pad_remote_pad_first(local)) + return -EBUSY; + return 0; +} + +static const struct v4l2_subdev_core_ops vin_core_ops =3D { + .s_power =3D vin_set_power, +}; + +static const struct v4l2_subdev_video_ops vin_video_ops =3D { + .s_stream =3D vin_set_stream, +}; + +static const struct v4l2_subdev_pad_ops vin_pad_ops =3D { + .enum_mbus_code =3D vin_enum_mbus_code, + .enum_frame_size =3D vin_enum_frame_size, + .get_fmt =3D vin_get_format, + .set_fmt =3D vin_set_format, +}; + +static const struct v4l2_subdev_ops vin_v4l2_ops =3D { + .core =3D &vin_core_ops, + .video =3D &vin_video_ops, + .pad =3D &vin_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops vin_v4l2_internal_ops =3D { + .open =3D vin_init_formats, +}; + +static const struct stfcamss_video_ops stfcamss_vin_video_ops =3D { + .queue_buffer =3D vin_queue_buffer, + .flush_buffers =3D vin_flush_buffers, +}; + +static const struct media_entity_operations vin_media_ops =3D { + .link_setup =3D vin_link_setup, + .link_validate =3D v4l2_subdev_link_validate, +}; + +int stf_vin_register(struct stf_vin_dev *vin_dev, struct v4l2_device *v4l2= _dev) +{ + struct v4l2_subdev *sd; + struct stfcamss_video *video_out; + struct media_pad *pads; + int ret; + int i; + + for (i =3D 0; i < STF_ISP_LINE_MAX + 1; i++) { + char name[32]; + char *sub_name =3D vin_get_line_subdevname(i); + + sd =3D &vin_dev->line[i].subdev; + pads =3D vin_dev->line[i].pads; + video_out =3D &vin_dev->line[i].video_out; + video_out->id =3D i; + + v4l2_subdev_init(sd, &vin_v4l2_ops); + sd->internal_ops =3D &vin_v4l2_internal_ops; + sd->flags |=3D V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s", + STF_VIN_NAME, 0, sub_name); + v4l2_set_subdevdata(sd, &vin_dev->line[i]); + + ret =3D vin_init_formats(sd, NULL); + if (ret < 0) { + dev_err(vin_dev->stfcamss->dev, + "Failed to init format: %d\n", ret); + goto err_init; + } + + pads[STF_VIN_PAD_SINK].flags =3D MEDIA_PAD_FL_SINK; + pads[STF_VIN_PAD_SRC].flags =3D MEDIA_PAD_FL_SOURCE; + + sd->entity.function =3D + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops =3D &vin_media_ops; + ret =3D media_entity_pads_init(&sd->entity, + STF_VIN_PADS_NUM, pads); + if (ret < 0) { + dev_err(vin_dev->stfcamss->dev, + "Failed to init media entity: %d\n", + ret); + goto err_init; + } + + ret =3D v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(vin_dev->stfcamss->dev, + "Failed to register subdev: %d\n", ret); + goto err_reg_subdev; + } + + video_out->ops =3D &stfcamss_vin_video_ops; + video_out->bpl_alignment =3D 16 * 8; + + snprintf(name, ARRAY_SIZE(name), "%s_%s%d", + sd->name, "video", i); + ret =3D stf_video_register(video_out, v4l2_dev, name); + if (ret < 0) { + dev_err(vin_dev->stfcamss->dev, + "Failed to register video node: %d\n", ret); + goto err_vid_reg; + } + + ret =3D media_create_pad_link( + &sd->entity, STF_VIN_PAD_SRC, + &video_out->vdev.entity, 0, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(vin_dev->stfcamss->dev, + "Failed to link %s->%s entities: %d\n", + sd->entity.name, video_out->vdev.entity.name, + ret); + goto err_create_link; + } + } + + return 0; + +err_create_link: + stf_video_unregister(video_out); +err_vid_reg: + v4l2_device_unregister_subdev(sd); +err_reg_subdev: + media_entity_cleanup(&sd->entity); +err_init: + for (i--; i >=3D 0; i--) { + sd =3D &vin_dev->line[i].subdev; + video_out =3D &vin_dev->line[i].video_out; + + stf_video_unregister(video_out); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } + return ret; +} + +int stf_vin_unregister(struct stf_vin_dev *vin_dev) +{ + struct v4l2_subdev *sd; + struct stfcamss_video *video_out; + int i; + + mutex_destroy(&vin_dev->power_lock); + for (i =3D 0; i < STF_DUMMY_MODULE_NUMS; i++) + mutex_destroy(&vin_dev->dummy_buffer[i].stream_lock); + + for (i =3D 0; i < STF_ISP_LINE_MAX + 1; i++) { + sd =3D &vin_dev->line[i].subdev; + video_out =3D &vin_dev->line[i].video_out; + + stf_video_unregister(video_out); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + mutex_destroy(&vin_dev->line[i].stream_lock); + mutex_destroy(&vin_dev->line[i].power_lock); + } + return 0; +} diff --git a/drivers/media/platform/starfive/stf_vin.h b/drivers/media/plat= form/starfive/stf_vin.h new file mode 100644 index 000000000000..9db71d09e044 --- /dev/null +++ b/drivers/media/platform/starfive/stf_vin.h @@ -0,0 +1,180 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * stf_vin.h + * + * StarFive Camera Subsystem - VIN Module + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ + +#ifndef STF_VIN_H +#define STF_VIN_H + +#include +#include +#include + +#include "stf_video.h" + +#define SYSCONSAIF_SYSCFG(x) (x) + +/* syscon offset 0 */ +#define U0_VIN_CNFG_AXI_DVP_EN BIT(2) + +/* syscon offset 20 */ +#define U0_VIN_CHANNEL_SEL_MASK GENMASK(3, 0) +#define U0_VIN_AXIWR0_EN BIT(4) +#define CHANNEL(x) ((x) << 0) + +/* syscon offset 32 */ +#define U0_VIN_INTR_CLEAN BIT(0) +#define U0_VIN_INTR_M BIT(1) +#define U0_VIN_PIX_CNT_END_MASK GENMASK(12, 2) +#define U0_VIN_PIX_CT_MASK GENMASK(14, 13) +#define U0_VIN_PIXEL_HEIGH_BIT_SEL_MAKS GENMASK(16, 15) + +#define PIX_CNT_END(x) ((x) << 2) +#define PIX_CT(x) ((x) << 13) +#define PIXEL_HEIGH_BIT_SEL(x) ((x) << 15) + +/* syscon offset 36 */ +#define U0_VIN_CNFG_DVP_HS_POS BIT(1) +#define U0_VIN_CNFG_DVP_SWAP_EN BIT(2) +#define U0_VIN_CNFG_DVP_VS_POS BIT(3) +#define U0_VIN_CNFG_GEN_EN_AXIRD BIT(4) +#define U0_VIN_CNFG_ISP_DVP_EN0 BIT(5) +#define U0_VIN_MIPI_BYTE_EN_ISP0(n) ((n) << 6) +#define U0_VIN_MIPI_CHANNEL_SEL0(n) ((n) << 8) +#define U0_VIN_P_I_MIPI_HAEDER_EN0(n) ((n) << 12) +#define U0_VIN_PIX_NUM(n) ((n) << 13) +#define U0_VIN_MIPI_BYTE_EN_ISP0_MASK GENMASK(7, 6) +#define U0_VIN_MIPI_CHANNEL_SEL0_MASK GENMASK(11, 8) +#define U0_VIN_P_I_MIPI_HAEDER_EN0_MASK BIT(12) +#define U0_VIN_PIX_NUM_MASK GENMASK(16, 13) + +#define STF_VIN_PAD_SINK 0 +#define STF_VIN_PAD_SRC 1 +#define STF_VIN_PADS_NUM 2 + +#define ISP_DUMMY_BUFFER_NUMS STF_ISP_PAD_MAX +#define VIN_DUMMY_BUFFER_NUMS 1 + +enum { + STF_DUMMY_VIN, + STF_DUMMY_ISP, + STF_DUMMY_MODULE_NUMS, +}; + +enum link { + LINK_ERROR =3D -1, + LINK_DVP_TO_WR, + LINK_DVP_TO_ISP, + LINK_CSI_TO_WR, + LINK_CSI_TO_ISP, +}; + +struct vin_format { + u32 code; + u8 bpp; +}; + +struct vin_format_table { + const struct vin_format *fmts; + int nfmts; +}; + +enum vin_output_state { + VIN_OUTPUT_OFF, + VIN_OUTPUT_RESERVED, + VIN_OUTPUT_SINGLE, + VIN_OUTPUT_CONTINUOUS, + VIN_OUTPUT_IDLE, + VIN_OUTPUT_STOPPING +}; + +struct vin_output { + int active_buf; + struct stfcamss_buffer *buf[2]; + struct stfcamss_buffer *last_buffer; + struct list_head pending_bufs; + struct list_head ready_bufs; + enum vin_output_state state; + unsigned int sequence; + unsigned int frame_skip; +}; + +/* The vin output lines */ +enum vin_line_id { + VIN_LINE_NONE =3D -1, + VIN_LINE_WR =3D 0, + VIN_LINE_ISP, + VIN_LINE_MAX, +}; + +struct vin_line { + enum stf_subdev_type sdev_type; /* must be frist */ + enum vin_line_id id; + struct v4l2_subdev subdev; + struct media_pad pads[STF_VIN_PADS_NUM]; + struct v4l2_mbus_framefmt fmt[STF_VIN_PADS_NUM]; + struct stfcamss_video video_out; + struct mutex stream_lock; /* serialize stream control */ + int stream_count; + struct mutex power_lock; /* serialize pipeline control in power process*/ + int power_count; + struct vin_output output; /* pipeline and stream states */ + spinlock_t output_lock; + const struct vin_format *formats; + unsigned int nformats; +}; + +struct vin_dummy_buffer { + dma_addr_t paddr[3]; + void *vaddr; + u32 buffer_size; + u32 width; + u32 height; + u32 mcode; +}; + +struct dummy_buffer { + struct vin_dummy_buffer *buffer; + u32 nums; + struct mutex stream_lock; /* protects buffer data */ + int stream_count; + atomic_t frame_skip; +}; + +struct vin_isr_ops { + void (*isr_buffer_done)(struct vin_line *line); + void (*isr_change_buffer)(struct vin_line *line); +}; + +struct stf_vin_dev { + struct stfcamss *stfcamss; + struct vin_line line[VIN_LINE_MAX]; + struct dummy_buffer dummy_buffer[STF_DUMMY_MODULE_NUMS]; + struct vin_isr_ops *isr_ops; + atomic_t ref_count; + struct mutex power_lock; /* serialize power control*/ + int power_count; +}; + +int stf_vin_clk_enable(struct stf_vin_dev *vin_dev, enum link link); +int stf_vin_clk_disable(struct stf_vin_dev *vin_dev, enum link link); +int stf_vin_wr_stream_set(struct stf_vin_dev *vin_dev); +int stf_vin_stream_set(struct stf_vin_dev *vin_dev, enum link link); +void stf_vin_wr_irq_enable(struct stf_vin_dev *vin_dev, int enable); +void stf_vin_wr_set_ping_addr(struct stf_vin_dev *vin_dev, dma_addr_t addr= ); +void stf_vin_wr_set_pong_addr(struct stf_vin_dev *vin_dev, dma_addr_t addr= ); +void stf_vin_isp_set_yuv_addr(struct stf_vin_dev *vin_dev, + dma_addr_t y_addr, dma_addr_t uv_addr); +irqreturn_t stf_vin_wr_irq_handler(int irq, void *priv); +irqreturn_t stf_vin_isp_irq_handler(int irq, void *priv); +irqreturn_t stf_vin_isp_irq_csiline_handler(int irq, void *priv); +int stf_vin_subdev_init(struct stfcamss *stfcamss); +int stf_vin_register(struct stf_vin_dev *vin_dev, struct v4l2_device *v4l2= _dev); +int stf_vin_unregister(struct stf_vin_dev *vin_dev); +enum isp_pad_id stf_vin_map_isp_pad(enum vin_line_id line, enum isp_pad_id= def); + +#endif /* STF_VIN_H */ diff --git a/drivers/media/platform/starfive/stf_vin_hw_ops.c b/drivers/med= ia/platform/starfive/stf_vin_hw_ops.c new file mode 100644 index 000000000000..a4adb5e4ea60 --- /dev/null +++ b/drivers/media/platform/starfive/stf_vin_hw_ops.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * stf_vin_hw_ops.c + * + * Register interface file for StarFive VIN module driver + * + * Copyright (C) 2021-2023 StarFive Technology Co., Ltd. + */ +#include "stf_camss.h" + +static void vin_intr_clear(struct stfcamss *stfcamss) +{ + stf_syscon_reg_set_bit(stfcamss, SYSCONSAIF_SYSCFG(28), + U0_VIN_INTR_CLEAN); + stf_syscon_reg_clear_bit(stfcamss, SYSCONSAIF_SYSCFG(28), + U0_VIN_INTR_CLEAN); +} + +irqreturn_t stf_vin_wr_irq_handler(int irq, void *priv) +{ + struct stf_vin_dev *vin_dev =3D priv; + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + struct dummy_buffer *dummy_buffer =3D + &vin_dev->dummy_buffer[STF_DUMMY_VIN]; + + if (atomic_dec_if_positive(&dummy_buffer->frame_skip) < 0) { + vin_dev->isr_ops->isr_change_buffer(&vin_dev->line[VIN_LINE_WR]); + vin_dev->isr_ops->isr_buffer_done(&vin_dev->line[VIN_LINE_WR]); + } + + vin_intr_clear(stfcamss); + + return IRQ_HANDLED; +} + +irqreturn_t stf_vin_isp_irq_handler(int irq, void *priv) +{ + struct stf_vin_dev *vin_dev =3D priv; + u32 int_status; + + int_status =3D stf_isp_reg_read(vin_dev->stfcamss, ISP_REG_ISP_CTRL_0); + + if (int_status & ISPC_INTS) { + if ((int_status & ISPC_ENUO)) + vin_dev->isr_ops->isr_buffer_done( + &vin_dev->line[VIN_LINE_ISP]); + + /* clear interrupt */ + stf_isp_reg_write(vin_dev->stfcamss, + ISP_REG_ISP_CTRL_0, + (int_status & ~EN_INT_ALL) | + EN_INT_ISP_DONE | + EN_INT_CSI_DONE | + EN_INT_SC_DONE); + } + + return IRQ_HANDLED; +} + +irqreturn_t stf_vin_isp_irq_csiline_handler(int irq, void *priv) +{ + struct stf_vin_dev *vin_dev =3D priv; + struct stf_isp_dev *isp_dev; + u32 int_status; + + isp_dev =3D &vin_dev->stfcamss->isp_dev; + + int_status =3D stf_isp_reg_read(vin_dev->stfcamss, ISP_REG_ISP_CTRL_0); + if (int_status & ISPC_SCFEINT) { + struct dummy_buffer *dummy_buffer =3D + &vin_dev->dummy_buffer[STF_DUMMY_ISP]; + + if (atomic_dec_if_positive(&dummy_buffer->frame_skip) < 0) { + if ((int_status & ISPC_ENUO)) + vin_dev->isr_ops->isr_change_buffer( + &vin_dev->line[VIN_LINE_ISP]); + } + + stf_isp_reg_set_bit(isp_dev->stfcamss, ISP_REG_CSIINTS, + CSI_INTS_MASK, CSI_INTS(0x3)); + stf_isp_reg_set_bit(isp_dev->stfcamss, ISP_REG_IESHD, + SHAD_UP_M | SHAD_UP_EN, 0x3); + + /* clear interrupt */ + stf_isp_reg_write(vin_dev->stfcamss, ISP_REG_ISP_CTRL_0, + (int_status & ~EN_INT_ALL) | EN_INT_LINE_INT); + } + + return IRQ_HANDLED; +} + +int stf_vin_clk_enable(struct stf_vin_dev *vin_dev, enum link link) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + + clk_set_rate(stfcamss->sys_clk[STF_CLK_APB_FUNC].clk, 49500000); + + switch (link) { + case LINK_CSI_TO_WR: + clk_set_rate(stfcamss->sys_clk[STF_CLK_MIPI_RX0_PXL].clk, + 198000000); + reset_control_deassert(stfcamss->sys_rst[STF_RST_AXIWR].rstc); + clk_set_parent(stfcamss->sys_clk[STF_CLK_AXIWR].clk, + stfcamss->sys_clk[STF_CLK_MIPI_RX0_PXL].clk); + break; + case LINK_CSI_TO_ISP: + clk_set_rate(stfcamss->sys_clk[STF_CLK_MIPI_RX0_PXL].clk, + 198000000); + clk_set_parent(stfcamss->sys_clk[STF_CLK_WRAPPER_CLK_C].clk, + stfcamss->sys_clk[STF_CLK_MIPI_RX0_PXL].clk); + break; + case LINK_DVP_TO_WR: + case LINK_DVP_TO_ISP: + default: + return -EINVAL; + } + + return 0; +} + +int stf_vin_clk_disable(struct stf_vin_dev *vin_dev, enum link link) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + + switch (link) { + case LINK_CSI_TO_WR: + reset_control_assert(stfcamss->sys_rst[STF_RST_AXIWR].rstc); + break; + case LINK_CSI_TO_ISP: + break; + case LINK_DVP_TO_WR: + case LINK_DVP_TO_ISP: + default: + return -EINVAL; + } + + return 0; +} + +int stf_vin_wr_stream_set(struct stf_vin_dev *vin_dev) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + + /* make the axiwr alway on */ + stf_syscon_reg_set_bit(stfcamss, SYSCONSAIF_SYSCFG(20), + U0_VIN_AXIWR0_EN); + + return 0; +} + +int stf_vin_stream_set(struct stf_vin_dev *vin_dev, enum link link) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + u32 val; + + switch (link) { + case LINK_CSI_TO_WR: + val =3D stf_syscon_reg_read(stfcamss, SYSCONSAIF_SYSCFG(20)); + val &=3D ~U0_VIN_CHANNEL_SEL_MASK; + val |=3D CHANNEL(0); + stf_syscon_reg_write(stfcamss, SYSCONSAIF_SYSCFG(20), val); + + val =3D stf_syscon_reg_read(stfcamss, SYSCONSAIF_SYSCFG(28)); + val &=3D ~U0_VIN_PIX_CT_MASK; + val |=3D PIX_CT(1); + + val &=3D ~U0_VIN_PIXEL_HEIGH_BIT_SEL_MAKS; + val |=3D PIXEL_HEIGH_BIT_SEL(0); + + val &=3D ~U0_VIN_PIX_CNT_END_MASK; + val |=3D PIX_CNT_END(IMAGE_MAX_WIDTH / 4 - 1); + + stf_syscon_reg_write(stfcamss, SYSCONSAIF_SYSCFG(28), val); + break; + case LINK_CSI_TO_ISP: + val =3D stf_syscon_reg_read(stfcamss, SYSCONSAIF_SYSCFG(36)); + val &=3D ~U0_VIN_MIPI_BYTE_EN_ISP0_MASK; + val |=3D U0_VIN_MIPI_BYTE_EN_ISP0(0); + + val &=3D ~U0_VIN_MIPI_CHANNEL_SEL0_MASK; + val |=3D U0_VIN_MIPI_CHANNEL_SEL0(0); + + val &=3D ~U0_VIN_PIX_NUM_MASK; + val |=3D U0_VIN_PIX_NUM(0); + + val &=3D ~U0_VIN_P_I_MIPI_HAEDER_EN0_MASK; + val |=3D U0_VIN_P_I_MIPI_HAEDER_EN0(1); + + stf_syscon_reg_write(stfcamss, SYSCONSAIF_SYSCFG(36), val); + break; + case LINK_DVP_TO_WR: + case LINK_DVP_TO_ISP: + default: + return -EINVAL; + } + + return 0; +} + +void stf_vin_wr_irq_enable(struct stf_vin_dev *vin_dev, int enable) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + + if (enable) { + stf_syscon_reg_clear_bit(stfcamss, SYSCONSAIF_SYSCFG(28), + U0_VIN_INTR_M); + } else { + /* clear vin interrupt */ + stf_syscon_reg_set_bit(stfcamss, SYSCONSAIF_SYSCFG(28), + U0_VIN_INTR_CLEAN); + stf_syscon_reg_clear_bit(stfcamss, SYSCONSAIF_SYSCFG(28), + U0_VIN_INTR_CLEAN); + stf_syscon_reg_set_bit(stfcamss, SYSCONSAIF_SYSCFG(28), + U0_VIN_INTR_M); + } +} + +void stf_vin_wr_set_ping_addr(struct stf_vin_dev *vin_dev, dma_addr_t addr) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + + /* set the start address */ + stf_syscon_reg_write(stfcamss, SYSCONSAIF_SYSCFG(32), (long)addr); +} + +void stf_vin_wr_set_pong_addr(struct stf_vin_dev *vin_dev, dma_addr_t addr) +{ + struct stfcamss *stfcamss =3D vin_dev->stfcamss; + + /* set the start address */ + stf_syscon_reg_write(stfcamss, SYSCONSAIF_SYSCFG(24), (long)addr); +} + +void stf_vin_isp_set_yuv_addr(struct stf_vin_dev *vin_dev, + dma_addr_t y_addr, dma_addr_t uv_addr) +{ + stf_isp_reg_write(vin_dev->stfcamss, + ISP_REG_Y_PLANE_START_ADDR, y_addr); + stf_isp_reg_write(vin_dev->stfcamss, + ISP_REG_UV_PLANE_START_ADDR, uv_addr); +} --=20 2.34.1