From nobody Mon Dec 1 22:37:15 2025 Received: from CO1PR03CU002.outbound.protection.outlook.com (mail-westus2azon11010052.outbound.protection.outlook.com [52.101.46.52]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 45E72288C96; Fri, 28 Nov 2025 09:20:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=fail smtp.client-ip=52.101.46.52 ARC-Seal: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764321625; cv=fail; b=euWW8PdYfz6CEinlq4A1JFAbxQtVdp4XCnhxjDuLe+pfOvB/QC+9YBKScTPuLi3dJYjyQxk8W2PWb8f0NB5s8KPCLv8Y28JhLoD0sNAiZlXp1ZycOgtdyMbOQ1y3akmFNOHhPhXqhBi/RX+UIB7EzoRNfbVaRyrKsadA7zfWK9s= ARC-Message-Signature: i=2; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764321625; c=relaxed/simple; bh=uOl11kXR+//z+MQj98JOUcewGm2wKRt5/wnrdeZIR4U=; h=From:To:CC:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=AmS7S9a8Ac0bT6nTtoVrKrSFQQlAZevX0VJQ8NEBObh5KnywR+oghp59qckstOgxwA40ytUA1DRrEl3vLdmNzz5CTGE3R/fV9K+WRSNkKEtIkiO+SJyo3IEPwZWEswX7L3pgC+qvaEktw5+sUZzqsahHJzhQa7y4WjmwQw0MIkA= ARC-Authentication-Results: i=2; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com; spf=fail smtp.mailfrom=amd.com; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b=tCUEra9N; arc=fail smtp.client-ip=52.101.46.52 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=amd.com Authentication-Results: smtp.subspace.kernel.org; spf=fail smtp.mailfrom=amd.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=amd.com header.i=@amd.com header.b="tCUEra9N" ARC-Seal: i=1; a=rsa-sha256; s=arcselector10001; d=microsoft.com; cv=none; b=rxZvHp4lf2vHYTAea0zMMHU9QXB9K1iJ+T6O+7CRsZeW3TfDBsixWn/Jol2k9eQHPKzLFFlsBFIBxpQKz5NnpB91DBFtl/5TnkUvwMJocyf1VCvJDL8xaCRKrrk7wqM5KQZvr7tXVRsQfGKGFi2ulaHRWKEex0/I5T69mEZ/3MM69zjSHro85AehLaocM0B3+9L0DIDN3c7VNmfa+qLAJfTw/sxwpVXAxVwLvTQYMbJOJj7K/bsroz1seYeWSfSadIBIkbQaz7NNCHYEqXwdrN3Ov48dmY1ISZpz70HZQGowb9h5vF+dtcpPzIjIr5YZRHR4xK3vb+OHKPgyUwqdyg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector10001; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=s4ksSIflYrMMtvsh8dNXw6b+LscaVlVfmJu+ZoLQKpo=; b=Us+zrI9vOb+YChJtxRmb5gxrobNYQ+ufWYHu5lbKb2fxruxIF4WlTDwA/+ypWZmpXCtAud7FJGhQX9ZnQDq7Ckl1bGs42K5+4JFfJp/LJZsAfNjZnm+nNXOdBRfrQXx30ihPsk1Vy92yA3t4MU16T/UcYQxCvlS9f4g4gGoa5POAaTbhRzoOgrevhI9HYpoWb03Ubm85spzqvsxI6DRGFEr4IH81PFAOwK7z1jDsA59CQ1s/GHSq2eKhNyGc32QBGrO0EGj7rROSc13Q97eTunqFlSgS3nSlIQzHsig/XvHN2gnnVSOmN+BZBr4cKqT97MYJQODlPoHmac8d3De9PA== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=pass (sender ip is 165.204.84.17) smtp.rcpttodomain=kernel.org smtp.mailfrom=amd.com; dmarc=pass (p=quarantine sp=quarantine pct=100) action=none header.from=amd.com; dkim=none (message not signed); arc=none (0) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=amd.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=s4ksSIflYrMMtvsh8dNXw6b+LscaVlVfmJu+ZoLQKpo=; b=tCUEra9N6j6tZTWgrnoyfHAAsTozE1Y1rRwKlXUqtj8pgezudKZ8Hr43TRypr/XcB3kkv1ImDJwSJLjBcyI0/hzlMuG7P+GocY82RTBLYrznSgSFFaIpExenILHEaero5QHvHkrqqqQ83u+VHKlHoGetfEiEL1VGl4uxi//NfWc= Received: from SJ0PR03CA0374.namprd03.prod.outlook.com (2603:10b6:a03:3a1::19) by SA3PR12MB9198.namprd12.prod.outlook.com (2603:10b6:806:39f::10) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9366.17; Fri, 28 Nov 2025 09:20:15 +0000 Received: from SJ5PEPF000001F0.namprd05.prod.outlook.com (2603:10b6:a03:3a1:cafe::7d) by SJ0PR03CA0374.outlook.office365.com (2603:10b6:a03:3a1::19) with Microsoft SMTP Server (version=TLS1_3, cipher=TLS_AES_256_GCM_SHA384) id 15.20.9366.14 via Frontend Transport; Fri, 28 Nov 2025 09:20:15 +0000 X-MS-Exchange-Authentication-Results: spf=pass (sender IP is 165.204.84.17) smtp.mailfrom=amd.com; dkim=none (message not signed) header.d=none;dmarc=pass action=none header.from=amd.com; Received-SPF: Pass (protection.outlook.com: domain of amd.com designates 165.204.84.17 as permitted sender) receiver=protection.outlook.com; client-ip=165.204.84.17; helo=satlexmb07.amd.com; pr=C Received: from satlexmb07.amd.com (165.204.84.17) by SJ5PEPF000001F0.mail.protection.outlook.com (10.167.242.68) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.9366.7 via Frontend Transport; Fri, 28 Nov 2025 09:20:14 +0000 Received: from 555e2b870847.amd.com (10.180.168.240) by satlexmb07.amd.com (10.181.42.216) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.2.2562.17; Fri, 28 Nov 2025 03:20:05 -0600 From: Bin Du To: , , , , , , , , CC: , , , , , , , , , , Bin Du , Svetoslav Stoilov , "Alexey Zagorodnikov" Subject: [PATCH v6 4/7] media: platform: amd: isp4 subdev and firmware loading handling added Date: Fri, 28 Nov 2025 17:19:26 +0800 Message-ID: <20251128091929.165272-5-Bin.Du@amd.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20251128091929.165272-1-Bin.Du@amd.com> References: <20251128091929.165272-1-Bin.Du@amd.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-ClientProxiedBy: satlexmb08.amd.com (10.181.42.217) To satlexmb07.amd.com (10.181.42.216) X-EOPAttributedMessage: 0 X-MS-PublicTrafficType: Email X-MS-TrafficTypeDiagnostic: SJ5PEPF000001F0:EE_|SA3PR12MB9198:EE_ X-MS-Office365-Filtering-Correlation-Id: 7eb85633-6976-48fc-001f-08de2e5f5707 X-MS-Exchange-SenderADCheck: 1 X-MS-Exchange-AntiSpam-Relay: 0 X-Microsoft-Antispam: BCL:0;ARA:13230040|376014|7416014|82310400026|36860700013|1800799024; X-Microsoft-Antispam-Message-Info: =?us-ascii?Q?CFMTif3w86dB5w46qb9bBgS+aMr0usOuGYZ/0kOcCADPCQqKq4R4cTYF7/Gp?= =?us-ascii?Q?FFOCrtPCUsNQZRmchjgZNHnyP6bECrYM3+yXtzpevMYoNcKUdoe+kwUY+SYp?= =?us-ascii?Q?R7ChvqwtzDqkNrMHBerj6xZN2JSiCbv1g/2sSzWQLC/NwicoWmCawpGKScsq?= =?us-ascii?Q?bhEGc5Mw/FjHjaD4x51U9CTMankyumIjBBXHBIPxRT3wMHYqCQwY0RRo1Y1r?= =?us-ascii?Q?aIDAxaOWIj4/4MRgThcQseOo7V8HhprounvD8i+TbpIM0EitPavsyJbk/9eD?= =?us-ascii?Q?Opct1C2OC/qi61IWSk50qZRcP8qx3GQCKO1Eiw0kPJ2rb6WkZ2RIwPJJim4/?= =?us-ascii?Q?nXFQsKXkCfUpv3gATL91Pckiel8VbzLePnpPbwZtMDexdodB5v8c3RoFlXOa?= =?us-ascii?Q?1+fyexRI3PkxmULRU+aS0makL8/HeaFJg1LcolexobQ+wE9o6MhUD4FwTXJT?= =?us-ascii?Q?M8bOlu8q1rLP3cWDvTVI9IFLde7tfu1LhwlO8ur1C1ktFZehzTDn5gLwyh1f?= =?us-ascii?Q?E2Pshq5va+Tfed1iVvzP3cYHHv10zdfnDMoB79UidbZfYlOKDVIqVwFQNIe2?= =?us-ascii?Q?a7PXLj9Mxa7VtHjK5nKC11CIX+WF1hDO/mWHnVmPLDV30g/SlIWNs7MQ6+2A?= =?us-ascii?Q?RqvH7HW9he7vDcSV68SUAHSQkY3cWILyqG9RuJ7bLz9BwE/nfJf8dive7lqI?= =?us-ascii?Q?szcz171Pl2B/mzYv9B2+FY2CBw2bk6C0EAriolM8sBtgY8VJHZlx5hBk0lbm?= =?us-ascii?Q?hLYss5X1tV3whqMqyBm6eYOuQlXI7+m+nRGejloFw/xGXaRqXPgWrrBwHwwc?= =?us-ascii?Q?fchu6fL0iCgXeJ0qvO0r25h4/MB3TXGct5SSxyELP5QbLD8cZh2V0FX0Ko3m?= =?us-ascii?Q?9ghvm9JTakRjv53IbXmsEh6k6Jl+6fvFnCLZwNNydJU3cYGzo3QCK/D4st7+?= =?us-ascii?Q?0d39FC8tmO2dEUldMqwZS5L72w5kQP0e5qX6C/VgFmE4Bo52vMzaF+KfI0Hv?= =?us-ascii?Q?boD8Wq4JGDJan7bZ05F6hdm8qubYKzvkgGfP8nzsnnxd7OuSHNR7uQNE/afl?= =?us-ascii?Q?AP4/O9Gli40nqsM1ej0+RQPus6ROtN7FZkew78LF9BmdveATGgTOVBhQ5mQK?= =?us-ascii?Q?OLrbXiBbF+odESFgSC5U557qg3vGf/gsOPiF5Kzooc1fYk0MjfDd5HRD6exi?= =?us-ascii?Q?TZ1zub+itR1rugwnxOW30H/xDon3YRL3jDwH8S9XmZol5Ys8EKvlEmoJmNDO?= =?us-ascii?Q?5cFf/mDvJYabb6VCRru4CLeL5oS4WkemKm5lJQvaD9SZxa+L4H6nrY/1IzZN?= =?us-ascii?Q?f5SQb9lyJE3iUSP/+NwFgsjbGH5sFTavA+FlnXoY22HSZGTcvio9C5dBl5Yj?= =?us-ascii?Q?6ZokZuFflwjqn364ADm9/waOwMC4VXLWhNv+QeCtTSOOCj3ZrhbeARPV9voJ?= =?us-ascii?Q?oHj6ss4VE19Es5HQy2muVoe51IAA5XynHaD0KvbgHLgKzstnGlMIFppswL7J?= =?us-ascii?Q?Pq7ik/ekBxx97EqNJhjJRdnIzD7VXWuZtIt1nplrrhcPJMthlfCDA6NHESb1?= =?us-ascii?Q?+1UxC9ncftY8LKoWnmo=3D?= X-Forefront-Antispam-Report: CIP:165.204.84.17;CTRY:US;LANG:en;SCL:1;SRV:;IPV:NLI;SFV:NSPM;H:satlexmb07.amd.com;PTR:InfoDomainNonexistent;CAT:NONE;SFS:(13230040)(376014)(7416014)(82310400026)(36860700013)(1800799024);DIR:OUT;SFP:1101; X-OriginatorOrg: amd.com X-MS-Exchange-CrossTenant-OriginalArrivalTime: 28 Nov 2025 09:20:14.8700 (UTC) X-MS-Exchange-CrossTenant-Network-Message-Id: 7eb85633-6976-48fc-001f-08de2e5f5707 X-MS-Exchange-CrossTenant-Id: 3dd8961f-e488-4e60-8e11-a82d994e183d X-MS-Exchange-CrossTenant-OriginalAttributedTenantConnectingIp: TenantId=3dd8961f-e488-4e60-8e11-a82d994e183d;Ip=[165.204.84.17];Helo=[satlexmb07.amd.com] X-MS-Exchange-CrossTenant-AuthSource: SJ5PEPF000001F0.namprd05.prod.outlook.com X-MS-Exchange-CrossTenant-AuthAs: Anonymous X-MS-Exchange-CrossTenant-FromEntityHeader: HybridOnPrem X-MS-Exchange-Transport-CrossTenantHeadersStamped: SA3PR12MB9198 Content-Type: text/plain; charset="utf-8" Isp4 sub-device is implementing v4l2 sub-device interface. It has one capture video node, and supports only preview stream. It manages firmware states, stream configuration. Add interrupt handling and notification for isp firmware to isp-subdevice. Co-developed-by: Sultan Alsawaf Signed-off-by: Sultan Alsawaf Co-developed-by: Svetoslav Stoilov Signed-off-by: Svetoslav Stoilov Signed-off-by: Bin Du Tested-by: Alexey Zagorodnikov --- MAINTAINERS | 2 + drivers/media/platform/amd/isp4/Makefile | 3 +- drivers/media/platform/amd/isp4/isp4.c | 98 +- drivers/media/platform/amd/isp4/isp4.h | 7 +- drivers/media/platform/amd/isp4/isp4_subdev.c | 1004 +++++++++++++++++ drivers/media/platform/amd/isp4/isp4_subdev.h | 123 ++ 6 files changed, 1229 insertions(+), 8 deletions(-) create mode 100644 drivers/media/platform/amd/isp4/isp4_subdev.c create mode 100644 drivers/media/platform/amd/isp4/isp4_subdev.h diff --git a/MAINTAINERS b/MAINTAINERS index cccae369c876..48ffc8bbdcee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1149,6 +1149,8 @@ F: drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h F: drivers/media/platform/amd/isp4/isp4_hw_reg.h F: drivers/media/platform/amd/isp4/isp4_interface.c F: drivers/media/platform/amd/isp4/isp4_interface.h +F: drivers/media/platform/amd/isp4/isp4_subdev.c +F: drivers/media/platform/amd/isp4/isp4_subdev.h =20 AMD KFD M: Felix Kuehling diff --git a/drivers/media/platform/amd/isp4/Makefile b/drivers/media/platf= orm/amd/isp4/Makefile index a2a5bf98e912..6d4e6d6ac7f5 100644 --- a/drivers/media/platform/amd/isp4/Makefile +++ b/drivers/media/platform/amd/isp4/Makefile @@ -4,4 +4,5 @@ =20 obj-$(CONFIG_AMD_ISP4) +=3D amd_capture.o amd_capture-objs :=3D isp4.o \ - isp4_interface.o + isp4_interface.o \ + isp4_subdev.o diff --git a/drivers/media/platform/amd/isp4/isp4.c b/drivers/media/platfor= m/amd/isp4/isp4.c index e77713e4ea56..0f3d87b7d5fa 100644 --- a/drivers/media/platform/amd/isp4/isp4.c +++ b/drivers/media/platform/amd/isp4/isp4.c @@ -3,15 +3,19 @@ * Copyright (C) 2025 Advanced Micro Devices, Inc. */ =20 +#include #include #include +#include #include =20 #include "isp4.h" - -#define VIDEO_BUF_NUM 5 +#include "isp4_hw_reg.h" =20 #define ISP4_DRV_NAME "amd_isp_capture" +#define ISP4_FW_RESP_RB_IRQ_STATUS_MASK \ + (ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK | \ + ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK) =20 static const struct { const char *name; @@ -19,27 +23,96 @@ static const struct { u32 en_mask; u32 ack_mask; u32 rb_int_num; -} isp4_irq[] =3D { +} isp4_irq[ISP4SD_MAX_FW_RESP_STREAM_NUM] =3D { /* The IRQ order is aligned with the isp4_subdev.fw_resp_thread order */ { .name =3D "isp_irq_global", + .status_mask =3D ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK, + .en_mask =3D ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK, + .ack_mask =3D ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK, .rb_int_num =3D 4, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12 */ }, { .name =3D "isp_irq_stream1", + .status_mask =3D ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK, + .en_mask =3D ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK, + .ack_mask =3D ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK, .rb_int_num =3D 0, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9 */ }, }; =20 +void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enab= le) +{ + u32 intr_en; + + /* Synchronize ISP_SYS_INT0_EN writes with the IRQ handler's writes */ + spin_lock_irq(&isp_subdev->irq_lock); + intr_en =3D isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN); + if (enable) + intr_en |=3D isp4_irq[index].en_mask; + else + intr_en &=3D ~isp4_irq[index].en_mask; + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); + spin_unlock_irq(&isp_subdev->irq_lock); +} + +static void isp4_wake_up_resp_thread(struct isp4_subdev *isp_subdev, u32 i= ndex) +{ + struct isp4sd_thread_handler *thread_ctx =3D &isp_subdev->fw_resp_thread[= index]; + + thread_ctx->resp_ready =3D true; + wake_up_interruptible(&thread_ctx->waitq); +} + static irqreturn_t isp4_irq_handler(int irq, void *arg) { + struct isp4_subdev *isp_subdev =3D arg; + u32 intr_ack =3D 0, intr_en =3D 0, intr_status; + int seen =3D 0; + + /* Get the ISP_SYS interrupt status */ + intr_status =3D isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_STATUS); + intr_status &=3D ISP4_FW_RESP_RB_IRQ_STATUS_MASK; + + /* Find which ISP_SYS interrupts fired */ + for (size_t i =3D 0; i < ARRAY_SIZE(isp4_irq); i++) { + if (intr_status & isp4_irq[i].status_mask) { + intr_ack |=3D isp4_irq[i].ack_mask; + intr_en |=3D isp4_irq[i].en_mask; + seen |=3D BIT(i); + } + } + + /* + * Disable the ISP_SYS interrupts that fired. Must be done before waking + * the response threads, since they re-enable interrupts when finished. + * The lock synchronizes RMW of INT0_EN with isp4_enable_interrupt(). + */ + spin_lock(&isp_subdev->irq_lock); + intr_en =3D isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN) & ~intr_en; + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); + spin_unlock(&isp_subdev->irq_lock); + + /* + * Clear the ISP_SYS interrupts. This must be done after the interrupts + * are disabled, so that ISP FW won't flag any new interrupts on these + * streams, and thus we don't need to clear interrupts again before + * re-enabling them in the response thread. + */ + isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_ACK, intr_ack); + + /* Wake up the response threads */ + for (int i; (i =3D ffs(seen)); seen =3D (seen >> i) << i) + isp4_wake_up_resp_thread(isp_subdev, i - 1); + return IRQ_HANDLED; } =20 static int isp4_capture_probe(struct platform_device *pdev) { + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; struct device *dev =3D &pdev->dev; - int irq[ARRAY_SIZE(isp4_irq)]; + struct isp4_subdev *isp_subdev; struct isp4_device *isp_dev; size_t i; int ret; @@ -50,6 +123,11 @@ static int isp4_capture_probe(struct platform_device *p= dev) =20 dev->init_name =3D ISP4_DRV_NAME; =20 + isp_subdev =3D &isp_dev->isp_subdev; + isp_subdev->mmio =3D devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(isp_subdev->mmio)) + return dev_err_probe(dev, PTR_ERR(isp_subdev->mmio), "isp ioremap fail\n= "); + for (i =3D 0; i < ARRAY_SIZE(isp4_irq); i++) { irq[i] =3D platform_get_irq(pdev, isp4_irq[i].rb_int_num); if (irq[i] < 0) @@ -57,7 +135,7 @@ static int isp4_capture_probe(struct platform_device *pd= ev) isp4_irq[i].rb_int_num); =20 ret =3D devm_request_irq(dev, irq[i], isp4_irq_handler, - IRQF_NO_AUTOEN, isp4_irq[i].name, dev); + IRQF_NO_AUTOEN, isp4_irq[i].name, isp_subdev); if (ret) return dev_err_probe(dev, ret, "fail to req irq %d\n", irq[i]); } @@ -83,6 +161,13 @@ static int isp4_capture_probe(struct platform_device *p= dev) =20 pm_runtime_set_suspended(dev); pm_runtime_enable(dev); + spin_lock_init(&isp_subdev->irq_lock); + ret =3D isp4sd_init(&isp_dev->isp_subdev, &isp_dev->v4l2_dev, irq); + if (ret) { + dev_err_probe(dev, ret, "fail init isp4 sub dev\n"); + goto err_pm_disable; + } + ret =3D media_device_register(&isp_dev->mdev); if (ret) { dev_err_probe(dev, ret, "fail to register media device\n"); @@ -94,6 +179,8 @@ static int isp4_capture_probe(struct platform_device *pd= ev) return 0; =20 err_isp4_deinit: + isp4sd_deinit(&isp_dev->isp_subdev); +err_pm_disable: pm_runtime_disable(dev); v4l2_device_unregister(&isp_dev->v4l2_dev); err_clean_media: @@ -108,6 +195,7 @@ static void isp4_capture_remove(struct platform_device = *pdev) struct device *dev =3D &pdev->dev; =20 media_device_unregister(&isp_dev->mdev); + isp4sd_deinit(&isp_dev->isp_subdev); v4l2_device_unregister(&isp_dev->v4l2_dev); media_device_cleanup(&isp_dev->mdev); pm_runtime_disable(dev); diff --git a/drivers/media/platform/amd/isp4/isp4.h b/drivers/media/platfor= m/amd/isp4/isp4.h index 7f2db0dfa2d9..2db6683d6d8b 100644 --- a/drivers/media/platform/amd/isp4/isp4.h +++ b/drivers/media/platform/amd/isp4/isp4.h @@ -6,12 +6,15 @@ #ifndef _ISP4_H_ #define _ISP4_H_ =20 -#include -#include +#include +#include "isp4_subdev.h" =20 struct isp4_device { struct v4l2_device v4l2_dev; + struct isp4_subdev isp_subdev; struct media_device mdev; }; =20 +void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enab= le); + #endif /* _ISP4_H_ */ diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/= platform/amd/isp4/isp4_subdev.c new file mode 100644 index 000000000000..fac16207727e --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_subdev.c @@ -0,0 +1,1004 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include +#include + +#include "isp4_fw_cmd_resp.h" +#include "isp4_interface.h" +#include "isp4.h" + +#define ISP4SD_MIN_BUF_CNT_BEF_START_STREAM 4 + +#define ISP4SD_PERFORMANCE_STATE_LOW 0 +#define ISP4SD_PERFORMANCE_STATE_HIGH 1 + +/* align 32KB */ +#define ISP4SD_META_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000) + +#define to_isp4_subdev(v4l2_sdev) \ + container_of(v4l2_sdev, struct isp4_subdev, sdev) + +static const char *isp4sd_entity_name =3D "amd isp4"; + +static const char *isp4sd_thread_name[ISP4SD_MAX_FW_RESP_STREAM_NUM] =3D { + "amd_isp4_thread_global", + "amd_isp4_thread_stream1", +}; + +static void isp4sd_module_enable(struct isp4_subdev *isp_subdev, bool enab= le) +{ + if (isp_subdev->enable_gpio) { + gpiod_set_value(isp_subdev->enable_gpio, enable ? 1 : 0); + dev_dbg(isp_subdev->dev, "%s isp_subdev module\n", + enable ? "enable" : "disable"); + } +} + +static int isp4sd_setup_fw_mem_pool(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev =3D isp_subdev->dev; + int ret; + + if (!ispif->fw_mem_pool) { + dev_err(dev, "fail to alloc mem pool\n"); + return -ENOMEM; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee paddi= ng bits are + * zeroed, since this is not guaranteed on all compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + buf_type.buffer_type =3D BUFFER_TYPE_MEM_POOL; + buf_type.buffer.vmid_space.bit.space =3D ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(ispif->fw_mem_pool->gpu_mc_addr, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a =3D (u32)ispif->fw_mem_pool->mem_size; + + ret =3D isp4if_send_command(ispif, CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type)); + if (ret) { + dev_err(dev, "send fw mem pool 0x%llx(%u) fail %d\n", + ispif->fw_mem_pool->gpu_mc_addr, buf_type.buffer.buf_size_a, ret); + return ret; + } + + dev_dbg(dev, "send fw mem pool 0x%llx(%u) suc\n", + ispif->fw_mem_pool->gpu_mc_addr, buf_type.buffer.buf_size_a); + + return 0; +} + +static int isp4sd_set_stream_path(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4fw_cmd_set_stream_cfg cmd; + struct device *dev =3D isp_subdev->dev; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee paddi= ng bits are + * zeroed, since this is not guaranteed on all compilers. + */ + memset(&cmd, 0, sizeof(cmd)); + cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id =3D SENSOR_ID_ON_MIPI0; + cmd.stream_cfg.mipi_pipe_path_cfg.b_enable =3D true; + cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id =3D MIPI0_ISP_PIPELINE_ID; + + cmd.stream_cfg.b_enable_tnr =3D true; + dev_dbg(dev, "isp4fw_sensor_id %d, pipeId 0x%x EnableTnr %u\n", + cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id, + cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id, + cmd.stream_cfg.b_enable_tnr); + + return isp4if_send_command(ispif, CMD_ID_SET_STREAM_CONFIG, + &cmd, sizeof(cmd)); +} + +static int isp4sd_send_meta_buf(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev =3D isp_subdev->dev; + int i; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee paddi= ng bits are + * zeroed, since this is not guaranteed on all compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + for (i =3D 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + struct isp4if_gpu_mem_info *meta_info_buf =3D + isp_subdev->ispif.meta_info_buf[i]; + int ret; + + if (!meta_info_buf) { + dev_err(dev, "fail for no meta info buf(%u)\n", i); + return -ENOMEM; + } + buf_type.buffer_type =3D BUFFER_TYPE_META_INFO; + buf_type.buffer.vmid_space.bit.space =3D ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(isp_subdev->ispif.meta_info_buf[i]->gpu_mc_addr, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a =3D (u32)isp_subdev->ispif.meta_info_buf[i]->= mem_size; + ret =3D isp4if_send_command(ispif, CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type)); + if (ret) { + dev_err(dev, "send meta info(%u) fail\n", i); + return ret; + } + } + + dev_dbg(dev, "send meta info suc\n"); + return 0; +} + +static bool isp4sd_get_str_out_prop(struct isp4_subdev *isp_subdev, + struct isp4fw_image_prop *out_prop, + struct v4l2_subdev_state *state, u32 pad) +{ + struct device *dev =3D isp_subdev->dev; + struct v4l2_mbus_framefmt *format; + + format =3D v4l2_subdev_state_get_format(state, pad, 0); + if (!format) { + dev_err(dev, "fail get subdev state format\n"); + return false; + } + + switch (format->code) { + case MEDIA_BUS_FMT_YUYV8_1_5X8: + out_prop->image_format =3D IMAGE_FORMAT_NV12; + out_prop->width =3D format->width; + out_prop->height =3D format->height; + out_prop->luma_pitch =3D format->width; + out_prop->chroma_pitch =3D out_prop->width; + break; + case MEDIA_BUS_FMT_YUYV8_1X16: + out_prop->image_format =3D IMAGE_FORMAT_YUV422INTERLEAVED; + out_prop->width =3D format->width; + out_prop->height =3D format->height; + out_prop->luma_pitch =3D format->width * 2; + out_prop->chroma_pitch =3D 0; + break; + default: + dev_err(dev, "fail for bad image format:0x%x\n", + format->code); + return false; + } + + if (!out_prop->width || !out_prop->height) + return false; + + return true; +} + +static int isp4sd_kickoff_stream(struct isp4_subdev *isp_subdev, u32 w, u3= 2 h) +{ + struct isp4sd_sensor_info *sensor_info =3D &isp_subdev->sensor_info; + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + + if (sensor_info->status =3D=3D ISP4SD_START_STATUS_STARTED) + return 0; + + if (sensor_info->status =3D=3D ISP4SD_START_STATUS_START_FAIL) { + dev_err(dev, "fail for previous start fail\n"); + return -EINVAL; + } + + dev_dbg(dev, "w:%u,h:%u\n", w, h); + + if (isp4sd_send_meta_buf(isp_subdev)) { + dev_err(dev, "fail to send meta buf\n"); + sensor_info->status =3D ISP4SD_START_STATUS_START_FAIL; + return -EINVAL; + } + + sensor_info->status =3D ISP4SD_START_STATUS_OFF; + + if (!sensor_info->start_stream_cmd_sent && + sensor_info->buf_sent_cnt >=3D + ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) { + int ret =3D isp4if_send_command(ispif, CMD_ID_START_STREAM, + NULL, 0); + if (ret) { + dev_err(dev, "fail to start stream\n"); + return ret; + } + + sensor_info->start_stream_cmd_sent =3D true; + } else { + dev_dbg(dev, + "no send START_STREAM, start_sent %u, buf_sent %u\n", + sensor_info->start_stream_cmd_sent, + sensor_info->buf_sent_cnt); + } + + return 0; +} + +static int isp4sd_setup_output(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info =3D &isp_subdev->sensor_info.outpu= t_info; + struct isp4sd_sensor_info *sensor_info =3D &isp_subdev->sensor_info; + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4fw_cmd_set_out_ch_prop cmd_ch_prop; + struct isp4fw_cmd_enable_out_ch cmd_ch_en; + struct device *dev =3D isp_subdev->dev; + int ret; + + if (output_info->start_status =3D=3D ISP4SD_START_STATUS_STARTED) + return 0; + + if (output_info->start_status =3D=3D ISP4SD_START_STATUS_START_FAIL) { + dev_err(dev, "fail for previous start fail\n"); + return -EINVAL; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee paddi= ng bits are + * zeroed, since this is not guaranteed on all compilers. + */ + memset(&cmd_ch_prop, 0, sizeof(cmd_ch_prop)); + cmd_ch_prop.ch =3D ISP_PIPE_OUT_CH_PREVIEW; + + if (!isp4sd_get_str_out_prop(isp_subdev, &cmd_ch_prop.image_prop, state, = pad)) { + dev_err(dev, "fail to get out prop\n"); + return -EINVAL; + } + + dev_dbg(dev, "channel:%d,fmt %d,w:h=3D%u:%u,lp:%u,cp%u\n", + cmd_ch_prop.ch, + cmd_ch_prop.image_prop.image_format, + cmd_ch_prop.image_prop.width, cmd_ch_prop.image_prop.height, + cmd_ch_prop.image_prop.luma_pitch, + cmd_ch_prop.image_prop.chroma_pitch); + + ret =3D isp4if_send_command(ispif, CMD_ID_SET_OUT_CHAN_PROP, + &cmd_ch_prop, sizeof(cmd_ch_prop)); + if (ret) { + output_info->start_status =3D ISP4SD_START_STATUS_START_FAIL; + dev_err(dev, "fail to set out prop\n"); + return ret; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee paddi= ng bits are + * zeroed, since this is not guaranteed on all compilers. + */ + memset(&cmd_ch_en, 0, sizeof(cmd_ch_en)); + cmd_ch_en.ch =3D ISP_PIPE_OUT_CH_PREVIEW; + cmd_ch_en.is_enable =3D true; + ret =3D isp4if_send_command(ispif, CMD_ID_ENABLE_OUT_CHAN, + &cmd_ch_en, sizeof(cmd_ch_en)); + if (ret) { + output_info->start_status =3D ISP4SD_START_STATUS_START_FAIL; + dev_err(dev, "fail to enable channel\n"); + return ret; + } + + dev_dbg(dev, "enable channel %d\n", cmd_ch_en.ch); + + if (!sensor_info->start_stream_cmd_sent) { + ret =3D isp4sd_kickoff_stream(isp_subdev, + cmd_ch_prop.image_prop.width, + cmd_ch_prop.image_prop.height); + if (ret) { + dev_err(dev, "kickoff stream fail %d\n", ret); + return ret; + } + /* + * sensor_info->start_stream_cmd_sent will be set to true + * 1. in isp4sd_kickoff_stream, if app first send buffer then + * start stream + * 2. in isp_set_stream_buf, if app first start stream, then + * send buffer + * because ISP FW has the requirement, host needs to send buffer + * before send start stream cmd + */ + if (sensor_info->start_stream_cmd_sent) { + sensor_info->status =3D ISP4SD_START_STATUS_STARTED; + output_info->start_status =3D ISP4SD_START_STATUS_STARTED; + dev_dbg(dev, "kickoff stream suc,start cmd sent\n"); + } + } else { + dev_dbg(dev, "stream running, no need kickoff\n"); + output_info->start_status =3D ISP4SD_START_STATUS_STARTED; + } + + dev_dbg(dev, "setup output suc\n"); + return 0; +} + +static int isp4sd_init_stream(struct isp4_subdev *isp_subdev) +{ + struct device *dev =3D isp_subdev->dev; + int ret; + + ret =3D isp4sd_setup_fw_mem_pool(isp_subdev); + if (ret) { + dev_err(dev, "fail to setup fw mem pool\n"); + return ret; + } + + ret =3D isp4sd_set_stream_path(isp_subdev); + if (ret) { + dev_err(dev, "fail to setup stream path\n"); + return ret; + } + + return 0; +} + +static void isp4sd_reset_stream_info(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_sensor_info *sensor_info =3D &isp_subdev->sensor_info; + struct v4l2_mbus_framefmt *format; + + format =3D v4l2_subdev_state_get_format(state, pad, 0); + if (!format) { + dev_err(isp_subdev->dev, "fail to get v4l2 format\n"); + } else { + memset(format, 0, sizeof(*format)); + format->code =3D MEDIA_BUS_FMT_YUYV8_1_5X8; + } + + sensor_info->output_info.start_status =3D ISP4SD_START_STATUS_OFF; +} + +static void isp4sd_reset_camera_info(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_sensor_info *sensor_info =3D &isp_subdev->sensor_info; + + sensor_info->status =3D ISP4SD_START_STATUS_OFF; + isp4sd_reset_stream_info(isp_subdev, state, pad); + + sensor_info->start_stream_cmd_sent =3D false; +} + +static int isp4sd_uninit_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info =3D &isp_subdev->sensor_info.outpu= t_info; + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + + if (output_info->start_status =3D=3D ISP4SD_START_STATUS_STARTED) { + dev_dbg(dev, "fail for stream is still running\n"); + return -EINVAL; + } + + isp4sd_reset_camera_info(isp_subdev, state, pad); + isp4if_clear_cmdq(ispif); + return 0; +} + +static void isp4sd_fw_resp_cmd_done(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id, + struct isp4fw_resp_cmd_done *para) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4if_cmd_element *ele =3D + isp4if_rm_cmd_from_cmdq(ispif, para->cmd_seq_num, para->cmd_id); + struct device *dev =3D isp_subdev->dev; + + dev_dbg(dev, "stream %d,cmd (0x%08x)(%d),seq %u, ele %p\n", + stream_id, + para->cmd_id, para->cmd_status, para->cmd_seq_num, + ele); + + if (ele) { + complete(&ele->cmd_done); + if (atomic_dec_and_test(&ele->refcnt)) + kfree(ele); + } +} + +static struct isp4fw_meta_info *isp4sd_get_meta_by_mc(struct isp4_subdev *= isp_subdev, + u64 mc) +{ + for (int i =3D 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) { + struct isp4if_gpu_mem_info *meta_info_buf =3D + isp_subdev->ispif.meta_info_buf[i]; + + if (meta_info_buf->gpu_mc_addr =3D=3D mc) + return meta_info_buf->sys_addr; + } + + return NULL; +} + +static void isp4sd_send_meta_info(struct isp4_subdev *isp_subdev, + u64 meta_info_mc) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4fw_cmd_send_buffer buf_type; + struct device *dev =3D isp_subdev->dev; + + if (isp_subdev->sensor_info.status !=3D ISP4SD_START_STATUS_STARTED) { + dev_warn(dev, "not working status %i, meta_info 0x%llx\n", + isp_subdev->sensor_info.status, meta_info_mc); + return; + } + + /* + * The struct will be shared with ISP FW, use memset() to guarantee paddi= ng bits are + * zeroed, since this is not guaranteed on all compilers. + */ + memset(&buf_type, 0, sizeof(buf_type)); + buf_type.buffer_type =3D BUFFER_TYPE_META_INFO; + buf_type.buffer.vmid_space.bit.space =3D ADDR_SPACE_TYPE_GPU_VA; + isp4if_split_addr64(meta_info_mc, + &buf_type.buffer.buf_base_a_lo, + &buf_type.buffer.buf_base_a_hi); + buf_type.buffer.buf_size_a =3D ISP4SD_META_BUF_SIZE; + + if (isp4if_send_command(ispif, CMD_ID_SEND_BUFFER, + &buf_type, sizeof(buf_type))) + dev_err(dev, "fail send meta_info 0x%llx\n", + meta_info_mc); + else + dev_dbg(dev, "resend meta_info 0x%llx\n", meta_info_mc); +} + +static void isp4sd_fw_resp_frame_done(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id, + struct isp4fw_resp_param_package *para) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + struct isp4if_img_buf_node *prev; + struct isp4fw_meta_info *meta; + u64 mc =3D 0; + + mc =3D isp4if_join_addr64(para->package_addr_lo, para->package_addr_hi); + meta =3D isp4sd_get_meta_by_mc(isp_subdev, mc); + if (!meta) { + dev_err(dev, "fail to get meta from mc %llx\n", mc); + return; + } + + dev_dbg(dev, "ts:%llu,streamId:%d,poc:%u,preview_en:%u(%i)\n", + ktime_get_ns(), stream_id, meta->poc, + meta->preview.enabled, + meta->preview.status); + + if (meta->preview.enabled && + (meta->preview.status =3D=3D BUFFER_STATUS_SKIPPED || + meta->preview.status =3D=3D BUFFER_STATUS_DONE || + meta->preview.status =3D=3D BUFFER_STATUS_DIRTY)) { + prev =3D isp4if_dequeue_buffer(ispif); + if (prev) + isp4if_dealloc_buffer_node(prev); + else + dev_err(dev, "fail null prev buf\n"); + + } else if (meta->preview.enabled) { + dev_err(dev, "fail bad preview status %u\n", + meta->preview.status); + } + + if (isp_subdev->sensor_info.status =3D=3D ISP4SD_START_STATUS_STARTED) + isp4sd_send_meta_info(isp_subdev, mc); + + dev_dbg(dev, "stream_id:%d, status:%d\n", stream_id, + isp_subdev->sensor_info.status); +} + +static void isp4sd_fw_resp_func(struct isp4_subdev *isp_subdev, + enum isp4if_stream_id stream_id) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + struct isp4fw_resp resp; + + while (true) { + if (isp4if_f2h_resp(ispif, stream_id, &resp)) { + /* Re-enable the interrupt */ + isp4_intr_enable(isp_subdev, stream_id, true); + /* + * Recheck to see if there is a new response. + * To ensure that an in-flight interrupt is not lost, + * enabling the interrupt must occur _before_ checking + * for a new response, hence a memory barrier is needed. + * Disable the interrupt again if there was a new response. + */ + mb(); + if (likely(isp4if_f2h_resp(ispif, stream_id, &resp))) + break; + + isp4_intr_enable(isp_subdev, stream_id, false); + } + + switch (resp.resp_id) { + case RESP_ID_CMD_DONE: + isp4sd_fw_resp_cmd_done(isp_subdev, stream_id, + &resp.param.cmd_done); + break; + case RESP_ID_NOTI_FRAME_DONE: + isp4sd_fw_resp_frame_done(isp_subdev, stream_id, + &resp.param.frame_done); + break; + default: + dev_err(dev, "-><- fail respid (0x%x)\n", resp.resp_id); + break; + } + } +} + +static s32 isp4sd_fw_resp_thread(void *context) +{ + struct isp4_subdev_thread_param *para =3D context; + struct isp4_subdev *isp_subdev =3D para->isp_subdev; + struct isp4sd_thread_handler *thread_ctx =3D + &isp_subdev->fw_resp_thread[para->idx]; + struct device *dev =3D isp_subdev->dev; + + dev_dbg(dev, "[%u] fw resp thread started\n", para->idx); + while (true) { + wait_event_interruptible(thread_ctx->waitq, thread_ctx->resp_ready); + thread_ctx->resp_ready =3D false; + + if (kthread_should_stop()) { + dev_dbg(dev, "[%u] fw resp thread quit\n", para->idx); + break; + } + + isp4sd_fw_resp_func(isp_subdev, para->idx); + } + + return 0; +} + +static int isp4sd_stop_resp_proc_threads(struct isp4_subdev *isp_subdev) +{ + int i; + + for (i =3D 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) { + struct isp4sd_thread_handler *thread_ctx =3D + &isp_subdev->fw_resp_thread[i]; + + if (thread_ctx->thread) { + kthread_stop(thread_ctx->thread); + thread_ctx->thread =3D NULL; + } + } + + return 0; +} + +static int isp4sd_start_resp_proc_threads(struct isp4_subdev *isp_subdev) +{ + struct device *dev =3D isp_subdev->dev; + int i; + + for (i =3D 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) { + struct isp4sd_thread_handler *thread_ctx =3D &isp_subdev->fw_resp_thread= [i]; + + isp_subdev->isp_resp_para[i].idx =3D i; + isp_subdev->isp_resp_para[i].isp_subdev =3D isp_subdev; + init_waitqueue_head(&thread_ctx->waitq); + thread_ctx->resp_ready =3D false; + + thread_ctx->thread =3D kthread_run(isp4sd_fw_resp_thread, + &isp_subdev->isp_resp_para[i], + isp4sd_thread_name[i]); + if (IS_ERR(thread_ctx->thread)) { + dev_err(dev, "create thread [%d] fail\n", i); + thread_ctx->thread =3D NULL; + isp4sd_stop_resp_proc_threads(isp_subdev); + return -EINVAL; + } + } + + return 0; +} + +static int isp4sd_pwroff_and_deinit(struct isp4_subdev *isp_subdev) +{ + struct isp4sd_sensor_info *sensor_info =3D &isp_subdev->sensor_info; + unsigned int perf_state =3D ISP4SD_PERFORMANCE_STATE_LOW; + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + int ret; + + if (sensor_info->status =3D=3D ISP4SD_START_STATUS_STARTED) { + dev_err(dev, "fail for stream still running\n"); + return -EINVAL; + } + + sensor_info->status =3D ISP4SD_START_STATUS_OFF; + + if (isp_subdev->irq_enabled) { + for (int i =3D 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + disable_irq(isp_subdev->irq[i]); + isp_subdev->irq_enabled =3D false; + } + + isp4sd_stop_resp_proc_threads(isp_subdev); + dev_dbg(dev, "isp_subdev stop resp proc streads suc"); + + isp4if_stop(ispif); + + ret =3D dev_pm_genpd_set_performance_state(dev, perf_state); + if (ret) + dev_err(dev, + "fail to set isp_subdev performance state %u,ret %d\n", + perf_state, ret); + + /* hold ccpu reset */ + isp4hw_wreg(isp_subdev->mmio, ISP_SOFT_RESET, 0); + isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0); + ret =3D pm_runtime_put_sync(dev); + if (ret) + dev_err(dev, "power off isp_subdev fail %d\n", ret); + else + dev_dbg(dev, "power off isp_subdev suc\n"); + + ispif->status =3D ISP4IF_STATUS_PWR_OFF; + isp4if_clear_cmdq(ispif); + isp4sd_module_enable(isp_subdev, false); + + /* + * When opening the camera, isp4sd_module_enable(isp_subdev, true) is cal= led. + * Hardware requires at least a 20ms delay between disabling and enabling= the module, + * so a sleep is added to ensure ISP stability during quick reopen scenar= ios. + */ + msleep(20); + + return 0; +} + +static int isp4sd_pwron_and_init(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + int ret; + + if (ispif->status =3D=3D ISP4IF_STATUS_FW_RUNNING) { + dev_dbg(dev, "camera already opened, do nothing\n"); + return 0; + } + + isp4sd_module_enable(isp_subdev, true); + + isp_subdev->sensor_info.start_stream_cmd_sent =3D false; + isp_subdev->sensor_info.buf_sent_cnt =3D 0; + + if (ispif->status < ISP4IF_STATUS_PWR_ON) { + unsigned int perf_state =3D ISP4SD_PERFORMANCE_STATE_HIGH; + + ret =3D pm_runtime_resume_and_get(dev); + if (ret) { + dev_err(dev, "fail to power on isp_subdev ret %d\n", + ret); + goto err_deinit; + } + + /* ISPPG ISP Power Status */ + isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0x7FF); + ret =3D dev_pm_genpd_set_performance_state(dev, perf_state); + if (ret) { + dev_err(dev, + "fail to set performance state %u, ret %d\n", + perf_state, ret); + goto err_deinit; + } + + ispif->status =3D ISP4IF_STATUS_PWR_ON; + } + + isp_subdev->sensor_info.start_stream_cmd_sent =3D false; + isp_subdev->sensor_info.buf_sent_cnt =3D 0; + + ret =3D isp4if_start(ispif); + if (ret) { + dev_err(dev, "fail to start isp_subdev interface\n"); + goto err_deinit; + } + + if (isp4sd_start_resp_proc_threads(isp_subdev)) { + dev_err(dev, "isp_start_resp_proc_threads fail"); + goto err_deinit; + } + dev_dbg(dev, "create resp threads ok"); + + for (int i =3D 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + enable_irq(isp_subdev->irq[i]); + isp_subdev->irq_enabled =3D true; + + return 0; +err_deinit: + isp4sd_pwroff_and_deinit(isp_subdev); + return -EINVAL; +} + +static int isp4sd_stop_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info =3D + &isp_subdev->sensor_info.output_info; + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + int ret =3D 0; + + guard(mutex)(&isp_subdev->ops_mutex); + dev_dbg(dev, "status %i\n", output_info->start_status); + + if (output_info->start_status =3D=3D ISP4SD_START_STATUS_STARTED) { + struct isp4fw_cmd_enable_out_ch cmd_ch_disable; + + /* + * The struct will be shared with ISP FW, use memset() to guarantee + * padding bits are zeroed, since this is not guaranteed on all compiler= s. + */ + memset(&cmd_ch_disable, 0, sizeof(cmd_ch_disable)); + cmd_ch_disable.ch =3D ISP_PIPE_OUT_CH_PREVIEW; + /* `cmd_ch_disable.is_enable` is already false */ + ret =3D isp4if_send_command_sync(ispif, CMD_ID_ENABLE_OUT_CHAN, + &cmd_ch_disable, + sizeof(cmd_ch_disable)); + if (ret) + dev_err(dev, "fail to disable stream\n"); + else + dev_dbg(dev, "wait disable stream suc\n"); + + ret =3D isp4if_send_command_sync(ispif, CMD_ID_STOP_STREAM, + NULL, 0); + if (ret) + dev_err(dev, "fail to stop steam\n"); + else + dev_dbg(dev, "wait stop stream suc\n"); + } + + isp4if_clear_bufq(ispif); + + output_info->start_status =3D ISP4SD_START_STATUS_OFF; + isp4sd_reset_stream_info(isp_subdev, state, pad); + + isp4sd_uninit_stream(isp_subdev, state, pad); + + /* + * Return success to ensure the stop process proceeds, + * and disregard any errors since they are not fatal. + */ + return 0; +} + +static int isp4sd_start_stream(struct isp4_subdev *isp_subdev, + struct v4l2_subdev_state *state, u32 pad) +{ + struct isp4sd_output_info *output_info =3D + &isp_subdev->sensor_info.output_info; + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct device *dev =3D isp_subdev->dev; + int ret; + + guard(mutex)(&isp_subdev->ops_mutex); + + if (ispif->status !=3D ISP4IF_STATUS_FW_RUNNING) { + dev_err(dev, "fail, bad fsm %d", ispif->status); + return -EINVAL; + } + + switch (output_info->start_status) { + case ISP4SD_START_STATUS_OFF: + break; + case ISP4SD_START_STATUS_STARTED: + dev_dbg(dev, "stream already started, do nothing\n"); + return 0; + case ISP4SD_START_STATUS_START_FAIL: + dev_err(dev, "stream previously failed to start\n"); + return -EINVAL; + } + + ret =3D isp4sd_init_stream(isp_subdev); + if (ret) { + dev_err(dev, "fail to init isp_subdev stream\n"); + goto err_stop_stream; + } + + ret =3D isp4sd_setup_output(isp_subdev, state, pad); + if (ret) { + dev_err(dev, "fail to setup output\n"); + goto err_stop_stream; + } + + return 0; + +err_stop_stream: + isp4sd_stop_stream(isp_subdev, state, pad); + return ret; +} + +static int isp4sd_set_power(struct v4l2_subdev *sd, int on) +{ + struct isp4_subdev *isp_subdev =3D to_isp4_subdev(sd); + + guard(mutex)(&isp_subdev->ops_mutex); + if (on) + return isp4sd_pwron_and_init(isp_subdev); + else + return isp4sd_pwroff_and_deinit(isp_subdev); +} + +static const struct v4l2_subdev_core_ops isp4sd_core_ops =3D { + .s_power =3D isp4sd_set_power, +}; + +static const struct v4l2_subdev_video_ops isp4sd_video_ops =3D { + .s_stream =3D v4l2_subdev_s_stream_helper, +}; + +static int isp4sd_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct isp4sd_output_info *steam_info =3D + &(to_isp4_subdev(sd)->sensor_info.output_info); + struct v4l2_mbus_framefmt *format; + + format =3D v4l2_subdev_state_get_format(sd_state, fmt->pad); + + if (!format) { + dev_err(sd->dev, "fail to get state format\n"); + return -EINVAL; + } + + *format =3D fmt->format; + switch (format->code) { + case MEDIA_BUS_FMT_YUYV8_1_5X8: + steam_info->image_size =3D format->width * format->height * 3 / 2; + break; + case MEDIA_BUS_FMT_YUYV8_1X16: + steam_info->image_size =3D format->width * format->height * 2; + break; + default: + steam_info->image_size =3D 0; + break; + } + if (!steam_info->image_size) { + dev_err(sd->dev, + "fail set pad format,code 0x%x,width %u, height %u\n", + format->code, format->width, format->height); + return -EINVAL; + } + dev_dbg(sd->dev, + "set pad format suc, code:%x w:%u h:%u size:%u\n", format->code, + format->width, format->height, steam_info->image_size); + + return 0; +} + +static int isp4sd_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct isp4_subdev *isp_subdev =3D to_isp4_subdev(sd); + + return isp4sd_start_stream(isp_subdev, state, pad); +} + +static int isp4sd_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct isp4_subdev *isp_subdev =3D to_isp4_subdev(sd); + + return isp4sd_stop_stream(isp_subdev, state, pad); +} + +static const struct v4l2_subdev_pad_ops isp4sd_pad_ops =3D { + .get_fmt =3D v4l2_subdev_get_fmt, + .set_fmt =3D isp4sd_set_pad_format, + .enable_streams =3D isp4sd_enable_streams, + .disable_streams =3D isp4sd_disable_streams, +}; + +static const struct v4l2_subdev_ops isp4sd_subdev_ops =3D { + .core =3D &isp4sd_core_ops, + .video =3D &isp4sd_video_ops, + .pad =3D &isp4sd_pad_ops, +}; + +static int isp4sd_sdev_link_validate(struct media_link *link) +{ + return 0; +} + +static const struct media_entity_operations isp4sd_sdev_ent_ops =3D { + .link_validate =3D isp4sd_sdev_link_validate, +}; + +int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_d= ev, + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + struct isp4sd_sensor_info *sensor_info; + struct device *dev =3D v4l2_dev->dev; + int ret; + + isp_subdev->dev =3D dev; + v4l2_subdev_init(&isp_subdev->sdev, &isp4sd_subdev_ops); + isp_subdev->sdev.owner =3D THIS_MODULE; + isp_subdev->sdev.dev =3D dev; + snprintf(isp_subdev->sdev.name, sizeof(isp_subdev->sdev.name), "%s", + dev_name(dev)); + + isp_subdev->sdev.entity.name =3D isp4sd_entity_name; + isp_subdev->sdev.entity.function =3D MEDIA_ENT_F_PROC_VIDEO_ISP; + isp_subdev->sdev.entity.ops =3D &isp4sd_sdev_ent_ops; + isp_subdev->sdev_pad.flags =3D MEDIA_PAD_FL_SOURCE; + ret =3D media_entity_pads_init(&isp_subdev->sdev.entity, 1, + &isp_subdev->sdev_pad); + if (ret) { + dev_err(dev, "fail to init isp4 subdev entity pad %d\n", ret); + return ret; + } + ret =3D v4l2_subdev_init_finalize(&isp_subdev->sdev); + if (ret < 0) { + dev_err(dev, "fail to init finalize isp4 subdev %d\n", + ret); + return ret; + } + ret =3D v4l2_device_register_subdev(v4l2_dev, &isp_subdev->sdev); + if (ret) { + dev_err(dev, "fail to register isp4 subdev to V4L2 device %d\n", ret); + goto err_media_clean_up; + } + + sensor_info =3D &isp_subdev->sensor_info; + + isp4if_init(ispif, dev, isp_subdev->mmio); + + mutex_init(&isp_subdev->ops_mutex); + sensor_info->start_stream_cmd_sent =3D false; + sensor_info->status =3D ISP4SD_START_STATUS_OFF; + + /* create ISP enable gpio control */ + isp_subdev->enable_gpio =3D devm_gpiod_get(isp_subdev->dev, + "enable_isp", + GPIOD_OUT_LOW); + if (IS_ERR(isp_subdev->enable_gpio)) { + ret =3D PTR_ERR(isp_subdev->enable_gpio); + dev_err(dev, "fail to get gpiod %d\n", ret); + goto err_subdev_unreg; + } + + for (int i =3D 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) + isp_subdev->irq[i] =3D irq[i]; + + isp_subdev->host2fw_seq_num =3D 1; + ispif->status =3D ISP4IF_STATUS_PWR_OFF; + + return 0; + +err_subdev_unreg: + v4l2_device_unregister_subdev(&isp_subdev->sdev); +err_media_clean_up: + v4l2_subdev_cleanup(&isp_subdev->sdev); + media_entity_cleanup(&isp_subdev->sdev.entity); + return ret; +} + +void isp4sd_deinit(struct isp4_subdev *isp_subdev) +{ + struct isp4_interface *ispif =3D &isp_subdev->ispif; + + v4l2_device_unregister_subdev(&isp_subdev->sdev); + media_entity_cleanup(&isp_subdev->sdev.entity); + isp4if_deinit(ispif); + isp4sd_module_enable(isp_subdev, false); + + ispif->status =3D ISP4IF_STATUS_PWR_OFF; +} diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.h b/drivers/media/= platform/amd/isp4/isp4_subdev.h new file mode 100644 index 000000000000..c6c6a20d2957 --- /dev/null +++ b/drivers/media/platform/amd/isp4/isp4_subdev.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#ifndef _ISP4_SUBDEV_H_ +#define _ISP4_SUBDEV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "isp4_fw_cmd_resp.h" +#include "isp4_hw_reg.h" +#include "isp4_interface.h" + +/* + * one is for none sesnor specefic response which is not used now + * another is for sensor specific response + */ +#define ISP4SD_MAX_FW_RESP_STREAM_NUM 2 + +/* + * cmd used to register frame done callback, parameter is + * struct isp4sd_register_framedone_cb_param * + * when a image buffer is filled by ISP, ISP will call the registered call= back. + * callback func prototype is isp4sd_framedone_cb, cb_ctx can be anything + * provided by caller which will be provided back as the first parameter o= f the + * callback function. + * both cb_func and cb_ctx are provide by caller, set cb_func to NULL to + * unregister the callback + */ + +/* used to indicate the ISP status */ +enum isp4sd_status { + ISP4SD_STATUS_PWR_OFF, + ISP4SD_STATUS_PWR_ON, + ISP4SD_STATUS_FW_RUNNING, + ISP4SD_STATUS_MAX +}; + +/* used to indicate the status of sensor, output stream */ +enum isp4sd_start_status { + ISP4SD_START_STATUS_OFF, + ISP4SD_START_STATUS_STARTED, + ISP4SD_START_STATUS_START_FAIL, +}; + +struct isp4sd_img_buf_node { + struct list_head node; + struct isp4if_img_buf_info buf_info; +}; + +/* this is isp output after processing bayer raw input from sensor */ +struct isp4sd_output_info { + enum isp4sd_start_status start_status; + u32 image_size; +}; + +/* + * This struct represents the sensor info which is input or source of ISP, + * status is the sensor status + * output_info is the isp output info after ISP processing the sensor inpu= t, + * start_stream_cmd_sent mean if CMD_ID_START_STREAM has sent to fw. + * buf_sent_cnt is buffer count app has sent to receive the images + */ +struct isp4sd_sensor_info { + struct isp4sd_output_info output_info; + enum isp4sd_start_status status; + bool start_stream_cmd_sent; + u32 buf_sent_cnt; +}; + +/* + * Thread created by driver to receive fw response + * thread will be wakeup by fw to driver response interrupt + */ +struct isp4sd_thread_handler { + struct task_struct *thread; + wait_queue_head_t waitq; + bool resp_ready; +}; + +struct isp4_subdev_thread_param { + u32 idx; + struct isp4_subdev *isp_subdev; +}; + +struct isp4_subdev { + struct v4l2_subdev sdev; + struct isp4_interface ispif; + + struct media_pad sdev_pad; + + enum isp4sd_status isp_status; + struct mutex ops_mutex; /* ops_mutex */ + + /* Used to store fw cmds sent to FW whose response driver needs to wait f= or */ + struct isp4sd_thread_handler fw_resp_thread[ISP4SD_MAX_FW_RESP_STREAM_NUM= ]; + + u32 host2fw_seq_num; + + struct isp4sd_sensor_info sensor_info; + + /* gpio descriptor */ + struct gpio_desc *enable_gpio; + struct device *dev; + void __iomem *mmio; + struct isp4_subdev_thread_param isp_resp_para[ISP4SD_MAX_FW_RESP_STREAM_N= UM]; + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; + bool irq_enabled; + spinlock_t irq_lock; +}; + +int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_d= ev, + int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]); +void isp4sd_deinit(struct isp4_subdev *isp_subdev); + +#endif /* _ISP4_SUBDEV_H_ */ --=20 2.34.1