From patchwork Thu May 27 07:54:05 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Kepplinger X-Patchwork-Id: 12283649 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-14.1 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 13F4DC47089 for ; Thu, 27 May 2021 07:57:48 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id D21406143C for ; Thu, 27 May 2021 07:57:47 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org D21406143C Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=puri.sm Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:List-Subscribe:List-Help: List-Post:List-Archive:List-Unsubscribe:List-Id:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=ohzTPIM3yGI0u3U1Khzlk3IHv658T9LgP7ZPx0YnmS0=; b=QT6HhaeLeaaSpy +vIwCh6yhKKk0cKvlenrNm0MFlJZeIrk2LXfQnQw77GS+ndqt8g2vzVg2ymmkUFMzLgzlKpMN7EYD gwuIc/tjdmg6DDdwqzemr3QUyKbzSvHXj7dM1N4i5TUs5spYnv5078meyxI8YcxCmWI4LKlIym98b 7ooZ1NygRn4RSuJ+COs9ldnZJZuh5bhnIiqW9+qNGq4NeX6IACx/Q2xpYQ5ZCUk28BKugTB1bT/GO tUrT2xfZGMexNmWbfAFn7uJHkQUgFMxI7By6xVtkE+66+gyaFzu0cXgRbzNTYZPizo3XOWo9Mj/60 tjk9AoyXgm0VJ6i+CX0g==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.94.2 #2 (Red Hat Linux)) id 1lmArq-003YP9-D2; Thu, 27 May 2021 07:55:46 +0000 Received: from comms.puri.sm ([159.203.221.185]) by bombadil.infradead.org with esmtps (Exim 4.94.2 #2 (Red Hat Linux)) id 1lmArM-003YEE-JX for linux-arm-kernel@lists.infradead.org; Thu, 27 May 2021 07:55:18 +0000 Received: from localhost (localhost [127.0.0.1]) by comms.puri.sm (Postfix) with ESMTP id 56AE0E204E; Thu, 27 May 2021 00:54:46 -0700 (PDT) Received: from comms.puri.sm ([127.0.0.1]) by localhost (comms.puri.sm [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id AvVzDm61ahfv; Thu, 27 May 2021 00:54:45 -0700 (PDT) From: Martin Kepplinger To: laurent.pinchart@ideasonboard.com, mchehab@kernel.org, robh@kernel.org Cc: shawnguo@kernel.org, kernel@pengutronix.de, festevam@gmail.com, linux-imx@nxp.com, slongerbeam@gmail.com, kernel@puri.sm, krzk@kernel.org, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-staging@lists.linux.dev, Martin Kepplinger Subject: [PATCH v1 1/3] dt-bindings: media: document the nxp, imx8mq-mipi-csi2 receiver phy and controller Date: Thu, 27 May 2021 09:54:05 +0200 Message-Id: <20210527075407.3180744-2-martin.kepplinger@puri.sm> In-Reply-To: <20210527075407.3180744-1-martin.kepplinger@puri.sm> References: <20210527075407.3180744-1-martin.kepplinger@puri.sm> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210527_005516_672374_B870EF18 X-CRM114-Status: GOOD ( 11.40 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org The i.MX8MQ SoC integrates a different MIPI CSI receiver as the i.MX8MM so describe the DT bindings for it. Signed-off-by: Martin Kepplinger --- .../bindings/media/nxp,imx8mq-mipi-csi2.yaml | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml diff --git a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml new file mode 100644 index 000000000000..823609ac4cf5 --- /dev/null +++ b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/nxp,imx8mq-mipi-csi2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: NXP and i.MX8MQ MIPI CSI-2 receiver + +maintainers: + - Martin Kepplinger + +description: |- + This binding covers the CSI-2 RX PHY and host controller included in the + NXP i.MX8MQ SoC. It handles the sensor/image input and process for all the + input imaging devices. + +properties: + compatible: + enum: + - fsl,imx8mq-mipi-csi2 + + reg: + maxItems: 1 + + clocks: + minItems: 4 + + clock-names: + minItems: 4 + items: + - const: core + - const: esc + - const: pxl + - const: clko2 + + assigned-clocks: + maxItems: 3 + + assigned-clock-rates: + maxItems: 3 + + assigned-clock-parents: + maxItems: 3 + + power-domains: + maxItems: 1 + + phy-reset: + description: + The phandle to the imx8mq reset-controller. + maxItems: 1 + + phy-gpr: + description: + The phandle to the imx8mq syscon iomux-gpr. + maxItems: 1 + + interconnects: + maxItems: 1 + + interconnect-names: + const: dram + + ports: + $ref: /schemas/graph.yaml#/properties/ports + + properties: + port@0: + $ref: /schemas/graph.yaml#/$defs/port-base + unevaluatedProperties: false + description: + Input port node, single endpoint describing the CSI-2 transmitter. + + properties: + endpoint: + $ref: video-interfaces.yaml# + unevaluatedProperties: false + + properties: + data-lanes: + items: + minItems: 1 + maxItems: 4 + items: + - const: 1 + - const: 2 + - const: 3 + - const: 4 + + required: + - data-lanes + + port@1: + $ref: /schemas/graph.yaml#/properties/port + description: + Output port node + +required: + - compatible + - reg + - clocks + - clock-names + - assigned-clocks + - assigned-clock-rates + - assigned-clock-parents + - power-domains + - phy-reset + - phy-gpr + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + mipi_csi1@30a70000 { + compatible = "fsl,imx8mq-mipi-csi2"; + reg = <0x30a70000 0x1000>; /* MIPI CSI1 Controller base addr */ + clocks = <&clk IMX8MQ_CLK_CSI1_CORE>, + <&clk IMX8MQ_CLK_CSI1_ESC>, + <&clk IMX8MQ_CLK_CSI1_PHY_REF>, + <&clk IMX8MQ_CLK_CLKO2>; + clock-names = "core", "esc", "pxl", "clko2"; + assigned-clocks = <&clk IMX8MQ_CLK_CSI1_CORE>, + <&clk IMX8MQ_CLK_CSI1_PHY_REF>, + <&clk IMX8MQ_CLK_CSI1_ESC>; + assigned-clock-rates = <266000000>, <200000000>, <66000000>; + assigned-clock-parents = <&clk IMX8MQ_SYS1_PLL_266M>, + <&clk IMX8MQ_SYS2_PLL_1000M>, + <&clk IMX8MQ_SYS1_PLL_800M>; + power-domains = <&pgc_mipi_csi1>; + phy-reset = <&src>; + phy-gpr = <&iomuxc_gpr>; + interconnects = <&noc IMX8MQ_ICM_CSI1 &noc IMX8MQ_ICS_DRAM>; + interconnect-names = "dram"; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + imx8mm_mipi_csi_in: endpoint { + remote-endpoint = <&imx477_out>; + data-lanes = <1 2 3 4>; + }; + }; + + port@1 { + reg = <1>; + + imx8mm_mipi_csi_out: endpoint { + remote-endpoint = <&csi_in>; + }; + }; + }; + }; + +... From patchwork Thu May 27 07:54:06 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Kepplinger X-Patchwork-Id: 12283651 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-11.3 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS, UNWANTED_LANGUAGE_BODY,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 87999C47089 for ; Thu, 27 May 2021 07:58:26 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 4AB61613F4 for ; Thu, 27 May 2021 07:58:26 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 4AB61613F4 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=puri.sm Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:List-Subscribe:List-Help: List-Post:List-Archive:List-Unsubscribe:List-Id:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=W+zikT6BfWM95NLeSapv3qswse1FH4N387iKRhAv4rI=; b=P80hJT9efhe5jz hNWa7JEDiZkuLfcMN3+ItR1uieyFrmdAcG/+GKsltkJhukjim5MWlUw7yP3QLrhYXWwDJWULLcT5I AXfPAsV1PT1VDLQYGNONrZGoEl4YTnjXtVWOV2fOCaMMYuGBLN+ADnqSTLv+eIwuB0a8fiG2VEg47 5OOUr418O9+s1I5WGK8a3vLXqfEhLIYjFxOwXnhlcM2ebTLrSOfG9zC3D/AFq6XrcZwX72poAc8so NhSDT6dBk4tdAYmDxgBmr+K39XauXQ3S1ykXTijCAIg570II+EmEwBXvZ8fVYC/9khFL5p+41uisg DQ64sZfUaX4Fna05kWqg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.94.2 #2 (Red Hat Linux)) id 1lmAsN-003Yfx-Vn; Thu, 27 May 2021 07:56:20 +0000 Received: from comms.puri.sm ([159.203.221.185]) by bombadil.infradead.org with esmtps (Exim 4.94.2 #2 (Red Hat Linux)) id 1lmArS-003YGd-1J for linux-arm-kernel@lists.infradead.org; Thu, 27 May 2021 07:55:25 +0000 Received: from localhost (localhost [127.0.0.1]) by comms.puri.sm (Postfix) with ESMTP id CCF0DE2060; Thu, 27 May 2021 00:54:51 -0700 (PDT) Received: from comms.puri.sm ([127.0.0.1]) by localhost (comms.puri.sm [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 74fKleT2MbEx; Thu, 27 May 2021 00:54:49 -0700 (PDT) From: Martin Kepplinger To: laurent.pinchart@ideasonboard.com, mchehab@kernel.org, robh@kernel.org Cc: shawnguo@kernel.org, kernel@pengutronix.de, festevam@gmail.com, linux-imx@nxp.com, slongerbeam@gmail.com, kernel@puri.sm, krzk@kernel.org, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-staging@lists.linux.dev, Martin Kepplinger Subject: [PATCH v1 2/3] media: imx: add a driver for i.MX8MQ mipi csi rx phy and controller Date: Thu, 27 May 2021 09:54:06 +0200 Message-Id: <20210527075407.3180744-3-martin.kepplinger@puri.sm> In-Reply-To: <20210527075407.3180744-1-martin.kepplinger@puri.sm> References: <20210527075407.3180744-1-martin.kepplinger@puri.sm> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210527_005522_143599_94332D7C X-CRM114-Status: GOOD ( 26.77 ) X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org The i.MX8MQ SoC includes a different MIPI CSI receiver than i.MX8MM so add a driver to support it. Signed-off-by: Martin Kepplinger --- drivers/staging/media/imx/Makefile | 1 + drivers/staging/media/imx/imx8mq-mipi-csi2.c | 1023 ++++++++++++++++++ 2 files changed, 1024 insertions(+) create mode 100644 drivers/staging/media/imx/imx8mq-mipi-csi2.c diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile index 6ac33275cc97..19c2fc54d424 100644 --- a/drivers/staging/media/imx/Makefile +++ b/drivers/staging/media/imx/Makefile @@ -16,3 +16,4 @@ obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o +obj-$(CONFIG_VIDEO_IMX7_CSI) += imx8mq-mipi-csi2.o diff --git a/drivers/staging/media/imx/imx8mq-mipi-csi2.c b/drivers/staging/media/imx/imx8mq-mipi-csi2.c new file mode 100644 index 000000000000..67b99be3e804 --- /dev/null +++ b/drivers/staging/media/imx/imx8mq-mipi-csi2.c @@ -0,0 +1,1023 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Freescale i.MX8MQ SoC series MIPI-CSI receiver driver + * + * Copyright (C) 2021 Purism SPC + * Copyright (C) 2019 Linaro Ltd + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define MIPI_CSI2_DRIVER_NAME "imx8mq-mipi-csi2" +#define MIPI_CSI2_SUBDEV_NAME MIPI_CSI2_DRIVER_NAME + +#define MIPI_CSI2_PAD_SINK 0 +#define MIPI_CSI2_PAD_SOURCE 1 +#define MIPI_CSI2_PADS_NUM 2 + +#define MIPI_CSI2_DEF_PIX_WIDTH 640 +#define MIPI_CSI2_DEF_PIX_HEIGHT 480 + +#define MIPI_CSI2_RX_CSR_1_BASE_ADDR 0x30A70000 +#define MIPI_CSI2_RX_CSR_1_GPR_REG 0x88 +#define MIPI_CSI2_RX_CSR_1_HW_RESET_REG 0x4C +#define MIPI_CSI2_RX_CSR_1_HW_RESET_VAL 7 +#define MIPI_CSI2_RX_CSR_2_BASE_ADDR 0x30B60000 +#define MIPI_CSI2_RX_CSR_2_GPR_REG 0xA4 +#define MIPI_CSI2_RX_CSR_2_HW_RESET_REG 0x50 +#define MIPI_CSI2_RX_CSR_2_HW_RESET_VAL 7 + +/* Register map definition */ + +/* i.MX8MQ CSI-2 controller CSR */ +#define CSI2RX_CFG_NUM_LANES 0x100 +#define CSI2RX_CFG_DISABLE_DATA_LANES 0x104 +#define CSI2RX_BIT_ERR 0x108 +#define CSI2RX_IRQ_STATUS 0x10C +#define CSI2RX_IRQ_MASK 0x110 +#define CSI2RX_ULPS_STATUS 0x114 +#define CSI2RX_PPI_ERRSOT_HS 0x118 +#define CSI2RX_PPI_ERRSOTSYNC_HS 0x11C +#define CSI2RX_PPI_ERRESC 0x120 +#define CSI2RX_PPI_ERRSYNCESC 0x124 +#define CSI2RX_PPI_ERRCONTROL 0x128 +#define CSI2RX_CFG_DISABLE_PAYLOAD_0 0x12C +#define CSI2RX_CFG_DISABLE_PAYLOAD_1 0x130 + +enum { + ST_POWERED = 1, + ST_STREAMING = 2, + ST_SUSPENDED = 4, +}; + +static const char * const mipi_csi2_clk_id[] = { + "core", + "esc", + "pxl", + "clko2", +}; + +#define GPR_CSI2_1_RX_ENABLE BIT(13) +#define GPR_CSI2_1_VID_INTFC_ENB BIT(12) +#define GPR_CSI2_1_HSEL BIT(10) +#define GPR_CSI2_1_CONT_CLK_MODE BIT(8) +#define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3F) << 2) + +struct csi_state { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data *clks; + struct reset_control *mrst; + struct regulator *mipi_phy_regulator; + u8 index; + + struct v4l2_subdev sd; + struct media_pad pads[MIPI_CSI2_PADS_NUM]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_sd; + + struct v4l2_fwnode_bus_mipi_csi2 bus; + u32 hs_settle; + u32 clk_settle; + + struct mutex lock; /* Protect csi2_fmt, format_mbus and state */ + const struct csi2_pix_format *csi2_fmt; + struct v4l2_mbus_framefmt format_mbus; + u32 state; + + struct regmap *hw_reset; + u8 hw_reset_reg; + + struct regmap *phy_gpr; + u8 phy_gpr_reg; + + u32 send_level; + + struct icc_path *icc_path; + s32 icc_path_bw; +}; + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct csi2_pix_format { + u32 code; + u8 width; +}; + +static const struct csi2_pix_format mipi_csi2_formats[] = { + /* RAW (Bayer and greyscale) formats. */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_Y12_1X12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .width = 14, + } +}; + +static const struct csi2_pix_format *find_csi2_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mipi_csi2_formats); i++) + if (code == mipi_csi2_formats[i].code) + return &mipi_csi2_formats[i]; + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static inline u32 mipi_csi2_read(struct csi_state *state, u32 reg) +{ + return readl(state->regs + reg); +} + +static inline void mipi_csi2_write(struct csi_state *state, u32 reg, u32 val) +{ + writel(val, state->regs + reg); +} + +static void mipi_csi2_sw_reset(struct csi_state *state) +{ + struct device *dev = state->dev; + struct device_node *np = dev->of_node; + struct device_node *node; + phandle phandle; + int ret; + + dev_dbg(dev, "%s: starting\n", __func__); + + ret = of_property_read_u32(np, "phy-reset", &phandle); + if (ret) { + dev_info(dev, "no csis-hw-reset property found: %d\n", ret); + return; + } + + node = of_find_node_by_phandle(phandle); + if (!node) { + ret = PTR_ERR(node); + dev_dbg(dev, "not find src node by phandle: %d\n", ret); + } + state->hw_reset = syscon_node_to_regmap(node); + if (IS_ERR(state->hw_reset)) { + ret = PTR_ERR(state->hw_reset); + dev_err(dev, "failed to get src regmap: %d\n", ret); + } + of_node_put(node); + if (ret < 0) + return; + + /* reset imx8mq mipi phy */ + regmap_update_bits(state->hw_reset, state->hw_reset_reg, 7, 7); + msleep(20); +} + +static void mipi_csi2_system_enable(struct csi_state *state, int on) +{ + struct device *dev = state->dev; + struct device_node *np = dev->of_node; + struct device_node *node; + phandle phandle; + int ret; + + if (!on) { + /* Disable Data lanes */ + mipi_csi2_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf); + return; + } + + ret = of_property_read_u32(np, "phy-gpr", &phandle); + if (ret) { + dev_info(dev, "no phy-gpr property found\n"); + return; + } + + node = of_find_node_by_phandle(phandle); + if (!node) { + dev_dbg(dev, "not find gpr node by phandle\n"); + ret = PTR_ERR(node); + } + state->phy_gpr = syscon_node_to_regmap(node); + if (IS_ERR(state->phy_gpr)) { + dev_err(dev, "failed to get gpr regmap\n"); + ret = PTR_ERR(state->phy_gpr); + } + of_node_put(node); + if (ret < 0) + return; + + regmap_update_bits(state->phy_gpr, + state->phy_gpr_reg, + 0x3FFF, + GPR_CSI2_1_RX_ENABLE | + GPR_CSI2_1_VID_INTFC_ENB | + GPR_CSI2_1_HSEL | + GPR_CSI2_1_CONT_CLK_MODE | + GPR_CSI2_1_S_PRG_RXHS_SETTLE(state->hs_settle)); + + dev_dbg(dev, "%s: hs_settle: 0x%X\n", __func__, state->hs_settle); +} + +static void mipi_csi2_set_params(struct csi_state *state) +{ + int lanes = state->bus.num_data_lanes; + u32 val = 0; + int i; + + /* Lanes */ + mipi_csi2_write(state, CSI2RX_CFG_NUM_LANES, lanes - 1); + + for (i = 0; i < lanes; i++) + val |= (1 << i); + + val = 0xF & ~val; + mipi_csi2_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, val); + + dev_dbg(state->dev, "imx8mq: CSI2RX_CFG_DISABLE_DATA_LANES: 0x%X\n", val); + + /* Mask interrupt */ + // Don't let ULPS (ultra-low power status) interrupts flood + mipi_csi2_write(state, CSI2RX_IRQ_MASK, 0x1ff); + + mipi_csi2_write(state, 0x180, 1); + /* vid_vc */ + mipi_csi2_write(state, 0x184, 1); + mipi_csi2_write(state, 0x188, state->send_level); +} + +static int mipi_csi2_clk_enable(struct csi_state *state) +{ + return clk_bulk_prepare_enable(ARRAY_SIZE(mipi_csi2_clk_id), state->clks); +} + +static void mipi_csi2_clk_disable(struct csi_state *state) +{ + clk_bulk_disable_unprepare(ARRAY_SIZE(mipi_csi2_clk_id), state->clks); +} + +static int mipi_csi2_clk_get(struct csi_state *state) +{ + unsigned int i; + int ret; + + state->clks = devm_kcalloc(state->dev, ARRAY_SIZE(mipi_csi2_clk_id), + sizeof(*state->clks), GFP_KERNEL); + + if (!state->clks) + return -ENOMEM; + + for (i = 0; i < ARRAY_SIZE(mipi_csi2_clk_id); i++) + state->clks[i].id = mipi_csi2_clk_id[i]; + + ret = devm_clk_bulk_get(state->dev, ARRAY_SIZE(mipi_csi2_clk_id), + state->clks); + return ret; +} + +static void mipi_csi2_start_stream(struct csi_state *state) +{ + mipi_csi2_sw_reset(state); + mipi_csi2_set_params(state); + mipi_csi2_system_enable(state, true); +} + +static void mipi_csi2_stop_stream(struct csi_state *state) +{ + mipi_csi2_system_enable(state, false); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct csi_state *mipi_sd_to_csi2_state(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csi_state, sd); +} + +static int mipi_csi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + int ret; + + mipi_csi2_write(state, CSI2RX_IRQ_MASK, 0x008); + + dev_dbg(state->dev, "%s: enable: %d\n", __func__, enable); + + if (enable) { + ret = pm_runtime_get_sync(state->dev); + if (ret < 0) { + pm_runtime_put_noidle(state->dev); + return ret; + } + ret = v4l2_subdev_call(state->src_sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto done; + } + + mutex_lock(&state->lock); + + if (enable) { + if (state->state & ST_SUSPENDED) { + ret = -EBUSY; + goto unlock; + } + + mipi_csi2_start_stream(state); + ret = v4l2_subdev_call(state->src_sd, video, s_stream, 1); + if (ret < 0) + goto unlock; + + state->state |= ST_STREAMING; + } else { + v4l2_subdev_call(state->src_sd, video, s_stream, 0); + ret = v4l2_subdev_call(state->src_sd, core, s_power, 0); + if (ret == -ENOIOCTLCMD) + ret = 0; + mipi_csi2_stop_stream(state); + state->state &= ~ST_STREAMING; + } + +unlock: + mutex_unlock(&state->lock); + +done: + if (!enable || ret < 0) + pm_runtime_put(state->dev); + + return ret; +} + +static struct v4l2_mbus_framefmt * +mipi_csi2_get_format(struct csi_state *state, + struct v4l2_subdev_pad_config *cfg, + enum v4l2_subdev_format_whence which, + unsigned int pad) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&state->sd, cfg, pad); + + return &state->format_mbus; +} + +static int mipi_csi2_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + struct v4l2_mbus_framefmt *fmt_sink; + struct v4l2_mbus_framefmt *fmt_source; + enum v4l2_subdev_format_whence which; + + which = cfg ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt_sink = mipi_csi2_get_format(state, cfg, which, MIPI_CSI2_PAD_SINK); + + fmt_sink->code = MEDIA_BUS_FMT_SGBRG10_1X10; + fmt_sink->width = MIPI_CSI2_DEF_PIX_WIDTH; + fmt_sink->height = MIPI_CSI2_DEF_PIX_HEIGHT; + fmt_sink->field = V4L2_FIELD_NONE; + + fmt_sink->colorspace = V4L2_COLORSPACE_RAW; + fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); + fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); + fmt_sink->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, + fmt_sink->ycbcr_enc); + + /* + * When called from mipi_csi2_subdev_init() to initialize the active + * configuration, cfg is NULL, which indicates there's no source pad + * configuration to set. + */ + if (!cfg) + return 0; + + fmt_source = mipi_csi2_get_format(state, cfg, which, MIPI_CSI2_PAD_SOURCE); + *fmt_source = *fmt_sink; + + return 0; +} + +static int mipi_csi2_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + struct v4l2_mbus_framefmt *fmt; + + fmt = mipi_csi2_get_format(state, cfg, sdformat->which, sdformat->pad); + + mutex_lock(&state->lock); + sdformat->format = *fmt; + mutex_unlock(&state->lock); + + return 0; +} + +static int mipi_csi2_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + /* + * We can't transcode in any way, the source format is identical + * to the sink format. + */ + if (code->pad == MIPI_CSI2_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = mipi_csi2_get_format(state, cfg, code->which, code->pad); + code->code = fmt->code; + return 0; + } + + if (code->pad != MIPI_CSI2_PAD_SINK) + return -EINVAL; + + if (code->index >= ARRAY_SIZE(mipi_csi2_formats)) + return -EINVAL; + + code->code = mipi_csi2_formats[code->index].code; + + return 0; +} + +static int mipi_csi2_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_pad_config *cfg, + struct v4l2_subdev_format *sdformat) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + struct csi2_pix_format const *csi2_fmt; + struct v4l2_mbus_framefmt *fmt; + + /* + * The device can't transcode in any way, the source format can't be + * modified. + */ + if (sdformat->pad == MIPI_CSI2_PAD_SOURCE) + return mipi_csi2_get_fmt(sd, cfg, sdformat); + + if (sdformat->pad != MIPI_CSI2_PAD_SINK) + return -EINVAL; + + csi2_fmt = find_csi2_format(sdformat->format.code); + if (!csi2_fmt) { + dev_err(state->dev, "no format found based on code %d\n", + sdformat->format.code); + csi2_fmt = &mipi_csi2_formats[0]; + } + + fmt = mipi_csi2_get_format(state, cfg, sdformat->which, sdformat->pad); + + mutex_lock(&state->lock); + + fmt->code = csi2_fmt->code; + fmt->width = sdformat->format.width; + fmt->height = sdformat->format.height; + + sdformat->format = *fmt; + + /* Propagate the format from sink to source. */ + fmt = mipi_csi2_get_format(state, cfg, sdformat->which, + MIPI_CSI2_PAD_SOURCE); + *fmt = sdformat->format; + + /* Store the CSI2 format descriptor for active formats. */ + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + state->csi2_fmt = csi2_fmt; + + mutex_unlock(&state->lock); + + /* https://community.nxp.com/t5/i-MX-Processors/Explenation-for-HS-SETTLE-parameter-in-MIPI-CSI-D-PHY-registers/m-p/764275/highlight/true#M118744 */ + if (sdformat->format.width * sdformat->format.height > 720 * 480) + state->hs_settle = 0x9; + else + state->hs_settle = 0x14; + + state->send_level = 64; + + dev_dbg(state->dev, + "%s: format %dx%d send_level %d hs_settle 0x%X\n", __func__, + sdformat->format.width, sdformat->format.height, + state->send_level, state->hs_settle); + + return 0; +} + +static int mipi_csi2_log_status(struct v4l2_subdev *sd) +{ + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + mutex_lock(&state->lock); + mutex_unlock(&state->lock); + + return 0; +} + +static const struct v4l2_subdev_core_ops mipi_csi2_core_ops = { + .log_status = mipi_csi2_log_status, +}; + +static const struct v4l2_subdev_video_ops mipi_csi2_video_ops = { + .s_stream = mipi_csi2_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mipi_csi2_pad_ops = { + .init_cfg = mipi_csi2_init_cfg, + .enum_mbus_code = mipi_csi2_enum_mbus_code, + .get_fmt = mipi_csi2_get_fmt, + .set_fmt = mipi_csi2_set_fmt, +}; + +static const struct v4l2_subdev_ops mipi_csi2_subdev_ops = { + .core = &mipi_csi2_core_ops, + .video = &mipi_csi2_video_ops, + .pad = &mipi_csi2_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static int mipi_csi2_link_setup(struct media_entity *entity, + const struct media_pad *local_pad, + const struct media_pad *remote_pad, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + struct v4l2_subdev *remote_sd; + + dev_dbg(state->dev, "link setup %s -> %s", remote_pad->entity->name, + local_pad->entity->name); + + /* We only care about the link to the source. */ + if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) + return 0; + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (state->src_sd) + return -EBUSY; + + state->src_sd = remote_sd; + } else { + state->src_sd = NULL; + } + + return 0; +} + +static const struct media_entity_operations mipi_csi2_entity_ops = { + .link_setup = mipi_csi2_link_setup, + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Async subdev notifier + */ + +static struct csi_state * +mipi_notifier_to_csi2_state(struct v4l2_async_notifier *n) +{ + return container_of(n, struct csi_state, notifier); +} + +static int mipi_csi2_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct csi_state *state = mipi_notifier_to_csi2_state(notifier); + struct media_pad *sink = &state->sd.entity.pads[MIPI_CSI2_PAD_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, 0); +} + +static const struct v4l2_async_notifier_operations mipi_csi2_notify_ops = { + .bound = mipi_csi2_notify_bound, +}; + +static int mipi_csi2_async_register(struct csi_state *state) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + unsigned int i; + int ret; + + v4l2_async_notifier_init(&state->notifier); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(state->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { + dev_err(state->dev, + "data lanes reordering is not supported"); + goto err_parse; + } + } + + state->bus = vep.bus.mipi_csi2; + + dev_dbg(state->dev, "data lanes: %d\n", state->bus.num_data_lanes); + dev_dbg(state->dev, "flags: 0x%08x\n", state->bus.flags); + + asd = v4l2_async_notifier_add_fwnode_remote_subdev(&state->notifier, + ep, struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto err_parse; + } + + fwnode_handle_put(ep); + + state->notifier.ops = &mipi_csi2_notify_ops; + + ret = v4l2_async_subdev_notifier_register(&state->sd, &state->notifier); + if (ret) + return ret; + + return v4l2_async_register_subdev(&state->sd); + +err_parse: + fwnode_handle_put(ep); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Suspend/resume + */ + +static int mipi_csi2_pm_suspend(struct device *dev, bool runtime) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + int ret = 0; + + mutex_lock(&state->lock); + if (state->state & ST_POWERED) { + mipi_csi2_stop_stream(state); + mipi_csi2_clk_disable(state); + state->state &= ~ST_POWERED; + if (!runtime) + state->state |= ST_SUSPENDED; + } + + mutex_unlock(&state->lock); + + ret = icc_set_bw(state->icc_path, 0, 0); + if (ret) + dev_err(dev, "icc_set_bw failed with %d\n", ret); + + return ret ? -EAGAIN : 0; +} + +static int mipi_csi2_pm_resume(struct device *dev, bool runtime) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + int ret = 0; + + ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw); + if (ret) { + dev_err(dev, "icc_set_bw failed with %d\n", ret); + return ret; + } + + mutex_lock(&state->lock); + if (!runtime && !(state->state & ST_SUSPENDED)) + goto unlock; + + if (!(state->state & ST_POWERED)) { + state->state |= ST_POWERED; + ret = mipi_csi2_clk_enable(state); + } + if (state->state & ST_STREAMING) + mipi_csi2_start_stream(state); + + state->state &= ~ST_SUSPENDED; + +unlock: + mutex_unlock(&state->lock); + + return ret ? -EAGAIN : 0; +} + +static int __maybe_unused mipi_csi2_suspend(struct device *dev) +{ + return mipi_csi2_pm_suspend(dev, false); +} + +static int __maybe_unused mipi_csi2_resume(struct device *dev) +{ + return mipi_csi2_pm_resume(dev, false); +} + +static int __maybe_unused mipi_csi2_runtime_suspend(struct device *dev) +{ + return mipi_csi2_pm_suspend(dev, true); +} + +static int __maybe_unused mipi_csi2_runtime_resume(struct device *dev) +{ + return mipi_csi2_pm_resume(dev, true); +} + +static const struct dev_pm_ops mipi_csi2_pm_ops = { + SET_RUNTIME_PM_OPS(mipi_csi2_runtime_suspend, mipi_csi2_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(mipi_csi2_suspend, mipi_csi2_resume) +}; + +/* ----------------------------------------------------------------------------- + * Probe/remove & platform driver + */ + +static int mipi_csi2_subdev_init(struct csi_state *state) +{ + struct v4l2_subdev *sd = &state->sd; + + v4l2_subdev_init(sd, &mipi_csi2_subdev_ops); + sd->owner = THIS_MODULE; + snprintf(sd->name, sizeof(sd->name), "%s.%d", + MIPI_CSI2_SUBDEV_NAME, state->index); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->ctrl_handler = NULL; + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &mipi_csi2_entity_ops; + + sd->dev = state->dev; + + state->csi2_fmt = &mipi_csi2_formats[0]; + mipi_csi2_init_cfg(sd, NULL); + + state->pads[MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + state->pads[MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_MUST_CONNECT; + return media_entity_pads_init(&sd->entity, MIPI_CSI2_PADS_NUM, + state->pads); +} + +static int init_icc(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(&pdev->dev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + int ret; + + /* Optional interconnect request */ + state->icc_path = of_icc_get(&pdev->dev, "dram"); + if (IS_ERR(state->icc_path)) { + ret = PTR_ERR(state->icc_path); + if (ret == -EPROBE_DEFER) + return ret; + + state->icc_path = NULL; + return 0; + } + + state->icc_path_bw = MBps_to_icc(700); + + ret = icc_set_bw(state->icc_path, 0, state->icc_path_bw); + if (ret) { + dev_err(&pdev->dev, "icc_set_bw failed with %d\n", ret); + return ret; + } + + return 0; +} + +static int mipi_csi2_parse_dt(struct csi_state *state) +{ + struct device_node *node = state->dev->of_node; + const u32 *addr; + int len; + int reg; + + addr = of_get_property(node, "reg", &len); + if (!addr) { + dev_err(state->dev, "no reg property found\n"); + return -EINVAL; + } + + reg = be32_to_cpup(addr); + + if ((reg >> 16) == MIPI_CSI2_RX_CSR_1_BASE_ADDR >> 16) { + state->phy_gpr_reg = MIPI_CSI2_RX_CSR_1_GPR_REG; + state->hw_reset_reg = MIPI_CSI2_RX_CSR_1_HW_RESET_REG; + } else if ((reg >> 16) == MIPI_CSI2_RX_CSR_2_BASE_ADDR >> 16) { + state->phy_gpr_reg = MIPI_CSI2_RX_CSR_2_GPR_REG; + state->hw_reset_reg = MIPI_CSI2_RX_CSR_2_HW_RESET_REG; + } + + return 0; +} + +static int mipi_csi2_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct csi_state *state; + int ret; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + + state->dev = dev; + + ret = mipi_csi2_parse_dt(state); + if (ret < 0) { + dev_err(dev, "Failed to parse device tree: %d\n", ret); + return ret; + } + + /* Acquire resources. */ + state->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + + ret = mipi_csi2_clk_get(state); + if (ret < 0) + return ret; + + /* Initialize and register the subdev. */ + ret = mipi_csi2_subdev_init(state); + if (ret < 0) + goto disable_clock; + + platform_set_drvdata(pdev, &state->sd); + + ret = mipi_csi2_async_register(state); + if (ret < 0) { + dev_err(dev, "async register failed: %d\n", ret); + goto cleanup; + } + + ret = init_icc(pdev); + if (ret) + goto cleanup; + + /* Enable runtime PM. */ + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = mipi_csi2_pm_resume(dev, true); + if (ret < 0) + goto cleanup; + } + + dev_info(dev, "lanes: %d\n", state->bus.num_data_lanes); + + return 0; + +cleanup: + media_entity_cleanup(&state->sd.entity); + v4l2_async_notifier_unregister(&state->notifier); + v4l2_async_notifier_cleanup(&state->notifier); + v4l2_async_unregister_subdev(&state->sd); +disable_clock: + mipi_csi2_clk_disable(state); + mutex_destroy(&state->lock); + + return ret; +} + +static int mipi_csi2_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csi_state *state = mipi_sd_to_csi2_state(sd); + + v4l2_async_notifier_unregister(&state->notifier); + v4l2_async_notifier_cleanup(&state->notifier); + v4l2_async_unregister_subdev(&state->sd); + + pm_runtime_disable(&pdev->dev); + mipi_csi2_pm_suspend(&pdev->dev, true); + mipi_csi2_clk_disable(state); + media_entity_cleanup(&state->sd.entity); + mutex_destroy(&state->lock); + pm_runtime_set_suspended(&pdev->dev); + icc_put(state->icc_path); + + return 0; +} + +static const struct of_device_id mipi_csi2_of_match[] = { + { .compatible = "fsl,imx8mq-mipi-csi2",}, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mipi_csi2_of_match); + +static struct platform_driver mipi_csi2_driver = { + .probe = mipi_csi2_probe, + .remove = mipi_csi2_remove, + .driver = { + .of_match_table = mipi_csi2_of_match, + .name = MIPI_CSI2_DRIVER_NAME, + .pm = &mipi_csi2_pm_ops, + }, +}; + +module_platform_driver(mipi_csi2_driver); + +MODULE_DESCRIPTION("i.MX8MQ MIPI CSI-2 receiver driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx8mq-mipi-csi2"); From patchwork Thu May 27 07:54:07 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Kepplinger X-Patchwork-Id: 12283645 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-14.1 required=3.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER, INCLUDES_PATCH,MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E8541C4707F for ; Thu, 27 May 2021 07:57:01 +0000 (UTC) Received: from bombadil.infradead.org (bombadil.infradead.org [198.137.202.133]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id AF3D9613E2 for ; Thu, 27 May 2021 07:57:01 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org AF3D9613E2 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=puri.sm Authentication-Results: mail.kernel.org; spf=none smtp.mailfrom=linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=lists.infradead.org; s=bombadil.20210309; h=Sender: Content-Transfer-Encoding:Content-Type:MIME-Version:List-Subscribe:List-Help: List-Post:List-Archive:List-Unsubscribe:List-Id:References:In-Reply-To: Message-Id:Date:Subject:Cc:To:From:Reply-To:Content-ID:Content-Description: Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc:Resent-Message-ID: List-Owner; bh=VrR2c+lAyrZih3Q+8OACL4w1E/EjFE58uXfddXsB+Ak=; b=KzPK/OLFDW9fuC ccaj2f6p7G+y7LQeUQi0XcTmHhMCnUEBqp2ff9v9mzHvR7x7GhD2YOnWCrayNqdluQxcaFbBvH1zK drKDY48Q9uJ8IRGjummJA20gj8WyEncrFKpyAsDD/v9aHCYUGy6m71oeXV9VM4TLt+waEspWVOXu3 ZIz1A3RfDGbaYIxVurryUthZB5L6kLCD74K1CsvaOoO+K0hqMv4lBJFjl2T0APr4f0Eb3ZjirO9D4 Pey3PMNPnaOHMHIhPuDt7JfqstqpK/2bWYw5imS1t4Hmlv/MHrGS+skcfxCC7GVwakYCXyMOUCZ6E 0mLZ85blTlc/JbD9ialg==; Received: from localhost ([::1] helo=bombadil.infradead.org) by bombadil.infradead.org with esmtp (Exim 4.94.2 #2 (Red Hat Linux)) id 1lmArA-003Y6f-CJ; Thu, 27 May 2021 07:55:04 +0000 Received: from comms.puri.sm ([159.203.221.185]) by bombadil.infradead.org with esmtps (Exim 4.94.2 #2 (Red Hat Linux)) id 1lmAr2-003Y3i-Kk for linux-arm-kernel@lists.infradead.org; Thu, 27 May 2021 07:54:58 +0000 Received: from localhost (localhost [127.0.0.1]) by comms.puri.sm (Postfix) with ESMTP id 6417BE2054; Thu, 27 May 2021 00:54:55 -0700 (PDT) Received: from comms.puri.sm ([127.0.0.1]) by localhost (comms.puri.sm [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 4lQP8-udZodX; Thu, 27 May 2021 00:54:54 -0700 (PDT) From: Martin Kepplinger To: laurent.pinchart@ideasonboard.com, mchehab@kernel.org, robh@kernel.org Cc: shawnguo@kernel.org, kernel@pengutronix.de, festevam@gmail.com, linux-imx@nxp.com, slongerbeam@gmail.com, kernel@puri.sm, krzk@kernel.org, linux-media@vger.kernel.org, devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-staging@lists.linux.dev, Martin Kepplinger Subject: [PATCH v1 3/3] arm64: dts: imx8mq: add mipi csi phy and csi bridge descriptions Date: Thu, 27 May 2021 09:54:07 +0200 Message-Id: <20210527075407.3180744-4-martin.kepplinger@puri.sm> In-Reply-To: <20210527075407.3180744-1-martin.kepplinger@puri.sm> References: <20210527075407.3180744-1-martin.kepplinger@puri.sm> X-CRM114-Version: 20100106-BlameMichelson ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20210527_005456_729812_A91490D6 X-CRM114-Status: UNSURE ( 8.61 ) X-CRM114-Notice: Please train this message. X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: "linux-arm-kernel" Errors-To: linux-arm-kernel-bounces+linux-arm-kernel=archiver.kernel.org@lists.infradead.org Describe the 2 available CSI interfaces on the i.MX8MQ with the MIPI-CSI2 receiver and the CSI Bridge that provides the user buffers. An image sensor is to be connected to the MIPIs' second port, described in board files. Signed-off-by: Martin Kepplinger --- arch/arm64/boot/dts/freescale/imx8mq.dtsi | 102 ++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/arch/arm64/boot/dts/freescale/imx8mq.dtsi b/arch/arm64/boot/dts/freescale/imx8mq.dtsi index 91df9c5350ae..5bf02f4f904d 100644 --- a/arch/arm64/boot/dts/freescale/imx8mq.dtsi +++ b/arch/arm64/boot/dts/freescale/imx8mq.dtsi @@ -1099,6 +1099,108 @@ uart4: serial@30a60000 { status = "disabled"; }; + mipi_csi1: mipi_csi1@30a70000 { + compatible = "fsl,imx8mq-mipi-csi2"; + reg = <0x30a70000 0x1000>; + clocks = <&clk IMX8MQ_CLK_CSI1_CORE>, + <&clk IMX8MQ_CLK_CSI1_ESC>, + <&clk IMX8MQ_CLK_CSI1_PHY_REF>, + <&clk IMX8MQ_CLK_CLKO2>; + clock-names = "core", "esc", "pxl", "clko2"; + assigned-clocks = <&clk IMX8MQ_CLK_CSI1_CORE>, + <&clk IMX8MQ_CLK_CSI1_PHY_REF>, + <&clk IMX8MQ_CLK_CSI1_ESC>; + assigned-clock-rates = <266000000>, <200000000>, <66000000>; + assigned-clock-parents = <&clk IMX8MQ_SYS1_PLL_266M>, + <&clk IMX8MQ_SYS2_PLL_1000M>, + <&clk IMX8MQ_SYS1_PLL_800M>; + power-domains = <&pgc_mipi_csi1>; + phy-reset = <&src>; + phy-gpr = <&iomuxc_gpr>; + interconnects = <&noc IMX8MQ_ICM_CSI1 &noc IMX8MQ_ICS_DRAM>; + interconnect-names = "dram"; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + csi1_mipi_ep: endpoint { + remote-endpoint = <&csi1_ep>; + }; + }; + }; + }; + + csi1_bridge: csi1_bridge@30a90000 { + compatible = "fsl,imx7-csi"; + reg = <0x30a90000 0x10000>; + interrupts = ; + clocks = <&clk IMX8MQ_CLK_CSI1_ROOT>; + clock-names = "mclk"; + status = "disabled"; + + port { + csi1_ep: endpoint { + remote-endpoint = <&csi1_mipi_ep>; + }; + }; + }; + + mipi_csi2: mipi_csi2@30b60000 { + compatible = "fsl,imx8mq-mipi-csi2"; + reg = <0x30b60000 0x1000>; + clocks = <&clk IMX8MQ_CLK_CSI2_CORE>, + <&clk IMX8MQ_CLK_CSI2_ESC>, + <&clk IMX8MQ_CLK_CSI2_PHY_REF>, + <&clk IMX8MQ_CLK_CLKO2>; + clock-names = "core", "esc", "pxl", "clko2"; + assigned-clocks = <&clk IMX8MQ_CLK_CSI2_CORE>, + <&clk IMX8MQ_CLK_CSI2_PHY_REF>, + <&clk IMX8MQ_CLK_CSI2_ESC>; + assigned-clock-rates = <266000000>, <333000000>, <66000000>; + assigned-clock-parents = <&clk IMX8MQ_SYS1_PLL_266M>, + <&clk IMX8MQ_SYS2_PLL_1000M>, + <&clk IMX8MQ_SYS1_PLL_800M>; + power-domains = <&pgc_mipi_csi2>; + phy-reset = <&src>; + phy-gpr = <&iomuxc_gpr>; + interconnects = <&noc IMX8MQ_ICM_CSI2 &noc IMX8MQ_ICS_DRAM>; + interconnect-names = "dram"; + status = "disabled"; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + + csi2_mipi_ep: endpoint { + remote-endpoint = <&csi2_ep>; + }; + }; + }; + }; + + csi2_bridge: csi2_bridge@30b80000 { + compatible = "fsl,imx7-csi"; + reg = <0x30b80000 0x10000>; + interrupts = ; + clocks = <&clk IMX8MQ_CLK_CSI2_ROOT>; + clock-names = "mclk"; + status = "disabled"; + + port { + csi2_ep: endpoint { + remote-endpoint = <&csi2_mipi_ep>; + }; + }; + }; + mu: mailbox@30aa0000 { compatible = "fsl,imx8mq-mu", "fsl,imx6sx-mu"; reg = <0x30aa0000 0x10000>;