From patchwork Tue Oct 24 11:29:11 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737698 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D1349C07545 for ; Tue, 24 Oct 2023 11:20:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230343AbjJXLUS (ORCPT ); Tue, 24 Oct 2023 07:20:18 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57308 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230093AbjJXLUS (ORCPT ); Tue, 24 Oct 2023 07:20:18 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 2E82E109 for ; Tue, 24 Oct 2023 04:20:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146416; x=1729682416; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=Ge3oYny9s8+NqTCETKetopgx0iAqMSYemYu+CHW5Q5s=; b=fdVq5PYQM8z6orTI5jpzS977Jej6Ho9lY350Py8r18qJ8+87hPYxwXO+ CDbh29rk4ERJr32rTkEWfY3WuNnATSFX+gUndE03HQTbT5SKEcBg4Vj1c SgqWHwJOlHOf91CkxLT0lGHqeTSqifcZPORec01m6WZPvwxO5p6mA/ueU UbXoiwY3mLGocrOmhPYTpS53PJNwJyzn0r+UhVhdsKdRnU2VFjuKjhON3 g6OGKCNOvBwLA1HxNuyb5sTNoqCQ08hHQ969vqNSpEc2Y/Uyo2yVSDOOy TNEBc9KqByZfId/se9H3X2Zl6kEJBYz8OUcFd4JfDJqKLrXWtTVOAUlUi g==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258681" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258681" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:20:15 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069838" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069838" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:11 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 02/15] media: intel/ipu6: add IPU auxiliary devices Date: Tue, 24 Oct 2023 19:29:11 +0800 Message-ID: <20231024112924.3934228-3-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao Even the IPU input system and processing system are in a single PCI device, each system has its own power sequence, the processing system power up depends on the input system power up. Besides, input system and processing system have their own MMU hardware for IPU DMA address mapping. Register the IS/PS devices on auxiliary bus and attach power domain to implement the power sequence dependency. Signed-off-by: Bingbu Cao --- drivers/media/pci/intel/ipu6/ipu6-bus.c | 157 ++++++++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu6-bus.h | 50 ++++++++ drivers/media/pci/intel/ipu6/ipu6.c | 5 +- 3 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.h diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.c b/drivers/media/pci/intel/ipu6/ipu6-bus.c new file mode 100644 index 000000000000..eabeda5ee4c9 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-dma.h" + +static int bus_pm_runtime_suspend(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + int ret; + + ret = pm_generic_runtime_suspend(dev); + if (ret) + return ret; + + ret = ipu6_buttress_power(dev, adev->ctrl, false); + if (!ret) + return 0; + + dev_err(dev, "power down failed!\n"); + + /* Powering down failed, attempt to resume device now */ + ret = pm_generic_runtime_resume(dev); + if (!ret) + return -EBUSY; + + return -EIO; +} + +static int bus_pm_runtime_resume(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + int ret; + + ret = ipu6_buttress_power(dev, adev->ctrl, true); + if (ret) + return ret; + + ret = pm_generic_runtime_resume(dev); + if (ret) + goto out_err; + + return 0; + +out_err: + ipu6_buttress_power(dev, adev->ctrl, false); + + return -EBUSY; +} + +static struct dev_pm_domain ipu6_bus_pm_domain = { + .ops = { + .runtime_suspend = bus_pm_runtime_suspend, + .runtime_resume = bus_pm_runtime_resume, + }, +}; + +static DEFINE_MUTEX(ipu6_bus_mutex); + +static void ipu6_bus_release(struct device *dev) +{ + struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); + + kfree(adev->pdata); + kfree(adev); +} + +struct ipu6_bus_device * +ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, + void *pdata, struct ipu6_buttress_ctrl *ctrl, + char *name) +{ + struct auxiliary_device *auxdev; + struct ipu6_bus_device *adev; + struct ipu6_device *isp = pci_get_drvdata(pdev); + int ret; + + adev = kzalloc(sizeof(*adev), GFP_KERNEL); + if (!adev) + return ERR_PTR(-ENOMEM); + + adev->dma_mask = DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS : + IPU6_MMU_ADDR_BITS_NON_SECURE); + adev->isp = isp; + adev->ctrl = ctrl; + adev->pdata = pdata; + auxdev = &adev->auxdev; + auxdev->name = name; + auxdev->id = (pci_domain_nr(pdev->bus) << 16) | + PCI_DEVID(pdev->bus->number, pdev->devfn); + + auxdev->dev.parent = parent; + auxdev->dev.release = ipu6_bus_release; + auxdev->dev.dma_ops = &ipu6_dma_ops; + auxdev->dev.dma_mask = &adev->dma_mask; + auxdev->dev.dma_parms = pdev->dev.dma_parms; + auxdev->dev.coherent_dma_mask = adev->dma_mask; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n", + ret); + kfree(adev); + return ERR_PTR(ret); + } + + dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain); + + pm_runtime_forbid(&adev->auxdev.dev); + pm_runtime_enable(&adev->auxdev.dev); + + return adev; +} + +int ipu6_bus_add_device(struct ipu6_bus_device *adev) +{ + struct auxiliary_device *auxdev = &adev->auxdev; + int ret; + + ret = auxiliary_device_add(auxdev); + if (ret) { + auxiliary_device_uninit(auxdev); + return ret; + } + + mutex_lock(&ipu6_bus_mutex); + list_add(&adev->list, &adev->isp->devices); + mutex_unlock(&ipu6_bus_mutex); + + pm_runtime_allow(&auxdev->dev); + + return 0; +} + +void ipu6_bus_del_devices(struct pci_dev *pdev) +{ + struct ipu6_device *isp = pci_get_drvdata(pdev); + struct ipu6_bus_device *adev, *save; + + mutex_lock(&ipu6_bus_mutex); + + list_for_each_entry_safe(adev, save, &isp->devices, list) { + pm_runtime_disable(&adev->auxdev.dev); + list_del(&adev->list); + auxiliary_device_delete(&adev->auxdev); + auxiliary_device_uninit(&adev->auxdev); + } + + mutex_unlock(&ipu6_bus_mutex); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h new file mode 100644 index 000000000000..fa8bd73c3976 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2023 Intel Corporation */ + +#ifndef IPU6_BUS_H +#define IPU6_BUS_H + +#include +#include + +#define IPU6_BUS_NAME IPU6_NAME "-bus" + +struct ipu6_buttress_ctrl; + +struct ipu6_bus_device { + struct auxiliary_device auxdev; + struct auxiliary_driver *auxdrv; + const struct ipu6_auxdrv_data *auxdrv_data; + struct list_head list; + void *pdata; + struct ipu6_mmu *mmu; + struct ipu6_device *isp; + struct ipu6_buttress_ctrl *ctrl; + u64 dma_mask; + const struct firmware *fw; + struct sg_table fw_sgt; + u64 *pkg_dir; + dma_addr_t pkg_dir_dma_addr; + unsigned int pkg_dir_size; +}; + +struct ipu6_auxdrv_data { + irqreturn_t (*isr)(struct ipu6_bus_device *adev); + irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev); + bool wake_isr_thread; +}; + +#define to_ipu6_bus_device(_dev) container_of(to_auxiliary_dev(_dev), \ + struct ipu6_bus_device, auxdev) +#define auxdev_to_adev(_auxdev) container_of(_auxdev, \ + struct ipu6_bus_device, auxdev) +#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) + +struct ipu6_bus_device * +ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, + void *pdata, struct ipu6_buttress_ctrl *ctrl, + char *name); +int ipu6_bus_add_device(struct ipu6_bus_device *adev); +void ipu6_bus_del_devices(struct pci_dev *pdev); + +#endif diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c index 84960a283daf..b652662fa72e 100644 --- a/drivers/media/pci/intel/ipu6/ipu6.c +++ b/drivers/media/pci/intel/ipu6/ipu6.c @@ -666,7 +666,10 @@ static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) return dev_err_probe(&pdev->dev, ret, "Failed to set DMA mask\n"); - dma_set_max_seg_size(&pdev->dev, UINT_MAX); + ret = dma_set_max_seg_size(&pdev->dev, UINT_MAX); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to set max_seg_size\n"); ret = ipu6_pci_config_setup(pdev, isp->hw_ver); if (ret) From patchwork Tue Oct 24 11:29:12 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737697 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id A738BC07545 for ; Tue, 24 Oct 2023 11:20:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231139AbjJXLU0 (ORCPT ); Tue, 24 Oct 2023 07:20:26 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:57328 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230093AbjJXLUZ (ORCPT ); Tue, 24 Oct 2023 07:20:25 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id BDD72FE for ; Tue, 24 Oct 2023 04:20:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146420; x=1729682420; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=g5lJ1yjyzd4mDIHYj8sNAqOqfvt7pRsWkQULMu6Zgxw=; b=Z9pspLYf+7pX+i4kuhWjY8FChq9qm01S19fWfee2JFgpkTfxga2Zymms 0RzC7FiNM1EKGjjUIHephGXBRQwbITZDqjGuxV+b9opC6Yy3xKedQr0v8 PtiVIHvEHAyf1Nfu01IuGBbf66asoemEuHI/blKhcbAcmXcSLulE316wT LVFGgYT3WSmTdsRBdnRYuU9XWJXBn7GtrvdAbjcDdIkZ48l4RSKx26cLT n94/ssxTnbTyOgT2UGXqiWyzKYGiRCI5AIfTjPwQvvEa/cwr1txTcv89R c8urvAU1mXQCAKffObZGK/7xwiKLofm9nF8B42L0ca0F4W3O0nnxos5LF Q==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258692" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258692" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:20:19 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069846" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069846" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:15 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 03/15] media: intel/ipu6: add IPU6 buttress interface driver Date: Tue, 24 Oct 2023 19:29:12 +0800 Message-ID: <20231024112924.3934228-4-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao The IPU6 buttress is the interface between IPU device (input system and processing system) with rest of the SoC. It contains overall IPU hardware control registers, these control registers are used as the interfaces with the Intel Converged Security Engine and Punit to do firmware authentication and power management. Signed-off-by: Bingbu Cao --- drivers/media/pci/intel/ipu6/ipu6-buttress.c | 900 ++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu6-buttress.h | 107 +++ .../intel/ipu6/ipu6-platform-buttress-regs.h | 230 +++++ 3 files changed, 1237 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c new file mode 100644 index 000000000000..4a2db598b2b9 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c @@ -0,0 +1,900 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-buttress.h" +#include "ipu6-platform-buttress-regs.h" + +#define BOOTLOADER_STATUS_OFFSET 0x15c + +#define BOOTLOADER_MAGIC_KEY 0xb00710ad + +#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 +#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 +#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE + +#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10 + +#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC) + +#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC) +#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC) +#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC) +#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_RESET_RETRY 2000 +#define BUTTRESS_CSE_IPC_RESET_RETRY 4 +#define BUTTRESS_IPC_CMD_SEND_RETRY 1 + +#define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 + +static const u32 ipu6_adev_irq_mask[2] = { + BUTTRESS_ISR_IS_IRQ, + BUTTRESS_ISR_PS_IRQ +}; + +int ipu6_buttress_ipc_reset(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + unsigned int retries = BUTTRESS_IPC_RESET_RETRY; + struct ipu6_buttress *b = &isp->buttress; + u32 val = 0, csr_in_clr; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode"); + return 0; + } + + mutex_lock(&b->ipc_mutex); + + /* Clear-by-1 CSR (all bits), corresponding internal states. */ + val = readl(isp->base + ipc->csr_in); + writel(val, isp->base + ipc->csr_in); + + /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ + writel(ENTRY, isp->base + ipc->csr_out); + /* + * Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + */ + csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ | + BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID | + BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY; + + do { + usleep_range(400, 500); + val = readl(isp->base + ipc->csr_in); + switch (val) { + case ENTRY | EXIT: + case ENTRY | EXIT | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_COMP_ACTIONS_RST_PHASE2). + * 2) Set peer CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + */ + writel(ENTRY | EXIT, isp->base + ipc->csr_in); + writel(QUERY, isp->base + ipc->csr_out); + break; + case ENTRY: + case ENTRY | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE). + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1. + */ + writel(ENTRY | QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + case EXIT: + case EXIT | QUERY: + /* + * Clear-by-1 CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * 1) Clear incoming doorbell. + * 2) Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + * 3) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + */ + writel(EXIT, isp->base + ipc->csr_in); + writel(0, isp->base + ipc->db0_in); + writel(csr_in_clr, isp->base + ipc->csr_in); + writel(EXIT, isp->base + ipc->csr_out); + + /* + * Read csr_in again to make sure if RST_PHASE2 is done. + * If csr_in is QUERY, it should be handled again. + */ + usleep_range(200, 300); + val = readl(isp->base + ipc->csr_in); + if (val & QUERY) { + dev_dbg(&isp->pdev->dev, + "RST_PHASE2 retry csr_in = %x\n", val); + break; + } + mutex_unlock(&b->ipc_mutex); + return 0; + case QUERY: + /* + * 1) Clear-by-1 CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1 + */ + writel(QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + default: + dev_warn_ratelimited(&isp->pdev->dev, + "Unexpected CSR 0x%x\n", val); + break; + } + } while (retries--); + + mutex_unlock(&b->ipc_mutex); + dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n"); + + return -ETIMEDOUT; +} + +static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); +} + +static int +ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc) +{ + unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; + void __iomem *addr; + int ret; + u32 val; + + writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); + + addr = isp->base + ipc->csr_in; + ret = readl_poll_timeout(addr, val, val & mask, 200, + BUTTRESS_IPC_VALIDITY_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); + ipu6_buttress_ipc_validity_close(isp, ipc); + } + + return ret; +} + +static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) +{ + if (ipc_msg) + *ipc_msg = readl(isp->base + ipc->data0_in); + writel(0, isp->base + ipc->db0_in); +} + +static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, + enum ipu6_buttress_ipc_domain ipc_domain, + struct ipu6_ipc_buttress_bulk_msg *msgs, + u32 size) +{ + unsigned long tx_timeout_jiffies, rx_timeout_jiffies; + unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; + struct ipu6_buttress *b = &isp->buttress; + struct ipu6_buttress_ipc *ipc; + u32 val; + int ret; + int tout; + + ipc = ipc_domain == IPU6_BUTTRESS_IPC_CSE ? &b->cse : &b->ish; + + mutex_lock(&b->ipc_mutex); + + ret = ipu6_buttress_ipc_validity_open(isp, ipc); + if (ret) { + dev_err(&isp->pdev->dev, "IPC validity open failed\n"); + goto out; + } + + tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS); + rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS); + + for (i = 0; i < size; i++) { + reinit_completion(&ipc->send_complete); + if (msgs[i].require_resp) + reinit_completion(&ipc->recv_complete); + + dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", + msgs[i].cmd); + writel(msgs[i].cmd, isp->base + ipc->data0_out); + val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; + writel(val, isp->base + ipc->db0_out); + + tout = wait_for_completion_timeout(&ipc->send_complete, + tx_timeout_jiffies); + if (!tout) { + dev_err(&isp->pdev->dev, "send IPC response timeout\n"); + if (!retry--) { + ret = -ETIMEDOUT; + goto out; + } + + /* Try again if CSE is not responding on first try */ + writel(0, isp->base + ipc->db0_out); + i--; + continue; + } + + retry = BUTTRESS_IPC_CMD_SEND_RETRY; + + if (!msgs[i].require_resp) + continue; + + tout = wait_for_completion_timeout(&ipc->recv_complete, + rx_timeout_jiffies); + if (!tout) { + dev_err(&isp->pdev->dev, "recv IPC response timeout\n"); + ret = -ETIMEDOUT; + goto out; + } + + if (ipc->nack_mask && + (ipc->recv_data & ipc->nack_mask) == ipc->nack) { + dev_err(&isp->pdev->dev, + "IPC NACK for cmd 0x%x\n", msgs[i].cmd); + ret = -EIO; + goto out; + } + + if (ipc->recv_data != msgs[i].expected_resp) { + dev_err(&isp->pdev->dev, + "expected resp: 0x%x, IPC response: 0x%x ", + msgs[i].expected_resp, ipc->recv_data); + ret = -EIO; + goto out; + } + } + + dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); + +out: + ipu6_buttress_ipc_validity_close(isp, ipc); + mutex_unlock(&b->ipc_mutex); + return ret; +} + +static int +ipu6_buttress_ipc_send(struct ipu6_device *isp, + enum ipu6_buttress_ipc_domain ipc_domain, + u32 ipc_msg, u32 size, bool require_resp, + u32 expected_resp) +{ + struct ipu6_ipc_buttress_bulk_msg msg = { + .cmd = ipc_msg, + .cmd_size = size, + .require_resp = require_resp, + .expected_resp = expected_resp, + }; + + return ipu6_buttress_ipc_send_bulk(isp, ipc_domain, &msg, 1); +} + +static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev) +{ + irqreturn_t ret = IRQ_WAKE_THREAD; + + if (!adev || !adev->auxdrv || !adev->auxdrv_data) + return IRQ_NONE; + + if (adev->auxdrv_data->isr) + ret = adev->auxdrv_data->isr(adev); + + if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded) + ret = IRQ_NONE; + + return ret; +} + +irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) +{ + struct ipu6_device *isp = isp_ptr; + struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; + struct ipu6_buttress *b = &isp->buttress; + u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; + irqreturn_t ret = IRQ_NONE; + u32 disable_irqs = 0; + u32 irq_status; + u32 i, count = 0; + + pm_runtime_get_noresume(&isp->pdev->dev); + + irq_status = readl(isp->base + reg_irq_sts); + if (!irq_status) { + pm_runtime_put_noidle(&isp->pdev->dev); + return IRQ_NONE; + } + + do { + writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); + + for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { + irqreturn_t r = ipu6_buttress_call_isr(adev[i]); + + if (!(irq_status & ipu6_adev_irq_mask[i])) + continue; + + if (r == IRQ_WAKE_THREAD) { + ret = IRQ_WAKE_THREAD; + disable_irqs |= ipu6_adev_irq_mask[i]; + } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { + ret = IRQ_HANDLED; + } + } + + if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) + ret = IRQ_HANDLED; + + if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); + ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); + complete(&b->cse.recv_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING\n"); + ipu6_buttress_ipc_recv(isp, &b->ish, &b->ish.recv_data); + complete(&b->ish.recv_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->cse.send_complete); + } + + if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH) { + dev_dbg(&isp->pdev->dev, + "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->ish.send_complete); + } + + if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && + ipu6_buttress_get_secure_mode(isp)) + dev_err(&isp->pdev->dev, + "BUTTRESS_ISR_SAI_VIOLATION\n"); + + if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR | + BUTTRESS_ISR_PS_FATAL_MEM_ERR)) + dev_err(&isp->pdev->dev, + "BUTTRESS_ISR_FATAL_MEM_ERR\n"); + + if (irq_status & BUTTRESS_ISR_UFI_ERROR) + dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n"); + + if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) { + dev_err(&isp->pdev->dev, "too many consecutive IRQs\n"); + ret = IRQ_NONE; + break; + } + + irq_status = readl(isp->base + reg_irq_sts); + } while (irq_status); + + if (disable_irqs) + writel(BUTTRESS_IRQS & ~disable_irqs, + isp->base + BUTTRESS_REG_ISR_ENABLE); + + pm_runtime_put(&isp->pdev->dev); + + return ret; +} + +irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) +{ + struct ipu6_device *isp = isp_ptr; + struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; + const struct ipu6_auxdrv_data *drv_data = NULL; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { + drv_data = adev[i]->auxdrv_data; + if (!drv_data) + continue; + + if (drv_data->wake_isr_thread && + drv_data->isr_threaded(adev[i]) == IRQ_HANDLED) + ret = IRQ_HANDLED; + } + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + + return ret; +} + +int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, + bool on) +{ + struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; + u32 pwr_sts, val; + int ret; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + if (!on) { + val = 0; + pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + } else { + val = BUTTRESS_FREQ_CTL_START | + FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, + ctrl->ratio) | + FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, + ctrl->qos_floor) | + BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; + + pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + } + + writel(val, isp->base + ctrl->freq_ctl); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, + val, (val & ctrl->pwr_sts_mask) == pwr_sts, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, + "Change power status timeout with 0x%x\n", val); + + ctrl->started = !ret && on; + + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) +{ + u32 val; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + + return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; +} + +bool ipu6_buttress_auth_done(struct ipu6_device *isp) +{ + u32 val; + + if (!isp->secure_mode) + return true; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); + + return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); + +int ipu6_buttress_reset_authentication(struct ipu6_device *isp) +{ + int ret; + u32 val; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); + return 0; + } + + writel(BUTTRESS_FW_RESET_CTL_START, isp->base + + BUTTRESS_REG_FW_RESET_CTL); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, + val & BUTTRESS_FW_RESET_CTL_DONE, 500, + BUTTRESS_CSE_FWRESET_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, + "Time out while resetting authentication state\n"); + return ret; + } + + dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); + writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); + /* leave some time for HW restore */ + usleep_range(800, 1000); + + return 0; +} + +int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, + const struct firmware *fw, struct sg_table *sgt) +{ + struct page **pages; + const void *addr; + unsigned long n_pages; + unsigned int i; + int ret; + + n_pages = PHYS_PFN(PAGE_ALIGN(fw->size)); + + pages = kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) + return -ENOMEM; + + addr = fw->data; + for (i = 0; i < n_pages; i++) { + struct page *p = vmalloc_to_page(addr); + + if (!p) { + ret = -ENOMEM; + goto out; + } + pages[i] = p; + addr += PAGE_SIZE; + } + + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, + GFP_KERNEL); + if (ret) { + ret = -ENOMEM; + goto out; + } + + ret = dma_map_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); + if (ret < 0) { + ret = -ENOMEM; + sg_free_table(sgt); + goto out; + } + + dma_sync_sgtable_for_device(&sys->auxdev.dev, sgt, DMA_TO_DEVICE); + +out: + kfree(pages); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); + +void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, + struct sg_table *sgt) +{ + dma_unmap_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); + sg_free_table(sgt); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); + +int ipu6_buttress_authenticate(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + struct ipu6_psys_pdata *psys_pdata; + u32 data, mask, done, fail; + int ret; + + if (!isp->secure_mode) { + dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); + return 0; + } + + psys_pdata = isp->psys->pdata; + + mutex_lock(&b->auth_mutex); + + if (ipu6_buttress_auth_done(isp)) { + ret = 0; + goto out_unlock; + } + + /* + * Write address of FIT table to FW_SOURCE register + * Let's use fw address. I.e. not using FIT table yet + */ + data = lower_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); + + data = upper_32_bits(isp->psys->pkg_dir_dma_addr); + writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); + + /* + * Write boot_load into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n"); + + ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, + BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); + if (ret) { + dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); + goto out_unlock; + } + + mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; + done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; + fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE boot_load timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(&isp->pdev->dev, "CSE auth failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, + data, data == BOOTLOADER_MAGIC_KEY, 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", + data); + goto out_unlock; + } + + /* + * Write authenticate_run into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n"); + ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, + BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); + if (ret) { + dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n"); + goto out_unlock; + } + + done = BUTTRESS_SECURITY_CTL_AUTH_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE authenticate timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + dev_info(&isp->pdev->dev, "CSE authenticate_run done\n"); + +out_unlock: + mutex_unlock(&b->auth_mutex); + + return ret; +} + +static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) +{ + u32 val, mask, done; + int ret; + + mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; + + writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, + isp->base + BUTTRESS_REG_FABRIC_CMD); + + val = readl(isp->base + BUTTRESS_REG_PWR_STATE); + val = FIELD_GET(mask, val); + if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { + dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); + return -EINVAL; + } + + done = BUTTRESS_PWR_STATE_HH_STATE_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, + FIELD_GET(mask, val) == done, 500, + BUTTRESS_TSC_SYNC_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); + + return ret; +} + +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) +{ + unsigned int i; + + for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + u32 val; + int ret; + + ret = ipu6_buttress_send_tsc_request(isp); + if (ret != -ETIMEDOUT) + return ret; + + val = readl(isp->base + BUTTRESS_REG_TSW_CTL); + val = val | BUTTRESS_TSW_CTL_SOFT_RESET; + writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; + writel(val, isp->base + BUTTRESS_REG_TSW_CTL); + } + + dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); + +void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) +{ + u32 tsc_hi_1, tsc_hi_2, tsc_lo; + unsigned long flags; + + local_irq_save(flags); + tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); + tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); + tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); + if (tsc_hi_1 == tsc_hi_2) { + *val = (u64)tsc_hi_1 << 32 | tsc_lo; + } else { + /* Check if TSC has rolled over */ + if (tsc_lo & BIT(31)) + *val = (u64)tsc_hi_1 << 32 | tsc_lo; + else + *val = (u64)tsc_hi_2 << 32 | tsc_lo; + } + local_irq_restore(flags); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); + +u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) +{ + u64 ns = ticks * 10000; + + /* + * converting TSC tick count to ns is calculated by: + * Example (TSC clock frequency is 19.2MHz): + * ns = ticks * 1000 000 000 / 19.2Mhz + * = ticks * 1000 000 000 / 19200000Hz + * = ticks * 10000 / 192 ns + */ + return div_u64(ns, isp->buttress.ref_clk); +} +EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); + +void ipu6_buttress_restore(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); +} + +int ipu6_buttress_init(struct ipu6_device *isp) +{ + int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY; + struct ipu6_buttress *b = &isp->buttress; + u32 val; + + mutex_init(&b->power_mutex); + mutex_init(&b->auth_mutex); + mutex_init(&b->cons_mutex); + mutex_init(&b->ipc_mutex); + init_completion(&b->ish.send_complete); + init_completion(&b->cse.send_complete); + init_completion(&b->ish.recv_complete); + init_completion(&b->cse.recv_complete); + + b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; + b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; + b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; + b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; + b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; + b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; + b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; + b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + + /* no ISH on IPU6 */ + memset(&b->ish, 0, sizeof(b->ish)); + INIT_LIST_HEAD(&b->constraints); + + isp->secure_mode = ipu6_buttress_get_secure_mode(isp); + dev_info(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", + isp->secure_mode ? "secure" : "non-secure", + readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), + readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); + + b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); + + /* get ref_clk frequency by reading the indication in btrs control */ + val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); + val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); + + switch (val) { + case 0x0: + b->ref_clk = 240; + break; + case 0x1: + b->ref_clk = 192; + break; + case 0x2: + b->ref_clk = 384; + break; + default: + dev_warn(&isp->pdev->dev, + "Unsupported ref clock, use 19.2Mhz by default.\n"); + b->ref_clk = 192; + break; + } + + /* Retry couple of times in case of CSE initialization is delayed */ + do { + ret = ipu6_buttress_ipc_reset(isp, &b->cse); + if (ret) { + dev_warn(&isp->pdev->dev, + "IPC reset protocol failed, retrying\n"); + } else { + dev_dbg(&isp->pdev->dev, "IPC reset done\n"); + return 0; + } + } while (ipc_reset_retry--); + + dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); + + return ret; +} + +void ipu6_buttress_exit(struct ipu6_device *isp) +{ + struct ipu6_buttress *b = &isp->buttress; + + writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h new file mode 100644 index 000000000000..8a62a2e0d661 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2023 Intel Corporation */ + +#ifndef IPU6_BUTTRESS_H +#define IPU6_BUTTRESS_H + +#include + +struct firmware; +struct ipu6_device; +struct ipu6_bus_device; + +#define IPU6_BUTTRESS_NUM_OF_SENS_CKS 3 +#define IPU6_BUTTRESS_NUM_OF_PLL_CKS 3 + +#define BUTTRESS_PS_FREQ_STEP 25U +#define BUTTRESS_MIN_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 8) +#define BUTTRESS_MAX_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 32) + +#define BUTTRESS_IS_FREQ_STEP 25U +#define BUTTRESS_MIN_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 8) +#define BUTTRESS_MAX_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 16) + +struct ipu6_buttress_ctrl { + u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; + unsigned int ratio; + unsigned int qos_floor; + bool started; +}; + +struct ipu6_buttress_fused_freqs { + unsigned int min_freq; + unsigned int max_freq; + unsigned int efficient_freq; +}; + +struct ipu6_buttress_ipc { + struct completion send_complete; + struct completion recv_complete; + u32 nack; + u32 nack_mask; + u32 recv_data; + u32 csr_out; + u32 csr_in; + u32 db0_in; + u32 db0_out; + u32 data0_out; + u32 data0_in; +}; + +struct ipu6_buttress { + struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; + struct ipu6_buttress_ipc cse; + struct ipu6_buttress_ipc ish; + struct list_head constraints; + u32 wdt_cached_value; + bool force_suspend; + u32 ref_clk; +}; + +struct ipu6_buttress_sensor_clk_freq { + unsigned int rate; + unsigned int val; +}; + +enum ipu6_buttress_ipc_domain { + IPU6_BUTTRESS_IPC_CSE, + IPU6_BUTTRESS_IPC_ISH, +}; + +struct ipu6_buttress_constraint { + struct list_head list; + unsigned int min_freq; +}; + +struct ipu6_ipc_buttress_bulk_msg { + u32 cmd; + u32 expected_resp; + bool require_resp; + u8 cmd_size; +}; + +int ipu6_buttress_ipc_reset(struct ipu6_device *isp, + struct ipu6_buttress_ipc *ipc); +int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, + const struct firmware *fw, + struct sg_table *sgt); +void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, + struct sg_table *sgt); +int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, + bool on); +bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp); +int ipu6_buttress_authenticate(struct ipu6_device *isp); +int ipu6_buttress_reset_authentication(struct ipu6_device *isp); +bool ipu6_buttress_auth_done(struct ipu6_device *isp); +int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp); +void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val); +u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp); + +irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr); +irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr); +int ipu6_buttress_init(struct ipu6_device *isp); +void ipu6_buttress_exit(struct ipu6_device *isp); +void ipu6_buttress_csi_port_config(struct ipu6_device *isp, + u32 legacy, u32 combo); +void ipu6_buttress_restore(struct ipu6_device *isp); +#endif /* IPU6_BUTTRESS_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h new file mode 100644 index 000000000000..5d31951b7253 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h @@ -0,0 +1,230 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2023 Intel Corporation */ + +#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H +#define IPU6_PLATFORM_BUTTRESS_REGS_H + +/* IS_WORKPOINT_REQ */ +#define IPU6_BUTTRESS_REG_IS_FREQ_CTL 0x34 +/* PS_WORKPOINT_REQ */ +#define IPU6_BUTTRESS_REG_PS_FREQ_CTL 0x38 + +#define IPU6_IS_FREQ_MAX 533 +#define IPU6_IS_FREQ_MIN 200 +#define IPU6_PS_FREQ_MAX 450 +#define IPU6_IS_FREQ_RATIO_BASE 25 +#define IPU6_PS_FREQ_RATIO_BASE 25 + +/* should be tuned for real silicon */ +#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO 0x08 +#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO 0x0a +#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO 0x0d + +#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x10 +#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x0708 + +#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3 +#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK GENMASK(4, 3) + +#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6 +#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK GENMASK(7, 6) + +#define IPU6_BUTTRESS_PWR_STATE_DN_DONE 0x0 +#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS 0x1 +#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS 0x2 +#define IPU6_BUTTRESS_PWR_STATE_UP_DONE 0x3 + +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0 0x270 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1 0x274 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2 0x278 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3 0x27c +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4 0x280 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5 0x284 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6 0x288 +#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7 0x28c + +#define BUTTRESS_REG_WDT 0x8 +#define BUTTRESS_REG_BTRS_CTRL 0xc +#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0 BIT(0) +#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1 BIT(1) +#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND GENMASK(9, 8) + +#define BUTTRESS_REG_FW_RESET_CTL 0x30 +#define BUTTRESS_FW_RESET_CTL_START BIT(0) +#define BUTTRESS_FW_RESET_CTL_DONE BIT(1) + +#define BUTTRESS_REG_IS_FREQ_CTL 0x34 +#define BUTTRESS_REG_PS_FREQ_CTL 0x38 + +#define BUTTRESS_FREQ_CTL_START BIT(31) +#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL GENMASK(19, 16) +#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK GENMASK(15, 8) +#define BUTTRESS_FREQ_CTL_RATIO_MASK GENMASK(7, 0) + +#define BUTTRESS_REG_PWR_STATE 0x5c + +#define BUTTRESS_PWR_STATE_RESET 0x0 +#define BUTTRESS_PWR_STATE_PWR_ON_DONE 0x1 +#define BUTTRESS_PWR_STATE_PWR_RDY 0x3 +#define BUTTRESS_PWR_STATE_PWR_IDLE 0x4 + +#define BUTTRESS_PWR_STATE_HH_STATUS_MASK GENMASK(12, 11) + +enum { + BUTTRESS_PWR_STATE_HH_STATE_IDLE, + BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS, + BUTTRESS_PWR_STATE_HH_STATE_DONE, + BUTTRESS_PWR_STATE_HH_STATE_ERR, +}; + +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK GENMASK(23, 19) + +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE 0x0 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP 0x1 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK 0x2 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK 0x3 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES 0x4 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1 0x5 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2 0x6 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES 0x7 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP 0x8 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT 0x9 +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY 0xa +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED 0xb +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3 0xc +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD 0xd +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT 0xe +#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0 0xf + +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK GENMASK(28, 24) + +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE 0x0 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY 0x1 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH 0x2 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD 0x3 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH 0x4 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO 0x5 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP 0x6 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK 0x7 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES 0x8 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1 0x9 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2 0xa +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES 0xb +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT 0xc +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT 0xd +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP 0xf +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED 0x10 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3 0x11 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK 0x12 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND 0x13 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4 0x14 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP 0x15 +#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK 0x16 + +#define BUTTRESS_REG_SECURITY_CTL 0x300 +#define BUTTRESS_REG_SKU 0x314 +#define BUTTRESS_REG_SECURITY_TOUCH 0x318 +#define BUTTRESS_REG_CAMERA_MASK 0x84 + +#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16) +#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0) + +#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0) +#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1) +#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3) + +#define BUTTRESS_REG_FW_SOURCE_BASE_LO 0x78 +#define BUTTRESS_REG_FW_SOURCE_BASE_HI 0x7C +#define BUTTRESS_REG_FW_SOURCE_SIZE 0x80 + +#define BUTTRESS_REG_ISR_STATUS 0x90 +#define BUTTRESS_REG_ISR_ENABLED_STATUS 0x94 +#define BUTTRESS_REG_ISR_ENABLE 0x98 +#define BUTTRESS_REG_ISR_CLEAR 0x9C + +#define BUTTRESS_ISR_IS_IRQ BIT(0) +#define BUTTRESS_ISR_PS_IRQ BIT(1) +#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE BIT(2) +#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH BIT(3) +#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING BIT(4) +#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING BIT(5) +#define BUTTRESS_ISR_CSE_CSR_SET BIT(6) +#define BUTTRESS_ISR_ISH_CSR_SET BIT(7) +#define BUTTRESS_ISR_SPURIOUS_CMP BIT(8) +#define BUTTRESS_ISR_WATCHDOG_EXPIRED BIT(9) +#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ BIT(10) +#define BUTTRESS_ISR_SAI_VIOLATION BIT(11) +#define BUTTRESS_ISR_HW_ASSERTION BIT(12) +#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR BIT(13) +#define BUTTRESS_ISR_IS_FATAL_MEM_ERR BIT(14) +#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR BIT(15) +#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR BIT(16) +#define BUTTRESS_ISR_PS_FATAL_MEM_ERR BIT(17) +#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR BIT(18) +#define BUTTRESS_ISR_PS_FAST_THROTTLE BIT(19) +#define BUTTRESS_ISR_UFI_ERROR BIT(20) + +#define BUTTRESS_REG_IU2CSEDB0 0x100 + +#define BUTTRESS_IU2CSEDB0_BUSY BIT(31) +#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2 + +#define BUTTRESS_REG_IU2CSEDATA0 0x104 + +#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1 +#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2 +#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3 +#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16 + +#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0) +#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1) +#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2) +#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4) + +#define BUTTRESS_REG_IU2CSECSR 0x108 + +#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0) +#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1) +#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2) +#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3) +#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4) +#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5) + +#define BUTTRESS_REG_CSE2IUDB0 0x304 +#define BUTTRESS_REG_CSE2IUCSR 0x30C +#define BUTTRESS_REG_CSE2IUDATA0 0x308 + +/* 0x20 == NACK, 0xf == unknown command */ +#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20 +#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0) + +#define BUTTRESS_REG_ISH2IUCSR 0x50 +#define BUTTRESS_REG_ISH2IUDB0 0x54 +#define BUTTRESS_REG_ISH2IUDATA0 0x58 + +#define BUTTRESS_REG_IU2ISHDB0 0x10C +#define BUTTRESS_REG_IU2ISHDATA0 0x110 +#define BUTTRESS_REG_IU2ISHDATA1 0x114 +#define BUTTRESS_REG_IU2ISHCSR 0x118 + +#define BUTTRESS_REG_FABRIC_CMD 0x88 + +#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC BIT(0) +#define BUTTRESS_FABRIC_CMD_IS_DRAIN BIT(4) + +#define BUTTRESS_REG_TSW_CTL 0x120 +#define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8) + +#define BUTTRESS_REG_TSC_LO 0x164 +#define BUTTRESS_REG_TSC_HI 0x168 + +#define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ + BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ + BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ) + +#define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ + BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING | \ + BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ + BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH | \ + BUTTRESS_ISR_SAI_VIOLATION) +#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ From patchwork Tue Oct 24 11:29:15 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737696 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8B1E9C07545 for ; Tue, 24 Oct 2023 11:20:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231150AbjJXLUi (ORCPT ); Tue, 24 Oct 2023 07:20:38 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51608 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231176AbjJXLUh (ORCPT ); Tue, 24 Oct 2023 07:20:37 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 64C4DD7E for ; Tue, 24 Oct 2023 04:20:32 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146432; x=1729682432; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=bbMS9uF21553CTT3PRkSilhlPZF1jlDvAJdofX8EvCQ=; b=eTindB/fKi9rHvJrmKs9n9lRQwwcenqQR7QpjIk5znjZZ5x248VSBuD3 0bo00DiwV59PcI07Juw3UJhmF8t9FAhK0OA/XofFWwg3l4R7949BDMNAS dlsiMpJyf/ZnIyjP4BTE+iLNPWJvzi1nzhEcI7E4DT9TyOnu2urPkAEAQ OdMwhlDAB/98vfy6558Ux6rPf6kHgsvpy8y1nvhbXLK+z7spFkdc9a07T aY+5Cj8ccx/NRnIpweLcjzXfmH/b2BA404WskxKcPEcGUC5jlTzES/cI/ OCAeNwcL+vi955Gj4aH16KDvyWjqK9BFJ11Lernaoac3BnMYkIneb6Dng g==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258730" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258730" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:20:31 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069870" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069870" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:28 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 06/15] media: intel/ipu6: add syscom interfaces between firmware and driver Date: Tue, 24 Oct 2023 19:29:15 +0800 Message-ID: <20231024112924.3934228-7-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao Syscom is an inter-process(or) communication mechanism between an IPU and host. Syscom uses message queues for message exchange between IPU and host. Each message queue has its consumer and producer, host queue messages to firmware as the producer and then firmware to dequeue the messages as consumer and vice versa. IPU and host use shared registers or memory to reside the read and write indices which are updated by consumer and producer. Signed-off-by: Bingbu Cao --- drivers/media/pci/intel/ipu6/ipu6-fw-com.c | 411 +++++++++++++++++++++ drivers/media/pci/intel/ipu6/ipu6-fw-com.h | 47 +++ 2 files changed, 458 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.h diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.c b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c new file mode 100644 index 000000000000..a162cc657234 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include "ipu6.h" +#include "ipu6-bus.h" +#include "ipu6-fw-com.h" + +/* + * FWCOM layer is a shared resource between FW and driver. It consist + * of token queues to both send and receive directions. Queue is simply + * an array of structures with read and write indexes to the queue. + * There are 1...n queues to both directions. Queues locates in + * system RAM and are mapped to ISP MMU so that both CPU and ISP can + * see the same buffer. Indexes are located in ISP DMEM so that FW code + * can poll those with very low latency and cost. CPU access to indexes is + * more costly but that happens only at message sending time and + * interrupt triggered message handling. CPU doesn't need to poll indexes. + * wr_reg / rd_reg are offsets to those dmem location. They are not + * the indexes itself. + */ + +/* Shared structure between driver and FW - do not modify */ +struct ipu6_fw_sys_queue { + u64 host_address; + u32 vied_address; + u32 size; + u32 token_size; + u32 wr_reg; /* reg number in subsystem's regmem */ + u32 rd_reg; + u32 _align; +} __packed; + +struct ipu6_fw_sys_queue_res { + u64 host_address; + u32 vied_address; + u32 reg; +} __packed; + +enum syscom_state { + /* Program load or explicit host setting should init to this */ + SYSCOM_STATE_UNINIT = 0x57A7E000, + /* SP Syscom sets this when it is ready for use */ + SYSCOM_STATE_READY = 0x57A7E001, + /* SP Syscom sets this when no more syscom accesses will happen */ + SYSCOM_STATE_INACTIVE = 0x57A7E002 +}; + +enum syscom_cmd { + /* Program load or explicit host setting should init to this */ + SYSCOM_COMMAND_UNINIT = 0x57A7F000, + /* Host Syscom requests syscom to become inactive */ + SYSCOM_COMMAND_INACTIVE = 0x57A7F001 +}; + +/* firmware config: data that sent from the host to SP via DDR */ +/* Cell copies data into a context */ + +struct ipu6_fw_syscom_config { + u32 firmware_address; + + u32 num_input_queues; + u32 num_output_queues; + + /* ISP pointers to an array of ipu6_fw_sys_queue structures */ + u32 input_queue; + u32 output_queue; + + /* ISYS / PSYS private data */ + u32 specific_addr; + u32 specific_size; +}; + +struct ipu6_fw_com_context { + struct ipu6_bus_device *adev; + void __iomem *dmem_addr; + int (*cell_ready)(struct ipu6_bus_device *adev); + void (*cell_start)(struct ipu6_bus_device *adev); + + void *dma_buffer; + dma_addr_t dma_addr; + unsigned int dma_size; + unsigned long attrs; + + struct ipu6_fw_sys_queue *input_queue; /* array of host to SP queues */ + struct ipu6_fw_sys_queue *output_queue; /* array of SP to host */ + + u32 config_vied_addr; + + unsigned int buttress_boot_offset; + void __iomem *base_addr; +}; + +#define FW_COM_WR_REG 0 +#define FW_COM_RD_REG 4 + +#define REGMEM_OFFSET 0 +#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a + +enum regmem_id { + /* pass pkg_dir address to SPC in non-secure mode */ + PKG_DIR_ADDR_REG = 0, + /* Tunit CFG blob for secure - provided by host.*/ + TUNIT_CFG_DWR_REG = 1, + /* syscom commands - modified by the host */ + SYSCOM_COMMAND_REG = 2, + /* Store interrupt status - updated by SP */ + SYSCOM_IRQ_REG = 3, + /* first syscom queue pointer register */ + SYSCOM_QPR_BASE_REG = 4 +}; + +enum message_direction { + DIR_RECV = 0, + DIR_SEND +}; + +#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000 +#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id) \ + ((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4) + +enum buttress_syscom_id { + /* pass syscom configuration to SPC */ + SYSCOM_CONFIG_ID = 0, + /* syscom state - modified by SP */ + SYSCOM_STATE_ID = 1, + /* syscom vtl0 addr mask */ + SYSCOM_VTL0_ADDR_MASK_ID = 2, + SYSCOM_ID_MAX +}; + +static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size, + unsigned int token_size, + struct ipu6_fw_sys_queue_res *res) +{ + unsigned int buf_size = (size + 1) * token_size; + + q->size = size + 1; + q->token_size = token_size; + + /* acquire the shared buffer space */ + q->host_address = res->host_address; + res->host_address += buf_size; + q->vied_address = res->vied_address; + res->vied_address += buf_size; + + /* acquire the shared read and writer pointers */ + q->wr_reg = res->reg; + res->reg++; + q->rd_reg = res->reg; + res->reg++; +} + +void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, + struct ipu6_bus_device *adev, void __iomem *base) +{ + size_t conf_size, inq_size, outq_size, specific_size; + struct ipu6_fw_syscom_config *config_host_addr; + unsigned int sizeinput = 0, sizeoutput = 0; + struct ipu6_fw_sys_queue_res res; + struct ipu6_fw_com_context *ctx; + struct device *dev = &adev->auxdev.dev; + size_t sizeall, offset; + unsigned long attrs = 0; + void *specific_host_addr; + unsigned int i; + + if (!cfg || !cfg->cell_start || !cfg->cell_ready) + return NULL; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET; + ctx->adev = adev; + ctx->cell_start = cfg->cell_start; + ctx->cell_ready = cfg->cell_ready; + ctx->buttress_boot_offset = cfg->buttress_boot_offset; + ctx->base_addr = base; + + /* + * Allocate DMA mapped memory. Allocate one big chunk. + */ + /* Base cfg for FW */ + conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8); + /* Descriptions of the queues */ + inq_size = size_mul(cfg->num_input_queues, + sizeof(struct ipu6_fw_sys_queue)); + outq_size = size_mul(cfg->num_output_queues, + sizeof(struct ipu6_fw_sys_queue)); + /* FW specific information structure */ + specific_size = roundup(cfg->specific_size, 8); + + sizeall = conf_size + inq_size + outq_size + specific_size; + + for (i = 0; i < cfg->num_input_queues; i++) + sizeinput += size_mul(cfg->input[i].queue_size + 1, + cfg->input[i].token_size); + + for (i = 0; i < cfg->num_output_queues; i++) + sizeoutput += size_mul(cfg->output[i].queue_size + 1, + cfg->output[i].token_size); + + sizeall += sizeinput + sizeoutput; + + ctx->dma_buffer = dma_alloc_attrs(dev, sizeall, &ctx->dma_addr, + GFP_KERNEL, attrs); + ctx->attrs = attrs; + if (!ctx->dma_buffer) { + dev_err(dev, "failed to allocate dma memory\n"); + kfree(ctx); + return NULL; + } + + ctx->dma_size = sizeall; + + config_host_addr = ctx->dma_buffer; + ctx->config_vied_addr = ctx->dma_addr; + + offset = conf_size; + ctx->input_queue = ctx->dma_buffer + offset; + config_host_addr->input_queue = ctx->dma_addr + offset; + config_host_addr->num_input_queues = cfg->num_input_queues; + + offset += inq_size; + ctx->output_queue = ctx->dma_buffer + offset; + config_host_addr->output_queue = ctx->dma_addr + offset; + config_host_addr->num_output_queues = cfg->num_output_queues; + + /* copy firmware specific data */ + offset += outq_size; + specific_host_addr = ctx->dma_buffer + offset; + config_host_addr->specific_addr = ctx->dma_addr + offset; + config_host_addr->specific_size = cfg->specific_size; + if (cfg->specific_addr && cfg->specific_size) + memcpy(specific_host_addr, cfg->specific_addr, + cfg->specific_size); + + /* initialize input queues */ + offset += specific_size; + res.reg = SYSCOM_QPR_BASE_REG; + res.host_address = (u64)(ctx->dma_buffer + offset); + res.vied_address = ctx->dma_addr + offset; + for (i = 0; i < cfg->num_input_queues; i++) + ipu6_sys_queue_init(ctx->input_queue + i, + cfg->input[i].queue_size, + cfg->input[i].token_size, &res); + + /* initialize output queues */ + offset += sizeinput; + res.host_address = (u64)(ctx->dma_buffer + offset); + res.vied_address = ctx->dma_addr + offset; + for (i = 0; i < cfg->num_output_queues; i++) { + ipu6_sys_queue_init(ctx->output_queue + i, + cfg->output[i].queue_size, + cfg->output[i].token_size, &res); + } + + return ctx; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6); + +int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx) +{ + /* write magic pattern to disable the tunit trace */ + writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4); + /* Check if SP is in valid state */ + if (!ctx->cell_ready(ctx->adev)) + return -EIO; + + /* store syscom uninitialized command */ + writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4); + + /* store syscom uninitialized state */ + writel(SYSCOM_STATE_UNINIT, + BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_STATE_ID)); + + /* store firmware configuration address */ + writel(ctx->config_vied_addr, + BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_CONFIG_ID)); + ctx->cell_start(ctx->adev); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6); + +int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx) +{ + int state; + + state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_STATE_ID)); + if (state != SYSCOM_STATE_READY) + return -EBUSY; + + /* set close request flag */ + writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr + + SYSCOM_COMMAND_REG * 4); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6); + +int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force) +{ + /* check if release is forced, an verify cell state if it is not */ + if (!force && !ctx->cell_ready(ctx->adev)) + return -EBUSY; + + dma_free_attrs(&ctx->adev->auxdev.dev, ctx->dma_size, + ctx->dma_buffer, ctx->dma_addr, ctx->attrs); + kfree(ctx); + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6); + +bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx) +{ + int state; + + state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, + ctx->buttress_boot_offset, + SYSCOM_STATE_ID)); + + return state == SYSCOM_STATE_READY; +} +EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6); + +void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int wr, rd; + unsigned int packets; + unsigned int index; + + wr = readl(q_dmem + FW_COM_WR_REG); + rd = readl(q_dmem + FW_COM_RD_REG); + + if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) + return NULL; + + if (wr < rd) + packets = rd - wr - 1; + else + packets = q->size - (wr - rd + 1); + + if (!packets) + return NULL; + + index = readl(q_dmem + FW_COM_WR_REG); + + return (void *)(q->host_address + index * q->token_size); +} +EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6); + +void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1; + + if (wr >= q->size) + wr = 0; + + writel(wr, q_dmem + FW_COM_WR_REG); +} +EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6); + +void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int wr, rd; + unsigned int packets; + + wr = readl(q_dmem + FW_COM_WR_REG); + rd = readl(q_dmem + FW_COM_RD_REG); + + if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) + return NULL; + + if (wr < rd) + wr += q->size; + + packets = wr - rd; + if (!packets) + return NULL; + + return (void *)(q->host_address + rd * q->token_size); +} +EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6); + +void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) +{ + struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; + void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; + unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1; + + if (rd >= q->size) + rd = 0; + + writel(rd, q_dmem + FW_COM_RD_REG); +} +EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6); diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.h b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h new file mode 100644 index 000000000000..660c406b3ac9 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2023 Intel Corporation */ + +#ifndef IPU6_FW_COM_H +#define IPU6_FW_COM_H + +struct ipu6_fw_com_context; +struct ipu6_bus_device; + +struct ipu6_fw_syscom_queue_config { + unsigned int queue_size; /* tokens per queue */ + unsigned int token_size; /* bytes per token */ +}; + +#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET 0 + +struct ipu6_fw_com_cfg { + unsigned int num_input_queues; + unsigned int num_output_queues; + struct ipu6_fw_syscom_queue_config *input; + struct ipu6_fw_syscom_queue_config *output; + + unsigned int dmem_addr; + + /* firmware-specific configuration data */ + void *specific_addr; + unsigned int specific_size; + int (*cell_ready)(struct ipu6_bus_device *adev); + void (*cell_start)(struct ipu6_bus_device *adev); + + unsigned int buttress_boot_offset; +}; + +void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, + struct ipu6_bus_device *adev, void __iomem *base); + +int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx); +bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx); +int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx); +int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force); + +void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); +void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); +void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); +void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); + +#endif From patchwork Tue Oct 24 11:29:16 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737695 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id C382CC00A8F for ; Tue, 24 Oct 2023 11:20:41 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231241AbjJXLUm (ORCPT ); Tue, 24 Oct 2023 07:20:42 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:51650 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231234AbjJXLUk (ORCPT ); Tue, 24 Oct 2023 07:20:40 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 74DAE10C3 for ; Tue, 24 Oct 2023 04:20:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146436; x=1729682436; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=GOlpH0cBtgArWXRVeoeQJbplBNs2sR7lt3Q0hyDeX5Y=; b=XK0DupLBA7cv4GGXMtwIABgTsnvWqXj5T1U+DH+kXavIgOvtR3+Jx9E+ wmHp8ys2E3UG8Xsx38RVcQK9TO0XLWBmDa9sUsypXrtHdocouOUVGXOAk yi/mo64cBDbxQZtN4pn88YCtdiS5ybRsMqTShu069u0KOrVC7FkX4AUS3 gMe65tgF+NoIcNoKeWSQW/9KDGMkJmTaJzTK6XhMy4IjQTcyVm7lRTLrs pheb0tfG+WrnyxCkGaxRUbzKKxpwQFDvNEAAioW2p0n8sBOxTCCMMpS2L Fzuo74yUJg/vL/7eZYQiYI9RDJewag7jpPFClg2Ec0SiIZziDV6gs4HUe A==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258742" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258742" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:20:36 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069879" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069879" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:32 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 07/15] media: intel/ipu6: input system ABI between firmware and driver Date: Tue, 24 Oct 2023 19:29:16 +0800 Message-ID: <20231024112924.3934228-8-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao Implement the input system firmware ABIs between the firmware and driver - include stream configuration, control command, capture request and response, etc. Signed-off-by: Bingbu Cao --- drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 484 +++++++++++++++++ drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 570 ++++++++++++++++++++ 2 files changed, 1054 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.h diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c new file mode 100644 index 000000000000..28353cb53607 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include +#include + +#include + +#include "ipu6-fw-com.h" +#include "ipu6-isys.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = { + "STREAM_OPEN", + "STREAM_START", + "STREAM_START_AND_CAPTURE", + "STREAM_CAPTURE", + "STREAM_STOP", + "STREAM_FLUSH", + "STREAM_CLOSE" +}; + +static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_fw_isys_proxy_resp_info_abi *resp; + int ret; + + resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + if (!resp) + return 1; + + dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n", + resp->request_id, resp->error_info.error, + resp->error_info.error_details); + + ret = req_id == resp->request_id ? 0 : -EIO; + + ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); + + return ret; +} + +int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, + unsigned int req_id, + unsigned int index, + unsigned int offset, u32 value) +{ + struct ipu6_fw_com_context *ctx = isys->fwcom; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_fw_proxy_send_queue_token *token; + unsigned int timeout = 1000; + int ret; + + dev_dbg(dev, + "proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n", + req_id, index, offset, value); + + token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); + if (!token) + return -EBUSY; + + token->request_id = req_id; + token->region_index = index; + token->offset = offset; + token->value = value; + ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); + + do { + usleep_range(100, 110); + ret = handle_proxy_response(isys, req_id); + if (!ret) + break; + if (ret == -EIO) { + dev_err(dev, "Proxy respond with unexpected id\n"); + break; + } + timeout--; + } while (ret && timeout); + + if (!timeout) + dev_err(dev, "Proxy response timed out\n"); + + return ret; +} + +int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) +{ + struct ipu6_fw_com_context *ctx = isys->fwcom; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_fw_send_queue_token *token; + + if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE) + return -EINVAL; + + dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]); + + /* + * Time to flush cache in case we have some payload. Not all messages + * have that + */ + if (cpu_mapped_buf) + clflush_cache_range(cpu_mapped_buf, size); + + token = ipu6_send_get_token(ctx, + stream_handle + IPU6_BASE_MSG_SEND_QUEUES); + if (!token) + return -EBUSY; + + token->payload = dma_mapped_buf; + token->buf_handle = (unsigned long)cpu_mapped_buf; + token->send_type = send_type; + + ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES); + + return 0; +} + +int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, u16 send_type) +{ + return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + send_type); +} + +int ipu6_fw_isys_close(struct ipu6_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + int retry = IPU6_ISYS_CLOSE_RETRY; + unsigned long flags; + void *fwcom; + int ret; + + /* + * Stop the isys fw. Actual close takes + * some time as the FW must stop its actions including code fetch + * to SP icache. + * spinlock to wait the interrupt handler to be finished + */ + spin_lock_irqsave(&isys->power_lock, flags); + ret = ipu6_fw_com_close(isys->fwcom); + fwcom = isys->fwcom; + isys->fwcom = NULL; + spin_unlock_irqrestore(&isys->power_lock, flags); + if (ret) + dev_err(dev, "Device close failure: %d\n", ret); + + /* release probably fails if the close failed. Let's try still */ + do { + usleep_range(400, 500); + ret = ipu6_fw_com_release(fwcom, 0); + retry--; + } while (ret && retry); + + if (ret) { + dev_err(dev, "Device release time out %d\n", ret); + spin_lock_irqsave(&isys->power_lock, flags); + isys->fwcom = fwcom; + spin_unlock_irqrestore(&isys->power_lock, flags); + } + + return ret; +} + +void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) +{ + int ret; + + ret = ipu6_fw_com_release(isys->fwcom, 1); + if (ret < 0) + dev_warn(&isys->adev->auxdev.dev, + "Device busy, fw_com release failed."); + isys->fwcom = NULL; +} + +static void start_sp(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *spc_regs_base = isys->pdata->base + + isys->pdata->ipdata->hw_variant.spc_offset; + u32 val = IPU6_ISYS_SPC_STATUS_START | + IPU6_ISYS_SPC_STATUS_RUN | + IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; + + val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0; + + writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); +} + +static int query_sp(struct ipu6_bus_device *adev) +{ + struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); + void __iomem *spc_regs_base = isys->pdata->base + + isys->pdata->ipdata->hw_variant.spc_offset; + u32 val; + + val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); + /* return true when READY == 1, START == 0 */ + val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START; + + return val == IPU6_ISYS_SPC_STATUS_READY; +} + +static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, + struct ipu6_fw_com_cfg *fwcom, + unsigned int num_streams) +{ + unsigned int max_send_queues, max_sram_blocks, max_devq_size; + struct ipu6_fw_syscom_queue_config *input_queue_cfg; + struct ipu6_fw_syscom_queue_config *output_queue_cfg; + struct device *dev = &isys->adev->auxdev.dev; + int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY; + int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV; + int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG; + int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES; + int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES; + int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES; + struct ipu6_fw_isys_fw_config *isys_fw_cfg; + u32 num_in_message_queues; + unsigned int max_streams; + unsigned int size; + unsigned int i; + + max_streams = isys->pdata->ipdata->max_streams; + max_send_queues = isys->pdata->ipdata->max_send_queues; + max_sram_blocks = isys->pdata->ipdata->max_sram_blocks; + max_devq_size = isys->pdata->ipdata->max_devq_size; + num_in_message_queues = clamp(num_streams, 1U, max_streams); + isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL); + if (!isys_fw_cfg) + return -ENOMEM; + + isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES; + isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES; + isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues; + isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES; + /* Common msg/dev return queue */ + isys_fw_cfg->num_recv_queues[type_dev] = 0; + isys_fw_cfg->num_recv_queues[type_msg] = 1; + + size = sizeof(*input_queue_cfg) * max_send_queues; + input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!input_queue_cfg) + return -ENOMEM; + + size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES; + output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); + if (!output_queue_cfg) + return -ENOMEM; + + fwcom->input = input_queue_cfg; + fwcom->output = output_queue_cfg; + + fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] + + isys_fw_cfg->num_send_queues[type_dev] + + isys_fw_cfg->num_send_queues[type_msg]; + + fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] + + isys_fw_cfg->num_recv_queues[type_dev] + + isys_fw_cfg->num_recv_queues[type_msg]; + + /* SRAM partitioning. Equal partitioning is set. */ + for (i = 0; i < max_sram_blocks; i++) { + if (i < num_in_message_queues) + isys_fw_cfg->buffer_partition.num_gda_pages[i] = + (IPU6_DEVICE_GDA_NR_PAGES * + IPU6_DEVICE_GDA_VIRT_FACTOR) / + num_in_message_queues; + else + isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0; + } + + /* FW assumes proxy interface at fwcom queue 0 */ + for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) { + input_queue_cfg[i].token_size = + sizeof(struct ipu6_fw_proxy_send_queue_token); + input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE; + } + + for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) { + input_queue_cfg[base_dev_send + i].token_size = + sizeof(struct ipu6_fw_send_queue_token); + input_queue_cfg[base_dev_send + i].queue_size = max_devq_size; + } + + for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) { + input_queue_cfg[base_msg_send + i].token_size = + sizeof(struct ipu6_fw_send_queue_token); + input_queue_cfg[base_msg_send + i].queue_size = + IPU6_ISYS_SIZE_SEND_QUEUE; + } + + for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) { + output_queue_cfg[i].token_size = + sizeof(struct ipu6_fw_proxy_resp_queue_token); + output_queue_cfg[i].queue_size = + IPU6_ISYS_SIZE_PROXY_RECV_QUEUE; + } + /* There is no recv DEV queue */ + for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) { + output_queue_cfg[base_msg_recv + i].token_size = + sizeof(struct ipu6_fw_resp_queue_token); + output_queue_cfg[base_msg_recv + i].queue_size = + IPU6_ISYS_SIZE_RECV_QUEUE; + } + + fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; + fwcom->specific_addr = isys_fw_cfg; + fwcom->specific_size = sizeof(*isys_fw_cfg); + + return 0; +} + +int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) +{ + struct device *dev = &isys->adev->auxdev.dev; + int retry = IPU6_ISYS_OPEN_RETRY; + struct ipu6_fw_com_cfg fwcom = { + .cell_start = start_sp, + .cell_ready = query_sp, + .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET, + }; + int ret; + + ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams); + + isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev, + isys->pdata->base); + if (!isys->fwcom) { + dev_err(dev, "isys fw com prepare failed\n"); + return -EIO; + } + + ret = ipu6_fw_com_open(isys->fwcom); + if (ret) { + dev_err(dev, "isys fw com open failed %d\n", ret); + return ret; + } + + do { + usleep_range(400, 500); + if (ipu6_fw_com_ready(isys->fwcom)) + break; + retry--; + } while (retry > 0); + + if (!retry) { + dev_err(dev, "isys port open ready failed %d\n", ret); + ipu6_fw_isys_close(isys); + ret = -EIO; + } + + return ret; +} + +struct ipu6_fw_isys_resp_info_abi * +ipu6_fw_isys_get_resp(void *context, unsigned int queue) +{ + return ipu6_recv_get_token(context, queue); +} + +void ipu6_fw_isys_put_resp(void *context, unsigned int queue) +{ + ipu6_recv_put_token(context, queue); +} + +void ipu6_fw_isys_dump_stream_cfg(struct device *dev, + struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +{ + unsigned int i; + + dev_dbg(dev, "-----------------------------------------------------\n"); + dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n"); + + dev_dbg(dev, "compfmt = %d\n", cfg->vc); + dev_dbg(dev, "src = %d\n", cfg->src); + dev_dbg(dev, "vc = %d\n", cfg->vc); + dev_dbg(dev, "isl_use = %d\n", cfg->isl_use); + dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type); + + dev_dbg(dev, "send_irq_sof_discarded = %d\n", + cfg->send_irq_sof_discarded); + dev_dbg(dev, "send_irq_eof_discarded = %d\n", + cfg->send_irq_eof_discarded); + dev_dbg(dev, "send_resp_sof_discarded = %d\n", + cfg->send_resp_sof_discarded); + dev_dbg(dev, "send_resp_eof_discarded = %d\n", + cfg->send_resp_eof_discarded); + + dev_dbg(dev, "crop:\n"); + dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset, + cfg->crop.top_offset); + dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset, + cfg->crop.bottom_offset); + + dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins); + for (i = 0; i < cfg->nof_input_pins; i++) { + dev_dbg(dev, "input pin[%d]:\n", i); + dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt); + dev_dbg(dev, "\t.mipi_store_mode = %d\n", + cfg->input_pins[i].mipi_store_mode); + dev_dbg(dev, "\t.bits_per_pix = %d\n", + cfg->input_pins[i].bits_per_pix); + dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", + cfg->input_pins[i].mapped_dt); + dev_dbg(dev, "\t.input_res = %dx%d\n", + cfg->input_pins[i].input_res.width, + cfg->input_pins[i].input_res.height); + dev_dbg(dev, "\t.mipi_decompression = %d\n", + cfg->input_pins[i].mipi_decompression); + dev_dbg(dev, "\t.capture_mode = %d\n", + cfg->input_pins[i].capture_mode); + } + + dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins); + for (i = 0; i < cfg->nof_output_pins; i++) { + dev_dbg(dev, "output_pin[%d]:\n", i); + dev_dbg(dev, "\t.input_pin_id = %d\n", + cfg->output_pins[i].input_pin_id); + dev_dbg(dev, "\t.output_res = %dx%d\n", + cfg->output_pins[i].output_res.width, + cfg->output_pins[i].output_res.height); + dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); + dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt); + dev_dbg(dev, "\t.payload_buf_size = %d\n", + cfg->output_pins[i].payload_buf_size); + dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); + dev_dbg(dev, "\t.watermark_in_lines = %d\n", + cfg->output_pins[i].watermark_in_lines); + dev_dbg(dev, "\t.send_irq = %d\n", + cfg->output_pins[i].send_irq); + dev_dbg(dev, "\t.reserve_compression = %d\n", + cfg->output_pins[i].reserve_compression); + dev_dbg(dev, "\t.snoopable = %d\n", + cfg->output_pins[i].snoopable); + dev_dbg(dev, "\t.error_handling_enable = %d\n", + cfg->output_pins[i].error_handling_enable); + dev_dbg(dev, "\t.sensor_type = %d\n", + cfg->output_pins[i].sensor_type); + } + dev_dbg(dev, "-----------------------------------------------------\n"); +} + +void +ipu6_fw_isys_dump_frame_buff_set(struct device *dev, + struct ipu6_fw_isys_frame_buff_set_abi *buf, + unsigned int outputs) +{ + unsigned int i; + + dev_dbg(dev, "-----------------------------------------------------\n"); + dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n"); + + for (i = 0; i < outputs; i++) { + dev_dbg(dev, "output_pin[%d]:\n", i); + dev_dbg(dev, "\t.out_buf_id = %llu\n", + buf->output_pins[i].out_buf_id); + dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); + dev_dbg(dev, "\t.compress = %d\n", + buf->output_pins[i].compress); + } + + dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof); + dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof); + dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof); + dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof); + dev_dbg(dev, "send_irq_capture_ack = 0x%x\n", + buf->send_irq_capture_ack); + dev_dbg(dev, "send_irq_capture_done = 0x%x\n", + buf->send_irq_capture_done); + dev_dbg(dev, "send_resp_capture_ack = 0x%x\n", + buf->send_resp_capture_ack); + dev_dbg(dev, "send_resp_capture_done = 0x%x\n", + buf->send_resp_capture_done); + + dev_dbg(dev, "-----------------------------------------------------\n"); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h new file mode 100644 index 000000000000..772d779332ba --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h @@ -0,0 +1,570 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2023 Intel Corporation */ + +#ifndef IPU6_FW_ISYS_H +#define IPU6_FW_ISYS_H + +struct ipu6_isys; + +/* Max number of Input/Output Pins */ +#define IPU6_MAX_IPINS 4 + +#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1) + +#define IPU6_STREAM_ID_MAX 16 +#define IPU6_NONSECURE_STREAM_ID_MAX 12 +#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX) +#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX) +#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX) +#define IPU6SE_STREAM_ID_MAX 8 +#define IPU6SE_NONSECURE_STREAM_ID_MAX 4 +#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX) +#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX) +#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX) + +/* Single return queue for all streams/commands type */ +#define IPU6_N_MAX_MSG_RECV_QUEUES 1 +/* Single device queue for high priority commands (bypass in-order queue) */ +#define IPU6_N_MAX_DEV_SEND_QUEUES 1 +/* Single dedicated send queue for proxy interface */ +#define IPU6_N_MAX_PROXY_SEND_QUEUES 1 +/* Single dedicated recv queue for proxy interface */ +#define IPU6_N_MAX_PROXY_RECV_QUEUES 1 +/* Send queues layout */ +#define IPU6_BASE_PROXY_SEND_QUEUES 0 +#define IPU6_BASE_DEV_SEND_QUEUES \ + (IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES) +#define IPU6_BASE_MSG_SEND_QUEUES \ + (IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES) +/* Recv queues layout */ +#define IPU6_BASE_PROXY_RECV_QUEUES 0 +#define IPU6_BASE_MSG_RECV_QUEUES \ + (IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES) +#define IPU6_N_MAX_RECV_QUEUES \ + (IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES) + +#define IPU6_N_MAX_SEND_QUEUES \ + (IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES) +#define IPU6SE_N_MAX_SEND_QUEUES \ + (IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES) + +/* Max number of supported input pins routed in ISL */ +#define IPU6_MAX_IPINS_IN_ISL 2 + +/* Max number of planes for frame formats supported by the FW */ +#define IPU6_PIN_PLANES_MAX 4 + +#define IPU6_FW_ISYS_SENSOR_TYPE_START 14 +#define IPU6_FW_ISYS_SENSOR_TYPE_END 19 +#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6 +#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11 +/* + * Device close takes some time from last ack message to actual stopping + * of the SP processor. As long as the SP processor runs we can't proceed with + * clean up of resources. + */ +#define IPU6_ISYS_OPEN_RETRY 2000 +#define IPU6_ISYS_CLOSE_RETRY 2000 +#define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000) + +enum ipu6_fw_isys_resp_type { + IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0, + IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, + IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, + IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK, + IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, + IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, + IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, + IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED, + IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED, + IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED, + IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED, + IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, + N_IPU6_FW_ISYS_RESP_TYPE +}; + +enum ipu6_fw_isys_send_type { + IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0, + IPU6_FW_ISYS_SEND_TYPE_STREAM_START, + IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE, + IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP, + IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE, + N_IPU6_FW_ISYS_SEND_TYPE +}; + +enum ipu6_fw_isys_queue_type { + IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0, + IPU6_FW_ISYS_QUEUE_TYPE_DEV, + IPU6_FW_ISYS_QUEUE_TYPE_MSG, + N_IPU6_FW_ISYS_QUEUE_TYPE +}; + +enum ipu6_fw_isys_stream_source { + IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0, + IPU6_FW_ISYS_STREAM_SRC_PORT_1, + IPU6_FW_ISYS_STREAM_SRC_PORT_2, + IPU6_FW_ISYS_STREAM_SRC_PORT_3, + IPU6_FW_ISYS_STREAM_SRC_PORT_4, + IPU6_FW_ISYS_STREAM_SRC_PORT_5, + IPU6_FW_ISYS_STREAM_SRC_PORT_6, + IPU6_FW_ISYS_STREAM_SRC_PORT_7, + IPU6_FW_ISYS_STREAM_SRC_PORT_8, + IPU6_FW_ISYS_STREAM_SRC_PORT_9, + IPU6_FW_ISYS_STREAM_SRC_PORT_10, + IPU6_FW_ISYS_STREAM_SRC_PORT_11, + IPU6_FW_ISYS_STREAM_SRC_PORT_12, + IPU6_FW_ISYS_STREAM_SRC_PORT_13, + IPU6_FW_ISYS_STREAM_SRC_PORT_14, + IPU6_FW_ISYS_STREAM_SRC_PORT_15, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8, + IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9, + N_IPU6_FW_ISYS_STREAM_SRC +}; + +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3 + +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_6 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_7 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_8 +#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \ + IPU6_FW_ISYS_STREAM_SRC_PORT_9 + +#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0 +#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1 + +/** + * enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec + * supports up to 4 virtual per physical channel + */ +enum ipu6_fw_isys_mipi_vc { + IPU6_FW_ISYS_MIPI_VC_0 = 0, + IPU6_FW_ISYS_MIPI_VC_1, + IPU6_FW_ISYS_MIPI_VC_2, + IPU6_FW_ISYS_MIPI_VC_3, + N_IPU6_FW_ISYS_MIPI_VC +}; + +enum ipu6_fw_isys_frame_format_type { + IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV12, /* 12 bit YUV 420, Y, UV plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */ + /* 12 bit YUV 420, Intel proprietary tiled format */ + IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY, + + IPU6_FW_ISYS_FRAME_FORMAT_NV16, /* 16 bit YUV 422, Y, UV plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV21, /* 12 bit YUV 420, Y, VU plane */ + IPU6_FW_ISYS_FRAME_FORMAT_NV61, /* 16 bit YUV 422, Y, VU plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YV12, /* 12 bit YUV 420, Y, V, U plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YV16, /* 16 bit YUV 422, Y, V, U plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */ + IPU6_FW_ISYS_FRAME_FORMAT_UYVY, /* 16 bit YUV 422, UYVY interleaved */ + IPU6_FW_ISYS_FRAME_FORMAT_YUYV, /* 16 bit YUV 422, YUYV interleaved */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */ + /* Internal format, 2 y lines followed by a uvinterleaved line */ + IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8, /* RAW8, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */ + IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */ + /** + * 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit + * value, 5 bits for R, 6 bits for G and 5 bits for B. + */ + IPU6_FW_ISYS_FRAME_FORMAT_RGB565, + IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */ + IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */ + IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */ + IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */ + N_IPU6_FW_ISYS_FRAME_FORMAT +}; + +#define IPU6_FW_ISYS_FRAME_FORMAT_RAW (IPU6_FW_ISYS_FRAME_FORMAT_RAW16) + +enum ipu6_fw_isys_pin_type { + /* captured as MIPI packets */ + IPU6_FW_ISYS_PIN_TYPE_MIPI = 0, + /* captured through the SoC path */ + IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3, +}; + +/** + * enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach + * MIPI SRAM with the long packet header or + * if not, then only option is to capture it with pin type MIPI. + */ +enum ipu6_fw_isys_mipi_store_mode { + IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0, + IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER, + N_IPU6_FW_ISYS_MIPI_STORE_MODE +}; + +enum ipu6_fw_isys_capture_mode { + IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0, + IPU6_FW_ISYS_CAPTURE_MODE_BURST, + N_IPU6_FW_ISYS_CAPTURE_MODE, +}; + +enum ipu6_fw_isys_sensor_mode { + IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0, + IPU6_FW_ISYS_SENSOR_MODE_TOBII, + N_IPU6_FW_ISYS_SENSOR_MODE, +}; + +enum ipu6_fw_isys_error { + IPU6_FW_ISYS_ERROR_NONE = 0, + IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY, + IPU6_FW_ISYS_ERROR_HW_CONSISTENCY, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION, + IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION, + IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES, + IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO, + IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO, + IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC, + IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION, + IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL, + N_IPU6_FW_ISYS_ERROR +}; + +enum ipu6_fw_proxy_error { + IPU6_FW_PROXY_ERROR_NONE = 0, + IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION, + IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET, + N_IPU6_FW_PROXY_ERROR +}; + +/* firmware ABI structure below are aligned in firmware, no need pack */ +struct ipu6_fw_isys_buffer_partition_abi { + u32 num_gda_pages[IPU6_STREAM_ID_MAX]; +}; + +struct ipu6_fw_isys_fw_config { + struct ipu6_fw_isys_buffer_partition_abi buffer_partition; + u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; + u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; +}; + +/** + * struct ipu6_fw_isys_resolution_abi: Generic resolution structure. + */ +struct ipu6_fw_isys_resolution_abi { + u32 width; + u32 height; +}; + +/** + * struct ipu6_fw_isys_output_pin_payload_abi + * @out_buf_id: Points to output pin buffer - buffer identifier + * @addr: Points to output pin buffer - CSS Virtual Address + * @compress: Request frame compression (1), or not (0) + */ +struct ipu6_fw_isys_output_pin_payload_abi { + u64 out_buf_id; + u32 addr; + u32 compress; +}; + +/** + * struct ipu6_fw_isys_output_pin_info_abi + * @output_res: output pin resolution + * @stride: output stride in Bytes (not valid for statistics) + * @watermark_in_lines: pin watermark level in lines + * @payload_buf_size: minimum size in Bytes of all buffers that will be + * supplied for capture on this pin + * @send_irq: assert if pin event should trigger irq + * @pt: pin type -real format "enum ipu6_fw_isys_pin_type" + * @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type" + * @input_pin_id: related input pin id + * @reserve_compression: reserve compression resources for pin + */ +struct ipu6_fw_isys_output_pin_info_abi { + struct ipu6_fw_isys_resolution_abi output_res; + u32 stride; + u32 watermark_in_lines; + u32 payload_buf_size; + u32 ts_offsets[IPU6_PIN_PLANES_MAX]; + u32 s2m_pixel_soc_pixel_remapping; + u32 csi_be_soc_pixel_remapping; + u8 send_irq; + u8 input_pin_id; + u8 pt; + u8 ft; + u8 reserved; + u8 reserve_compression; + u8 snoopable; + u8 error_handling_enable; + u32 sensor_type; +}; + +/** + * struct ipu6_fw_isys_input_pin_info_abi + * @input_res: input resolution + * @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type) + * @mipi_store_mode: defines if legacy long packet header will be stored or + * discarded if discarded, output pin type for this + * input pin can only be MIPI + * (enum ipu6_fw_isys_mipi_store_mode) + * @bits_per_pix: native bits per pixel + * @mapped_dt: actual data type from sensor + * @mipi_decompression: defines which compression will be in mipi backend + * @crop_first_and_last_lines Control whether to crop the + * first and last line of the + * input image. Crop done by HW + * device. + * @capture_mode: mode of capture, regular or burst, default value is regular + */ +struct ipu6_fw_isys_input_pin_info_abi { + struct ipu6_fw_isys_resolution_abi input_res; + u8 dt; + u8 mipi_store_mode; + u8 bits_per_pix; + u8 mapped_dt; + u8 mipi_decompression; + u8 crop_first_and_last_lines; + u8 capture_mode; + u8 reserved; +}; + +/** + * struct ipu6_fw_isys_cropping_abi - cropping coordinates + */ +struct ipu6_fw_isys_cropping_abi { + s32 top_offset; + s32 left_offset; + s32 bottom_offset; + s32 right_offset; +}; + +/** + * struct ipu6_fw_isys_stream_cfg_data_abi + * ISYS stream configuration data structure + * @crop: for extended use and is not used in FW currently + * @input_pins: input pin descriptors + * @output_pins: output pin descriptors + * @compfmt: de-compression setting for User Defined Data + * @nof_input_pins: number of input pins + * @nof_output_pins: number of output pins + * @send_irq_sof_discarded: send irq on discarded frame sof response + * - if '1' it will override the send_resp_sof_discarded + * and send the response + * - if '0' the send_resp_sof_discarded will determine + * whether to send the response + * @send_irq_eof_discarded: send irq on discarded frame eof response + * - if '1' it will override the send_resp_eof_discarded + * and send the response + * - if '0' the send_resp_eof_discarded will determine + * whether to send the response + * @send_resp_sof_discarded: send response for discarded frame sof detected, + * used only when send_irq_sof_discarded is '0' + * @send_resp_eof_discarded: send response for discarded frame eof detected, + * used only when send_irq_eof_discarded is '0' + * @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1 + * @vc: MIPI Virtual Channel (up to 4 virtual per physical channel) + * @isl_use: indicates whether stream requires ISL and how + * @sensor_type: type of connected sensor, tobii or others, default is 0 + */ +struct ipu6_fw_isys_stream_cfg_data_abi { + struct ipu6_fw_isys_cropping_abi crop; + struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS]; + struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS]; + u32 compfmt; + u8 nof_input_pins; + u8 nof_output_pins; + u8 send_irq_sof_discarded; + u8 send_irq_eof_discarded; + u8 send_resp_sof_discarded; + u8 send_resp_eof_discarded; + u8 src; + u8 vc; + u8 isl_use; + u8 sensor_type; + u8 reserved; + u8 reserved2; +}; + +/** + * struct ipu6_fw_isys_frame_buff_set - frame buffer set + * @output_pins: output pin addresses + * @send_irq_sof: send irq on frame sof response + * - if '1' it will override the send_resp_sof and + * send the response + * - if '0' the send_resp_sof will determine whether to + * send the response + * @send_irq_eof: send irq on frame eof response + * - if '1' it will override the send_resp_eof and + * send the response + * - if '0' the send_resp_eof will determine whether to + * send the response + * @send_resp_sof: send response for frame sof detected, + * used only when send_irq_sof is '0' + * @send_resp_eof: send response for frame eof detected, + * used only when send_irq_eof is '0' + * @send_resp_capture_ack: send response for capture ack event + * @send_resp_capture_done: send response for capture done event + */ +struct ipu6_fw_isys_frame_buff_set_abi { + struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS]; + u8 send_irq_sof; + u8 send_irq_eof; + u8 send_irq_capture_ack; + u8 send_irq_capture_done; + u8 send_resp_sof; + u8 send_resp_eof; + u8 send_resp_capture_ack; + u8 send_resp_capture_done; + u8 reserved[8]; +}; + +/** + * struct ipu6_fw_isys_error_info_abi + * @error: error code if something went wrong + * @error_details: depending on error code, it may contain additional error info + */ +struct ipu6_fw_isys_error_info_abi { + u32 error; + u32 error_details; +}; + +/** + * struct ipu6_fw_isys_resp_info_comm + * @pin: this var is only valid for pin event related responses, + * contains pin addresses + * @error_info: error information from the FW + * @timestamp: Time information for event if available + * @stream_handle: stream id the response corresponds to + * @type: response type (enum ipu6_fw_isys_resp_type) + * @pin_id: pin id that the pin payload corresponds to + */ +struct ipu6_fw_isys_resp_info_abi { + u64 buf_id; + struct ipu6_fw_isys_output_pin_payload_abi pin; + struct ipu6_fw_isys_error_info_abi error_info; + u32 timestamp[2]; + u8 stream_handle; + u8 type; + u8 pin_id; + u8 reserved; + u32 reserved2; +}; + +/** + * struct ipu6_fw_isys_proxy_error_info_comm + * @proxy_error: error code if something went wrong + * @proxy_error_details: depending on error code, it may contain additional + * error info + */ +struct ipu6_fw_isys_proxy_error_info_abi { + u32 error; + u32 error_details; +}; + +struct ipu6_fw_isys_proxy_resp_info_abi { + u32 request_id; + struct ipu6_fw_isys_proxy_error_info_abi error_info; +}; + +/** + * struct ipu6_fw_proxy_write_queue_token + * @request_id: update id for the specific proxy write request + * @region_index: Region id for the proxy write request + * @offset: Offset of the write request according to the base address + * of the region + * @value: Value that is requested to be written with the proxy write request + */ +struct ipu6_fw_proxy_write_queue_token { + u32 request_id; + u32 region_index; + u32 offset; + u32 value; +}; + +/** + * struct ipu6_fw_resp_queue_token + */ +struct ipu6_fw_resp_queue_token { + struct ipu6_fw_isys_resp_info_abi resp_info; +}; + +/** + * struct ipu6_fw_send_queue_token + */ +struct ipu6_fw_send_queue_token { + u64 buf_handle; + u32 payload; + u16 send_type; + u16 stream_id; +}; + +/** + * struct ipu6_fw_proxy_resp_queue_token + */ +struct ipu6_fw_proxy_resp_queue_token { + struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info; +}; + +/** + * struct ipu6_fw_proxy_send_queue_token + */ +struct ipu6_fw_proxy_send_queue_token { + u32 request_id; + u32 region_index; + u32 offset; + u32 value; +}; + +void ipu6_fw_isys_dump_stream_cfg(struct device *dev, + struct ipu6_fw_isys_stream_cfg_data_abi + *stream_cfg); +void +ipu6_fw_isys_dump_frame_buff_set(struct device *dev, + struct ipu6_fw_isys_frame_buff_set_abi *buf, + unsigned int outputs); +int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); +int ipu6_fw_isys_close(struct ipu6_isys *isys); +int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, u16 send_type); +int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, + size_t size, u16 send_type); +int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, + unsigned int req_id, + unsigned int index, + unsigned int offset, u32 value); +void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); +struct ipu6_fw_isys_resp_info_abi * +ipu6_fw_isys_get_resp(void *context, unsigned int queue); +void ipu6_fw_isys_put_resp(void *context, unsigned int queue); +#endif From patchwork Tue Oct 24 11:29:18 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737694 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id BDC44C07545 for ; Tue, 24 Oct 2023 11:20:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229583AbjJXLUv (ORCPT ); Tue, 24 Oct 2023 07:20:51 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:53654 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231209AbjJXLUu (ORCPT ); Tue, 24 Oct 2023 07:20:50 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id F255D10C3 for ; Tue, 24 Oct 2023 04:20:44 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146444; x=1729682444; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=HctUlUu7OQsdxOeWBrkMXoAucp9F0sFN0pOg45/uloQ=; b=d/T2IDyH42+wg/NFMjpZp2gjBSmr+4JQWWlFLm8rFQ4VGvBVJcXDcW8I 42VgCpK3WZRQ04WrsovcjdpyaR9P7sQN6MY2YhblJzvwO+d5CdQeRD8DI DoJmXAMa4HQyGfrbG6h0DCaJpiRLRJy2uVGAM8G7SoaxnKCmCbPDc6mrh ovqAjsEpSHlpduiyRDiRZJW5BVRAJC1atJTqnrOb+bNOj6poD2V31Q7CH vOvXS0wdvmNtLcN1vG9dfiTDHU2lZZwO8dP9KynG1ethO08wMjXOepkH+ ISXMsrq2IMkRy2ovj/nk++sIxKJuRDk/TfseoaqUWKt3je9yPzK3u5rQU w==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258765" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258765" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:20:44 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069897" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069897" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:40 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 09/15] media: intel/ipu6: add the CSI2 DPHY implementation Date: Tue, 24 Oct 2023 19:29:18 +0800 Message-ID: <20231024112924.3934228-10-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao IPU6 CSI2 DPHY hardware varies on different platforms, current IPU6 has three DPHY hardware instance which maybe used on tigerlake, alderlake, metorlake and jasperlake. MCD DPHY is shipped on tigerlake and alderlake, DWC DPHY is shipped on metorlake. Each PHY has its own register space, input system driver call the DPHY callback which was set at isys_probe(). Signed-off-by: Bingbu Cao --- .../media/pci/intel/ipu6/ipu6-isys-dwc-phy.c | 533 +++++++++++++ .../media/pci/intel/ipu6/ipu6-isys-jsl-phy.c | 237 ++++++ .../media/pci/intel/ipu6/ipu6-isys-mcd-phy.c | 712 ++++++++++++++++++ 3 files changed, 1482 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c new file mode 100644 index 000000000000..58af982f43ee --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c @@ -0,0 +1,533 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include +#include +#include + +#include "ipu6-isys.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +#define IPU6_DWC_DPHY_BASE(i) (0x238038 + 0x34 * (i)) +#define IPU6_DWC_DPHY_RSTZ 0x00 +#define IPU6_DWC_DPHY_SHUTDOWNZ 0x04 +#define IPU6_DWC_DPHY_HSFREQRANGE 0x08 +#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0x0c +#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0x10 +#define IPU6_DWC_DPHY_TEST_IFC_REQ 0x14 +#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0x18 +#define IPU6_DWC_DPHY_DFT_CTRL0 0x28 +#define IPU6_DWC_DPHY_DFT_CTRL1 0x2c +#define IPU6_DWC_DPHY_DFT_CTRL2 0x30 + +/* + * test IFC request definition: + * - req: 0 for read, 1 for write + * - 12 bits address + * - 8bits data (will ignore for read) + * --24----16------4-----0 + * --|-data-|-addr-|-req-| + */ +#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \ + FIELD_PREP(GENMASK(15, 4), addr) | \ + FIELD_PREP(GENMASK(1, 0), req)) + +#define TEST_IFC_REQ_READ 0 +#define TEST_IFC_REQ_WRITE 1 +#define TEST_IFC_REQ_RESET 2 + +#define TEST_IFC_ACCESS_MODE_FSM 0 +#define TEST_IFC_ACCESS_MODE_IFC_CTL 1 + +enum phy_fsm_state { + PHY_FSM_STATE_POWERON = 0, + PHY_FSM_STATE_BGPON = 1, + PHY_FSM_STATE_CAL_TYPE = 2, + PHY_FSM_STATE_BURNIN_CAL = 3, + PHY_FSM_STATE_TERMCAL = 4, + PHY_FSM_STATE_OFFSETCAL = 5, + PHY_FSM_STATE_OFFSET_LANE = 6, + PHY_FSM_STATE_IDLE = 7, + PHY_FSM_STATE_ULP = 8, + PHY_FSM_STATE_DDLTUNNING = 9, + PHY_FSM_STATE_SKEW_BACKWARD = 10, + PHY_FSM_STATE_INVALID, +}; + +static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 data) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + + dev_dbg(dev, "write: reg 0x%lx = data 0x%x", base + addr - isys_base, + data); + writel(data, base + addr); +} + +static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + u32 data; + + data = readl(base + addr); + dev_dbg(dev, "read: reg 0x%lx = data 0x%x", base + addr - isys_base, + data); + + return data; +} + +static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 data, u8 shift, u8 width) +{ + u32 temp; + u32 mask; + + mask = (1 << width) - 1; + temp = dwc_dphy_read(isys, phy_id, addr); + temp &= ~(mask << shift); + temp |= (data & mask) << shift; + dwc_dphy_write(isys, phy_id, addr, temp); +} + +static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id, + u32 addr, u8 shift, u8 width) +{ + u32 val; + + val = dwc_dphy_read(isys, phy_id, addr) >> shift; + return val & ((1 << width) - 1); +} + +#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC) +static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 *val) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + void __iomem *reg; + u32 completion; + int ret; + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, + IFC_REQ(TEST_IFC_REQ_READ, addr, 0)); + reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; + ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), + 10, DWC_DPHY_TIMEOUT); + if (ret) { + dev_err(dev, "DWC ifc request read timeout\n"); + return ret; + } + + *val = completion >> 8 & 0xff; + *val = FIELD_GET(GENMASK(15, 8), completion); + dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val); + + return 0; +} + +static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u32 data) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); + void __iomem *reg; + u32 completion; + int ret; + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, + IFC_REQ(TEST_IFC_REQ_WRITE, addr, data)); + completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION); + reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; + ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), + 10, DWC_DPHY_TIMEOUT); + if (ret) + dev_err(dev, "DWC ifc request write timeout\n"); + + return ret; +} + +static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id, + u32 addr, u32 data, u8 shift, u8 width) +{ + u32 temp, mask; + int ret; + + ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp); + if (ret) + return; + + mask = (1 << width) - 1; + temp &= ~(mask << shift); + temp |= (data & mask) << shift; + dwc_dphy_ifc_write(isys, phy_id, addr, temp); +} + +static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, + u8 shift, u8 width) +{ + int ret; + u32 val; + + ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val); + if (ret) + return 0; + + return ((val >> shift) & ((1 << width) - 1)); +} + +static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 fsm_state; + int ret; + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1); + usleep_range(10, 20); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1); + + ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state, + (fsm_state == PHY_FSM_STATE_IDLE || + fsm_state == PHY_FSM_STATE_ULP), + 100, DWC_DPHY_TIMEOUT, false, isys, + phy_id, 0x1e, 0, 4); + + if (ret) + dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id, + fsm_state); + + return ret; +} + +struct dwc_dphy_freq_range { + u8 hsfreq; + u16 min; + u16 max; + u16 default_mbps; + u16 osc_freq_target; +}; + +#define DPHY_FREQ_RANGE_NUM (63) +#define DPHY_FREQ_RANGE_INVALID_INDEX (0xff) +static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = { + {0x00, 80, 97, 80, 335}, + {0x10, 80, 107, 90, 335}, + {0x20, 84, 118, 100, 335}, + {0x30, 93, 128, 110, 335}, + {0x01, 103, 139, 120, 335}, + {0x11, 112, 149, 130, 335}, + {0x21, 122, 160, 140, 335}, + {0x31, 131, 170, 150, 335}, + {0x02, 141, 181, 160, 335}, + {0x12, 150, 191, 170, 335}, + {0x22, 160, 202, 180, 335}, + {0x32, 169, 212, 190, 335}, + {0x03, 183, 228, 205, 335}, + {0x13, 198, 244, 220, 335}, + {0x23, 212, 259, 235, 335}, + {0x33, 226, 275, 250, 335}, + {0x04, 250, 301, 275, 335}, + {0x14, 274, 328, 300, 335}, + {0x25, 297, 354, 325, 335}, + {0x35, 321, 380, 350, 335}, + {0x05, 369, 433, 400, 335}, + {0x16, 416, 485, 450, 335}, + {0x26, 464, 538, 500, 335}, + {0x37, 511, 590, 550, 335}, + {0x07, 559, 643, 600, 335}, + {0x18, 606, 695, 650, 335}, + {0x28, 654, 748, 700, 335}, + {0x39, 701, 800, 750, 335}, + {0x09, 749, 853, 800, 335}, + {0x19, 796, 905, 850, 335}, + {0x29, 844, 958, 900, 335}, + {0x3a, 891, 1010, 950, 335}, + {0x0a, 939, 1063, 1000, 335}, + {0x1a, 986, 1115, 1050, 335}, + {0x2a, 1034, 1168, 1100, 335}, + {0x3b, 1081, 1220, 1150, 335}, + {0x0b, 1129, 1273, 1200, 335}, + {0x1b, 1176, 1325, 1250, 335}, + {0x2b, 1224, 1378, 1300, 335}, + {0x3c, 1271, 1430, 1350, 335}, + {0x0c, 1319, 1483, 1400, 335}, + {0x1c, 1366, 1535, 1450, 335}, + {0x2c, 1414, 1588, 1500, 335}, + {0x3d, 1461, 1640, 1550, 208}, + {0x0d, 1509, 1693, 1600, 214}, + {0x1d, 1556, 1745, 1650, 221}, + {0x2e, 1604, 1798, 1700, 228}, + {0x3e, 1651, 1850, 1750, 234}, + {0x0e, 1699, 1903, 1800, 241}, + {0x1e, 1746, 1955, 1850, 248}, + {0x2f, 1794, 2008, 1900, 255}, + {0x3f, 1841, 2060, 1950, 261}, + {0x0f, 1889, 2113, 2000, 268}, + {0x40, 1936, 2165, 2050, 275}, + {0x41, 1984, 2218, 2100, 281}, + {0x42, 2031, 2270, 2150, 288}, + {0x43, 2079, 2323, 2200, 294}, + {0x44, 2126, 2375, 2250, 302}, + {0x45, 2174, 2428, 2300, 308}, + {0x46, 2221, 2480, 2350, 315}, + {0x47, 2269, 2500, 2400, 321}, + {0x48, 2316, 2500, 2450, 328}, + {0x49, 2364, 2500, 2500, 335} +}; + +static u16 get_hsfreq_by_mbps(u32 mbps) +{ + unsigned int i = DPHY_FREQ_RANGE_NUM; + + while (i--) { + if (freqranges[i].default_mbps == mbps || + (mbps >= freqranges[i].min && mbps <= freqranges[i].max)) + return i; + } + + return DPHY_FREQ_RANGE_INVALID_INDEX; +} + +static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys, + u32 phy_id, u32 mbps) +{ + struct ipu6_bus_device *adev = isys->adev; + struct device *dev = &adev->auxdev.dev; + struct ipu6_device *isp = adev->isp; + u32 cfg_clk_freqrange; + u32 osc_freq_target; + u32 index; + + dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps); + + index = get_hsfreq_by_mbps(mbps); + if (index == DPHY_FREQ_RANGE_INVALID_INDEX) { + dev_err(dev, "link freq not found for mbps %u", mbps); + return -EINVAL; + } + + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE, + freqranges[index].hsfreq, 0, 7); + + /* Force termination Calibration */ + if (isys->phy_termcal_val) { + dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1); + dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2); + dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, + isys->phy_termcal_val, 4, 4); + } + + /* + * Enable override to configure the DDL target oscillation + * frequency on bit 0 of register 0xe4 + */ + dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1); + /* + * configure registers 0xe2, 0xe3 with the + * appropriate DDL target oscillation frequency + * 0x1cc(460) + */ + osc_freq_target = freqranges[index].osc_freq_target; + dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2, + osc_freq_target & 0xff, 0, 8); + dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3, + (osc_freq_target >> 8) & 0xf, 0, 4); + + if (mbps < 1500) { + /* deskew_polarity_rw, for < 1.5Gbps */ + dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1); + } + + /* + * Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4] + * (38.4 - 17) * 4 = ~85 (0x55) + */ + cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10; + dev_dbg(dev, "ref_clk = %u clk_freqrange = %u", + isp->buttress.ref_clk, cfg_clk_freqrange); + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE, + cfg_clk_freqrange, 0, 8); + + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1); + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1); + + return 0; +} + +static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master, + u32 slave, u32 mbps) +{ + /* Config mastermacro */ + dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1); + + /* Config master PHY clk lane to drive long channel clk */ + dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1); + + /* Config both PHYs data lanes to get clk from long channel */ + dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1); + dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1); + + /* Config slave PHY clk lane to bypass long channel clk to DDR clk */ + dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1); + + /* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */ + dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2); + + /* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */ + dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1); + + /* Turn off slave PHY LP-RX clk lane */ + dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1); + dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5); +} + +#define PHY_E 4 +static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 rescal_done; + int ret; + + ret = dwc_dphy_pwr_up(isys, phy_id); + if (ret != 0) { + dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret); + return ret; + } + + /* reset forcerxmode */ + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1); + dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1); + + dev_dbg(dev, "Dphy %u is ready!", phy_id); + + if (phy_id != PHY_E || isys->phy_termcal_val) + return 0; + + usleep_range(100, 200); + rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1); + if (rescal_done) { + isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id, + 0x220, 2, 4); + dev_dbg(dev, "termcal done with value = %u", + isys->phy_termcal_val); + } + + return 0; +} + +static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id) +{ + dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id); + + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE, + TEST_IFC_ACCESS_MODE_FSM); + dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, + TEST_IFC_REQ_RESET); +} + +int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 phy_id, primary, secondary; + u32 nlanes, port, mbps; + s64 link_freq; + int ret; + + port = cfg->port; + + if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { + dev_warn(dev, "invalid port ID %d\n", port); + return -EINVAL; + } + + nlanes = cfg->nlanes; + /* only port 0, 2 and 4 support 4 lanes */ + if (nlanes == 4 && port % 2) { + dev_err(dev, "invalid csi-port %u with %u lanes\n", port, + nlanes); + return -EINVAL; + } + + link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]); + if (link_freq < 0) { + dev_err(dev, "get link freq failed(%lld).\n", link_freq); + return link_freq; + } + + mbps = div_u64(link_freq, 500000); + + phy_id = port; + primary = port & ~1; + secondary = primary + 1; + if (on) { + if (nlanes == 4) { + dev_dbg(dev, "config phy %u and %u in aggr mode\n", + primary, secondary); + + ipu6_isys_dwc_phy_reset(isys, primary); + ipu6_isys_dwc_phy_reset(isys, secondary); + ipu6_isys_dwc_phy_aggr_setup(isys, primary, + secondary, mbps); + + ret = ipu6_isys_dwc_phy_config(isys, primary, mbps); + if (ret) + return ret; + ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps); + if (ret) + return ret; + + ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary); + if (ret) + return ret; + + ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary); + return ret; + } + + dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n", + phy_id, nlanes); + + ipu6_isys_dwc_phy_reset(isys, phy_id); + ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps); + if (ret) + return ret; + + ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id); + return ret; + } + + if (nlanes == 4) { + dev_dbg(dev, "Power down phy %u and phy %u for port %u\n", + primary, secondary, port); + ipu6_isys_dwc_phy_reset(isys, secondary); + ipu6_isys_dwc_phy_reset(isys, primary); + + return 0; + } + + dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes); + + ipu6_isys_dwc_phy_reset(isys, phy_id); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c new file mode 100644 index 000000000000..f9ee7496613e --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c @@ -0,0 +1,237 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ +#include + +#include "ipu6-isys.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +/* only use BB0, BB2, BB4, and BB6 on PHY0 */ +#define IPU6SE_ISYS_PHY_BB_NUM 4 +#define IPU6SE_ISYS_PHY_0_BASE 0x10000 + +#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x)) +#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x)) +#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x)) +#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x)) + +/* + * use port_cfg to configure that which data lanes used + * +---------+ +------+ +-----+ + * | port0 x4<-----| | | | + * | | | port | | | + * | port1 x2<-----| | | | + * | | | <-| PHY | + * | port2 x4<-----| | | | + * | | |config| | | + * | port3 x2<-----| | | | + * +---------+ +------+ +-----+ + */ +static const unsigned int csi2_port_cfg[][3] = { + {0, 0, 0x1f}, /* no link */ + {4, 0, 0x10}, /* x4 + x4 config */ + {2, 0, 0x12}, /* x2 + x2 config */ + {1, 0, 0x13}, /* x1 + x1 config */ + {2, 1, 0x15}, /* x2x1 + x2x1 config */ + {1, 1, 0x16}, /* x1x1 + x1x1 config */ + {2, 2, 0x18}, /* x2x2 + x2x2 config */ + {1, 2, 0x19} /* x1x2 + x1x2 config */ +}; + +/* port, nlanes, bbindex, portcfg */ +static const unsigned int phy_port_cfg[][4] = { + /* sip0 */ + {0, 1, 0, 0x15}, + {0, 2, 0, 0x15}, + {0, 4, 0, 0x15}, + {0, 4, 2, 0x22}, + /* sip1 */ + {2, 1, 4, 0x15}, + {2, 2, 4, 0x15}, + {2, 4, 4, 0x15}, + {2, 4, 6, 0x22} +}; + +static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys, + unsigned int port, + unsigned int nlanes) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *base = isys->adev->isp->base; + unsigned int bbnum; + u32 val, reg, i; + + dev_dbg(dev, "port %u with %u lanes", port, nlanes); + + /* only support <1.5Gbps */ + for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) { + /* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); + val = readl(base + reg); + val |= FIELD_PREP(GENMASK(6, 1), 13); + writel(val, base + reg); + + /* cphy_rx_control1.en_crc1 = 1 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i); + val = readl(base + reg); + val |= BIT(31); + writel(val, base + reg); + + /* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i); + val = readl(base + reg); + val |= BIT(25) | BIT(26); + writel(val, base + reg); + + /* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */ + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); + val = readl(base + reg); + val |= BIT(0); + writel(val, base + reg); + } + + /* Front end config, use minimal channel loss */ + for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) { + if (phy_port_cfg[i][0] == port && + phy_port_cfg[i][1] == nlanes) { + bbnum = phy_port_cfg[i][2] / 2; + reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum); + val = readl(base + reg); + val |= phy_port_cfg[i][3]; + writel(val, base + reg); + } + } +} + +static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys) +{ + void __iomem *base = isys->adev->isp->base; + u32 val, reg; + + reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL); + + reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL); + + reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL); + + reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL; + val = readl(base + reg); + val |= BIT(0); + writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL); +} + +static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys, + unsigned int port, unsigned int nlanes) +{ + struct device *dev = &isys->adev->auxdev.dev; + unsigned int sip = port / 2; + unsigned int index; + + switch (nlanes) { + case 1: + index = 5; + break; + case 2: + index = 6; + break; + case 4: + index = 1; + break; + default: + dev_err(dev, "lanes nr %u is unsupported\n", nlanes); + return -EINVAL; + } + + dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes); + + writel(csi2_port_cfg[index][2], + isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip)); + + return 0; +} + +static void +ipu6_isys_csi2_set_timing(struct ipu6_isys *isys, + const struct ipu6_isys_csi2_timing *timing, + unsigned int port, unsigned int nlanes) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *reg; + u32 port_base; + u32 i; + + port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) : + CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port); + + dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes); + + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE; + + writel(timing->ctermen, reg); + + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE; + writel(timing->csettle, reg); + + for (i = 0; i < nlanes; i++) { + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i); + writel(timing->dtermen, reg); + + reg = isys->pdata->base + port_base; + reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i); + writel(timing->dsettle, reg); + } +} + +#define DPHY_TIMER_INCR 0x28 +int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + int ret = 0; + u32 nlanes; + u32 port; + + if (!on) + return 0; + + port = cfg->port; + nlanes = cfg->nlanes; + + if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { + dev_warn(dev, "invalid port ID %d\n", port); + return -EINVAL; + } + + ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes); + + writel(DPHY_TIMER_INCR, + isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR); + + /* set port cfg and rx timing */ + ipu6_isys_csi2_set_timing(isys, timing, port, nlanes); + + ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes); + if (ret) + return ret; + + ipu6_isys_csi2_rx_control(isys); + + return 0; +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c new file mode 100644 index 000000000000..3482ce5b252e --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ +#include +#include + +#include + +#include "ipu6-isys.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8) +#define CSI_REG_HUB_GPREG_PHY_CTL_RESET BIT(4) +#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN BIT(0) +#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8) +#define CSI_REG_HUB_GPREG_PHY_POWER_ACK BIT(0) +#define CSI_REG_HUB_GPREG_PHY_READY BIT(4) + +#define MCD_PHY_POWER_STATUS_TIMEOUT (200 * USEC_PER_MSEC) + +/* + * bridge to phy in buttress reg map, each phy has 16 kbytes + * only 2 phys for TGL U and Y + */ +#define IPU6_ISYS_MCD_PHY_BASE(i) (0x10000 + (i) * 0x4000) + +/* + * There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL. + * Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes. + * CSI port 1, 3 (5, 7) can support max 2 data lanes. + * CSI port 0, 2 (4, 6) can support max 4 data lanes. + * PHY configurations are PPI based instead of port. + * Left: + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | PPI | PPI5 | PPI4 | PPI3 | PPI2 | PPI1 | PPI0 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x4 | unused | D3 | D2 | C0 | D0 | D1 | + * |---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x2 | C1 | D0 | D1 | C0 | D0 | D1 | + * ----------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x1 | C1 | D0 | unused | C0 | D0 | D1 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x1 | C1 | D0 | unused | C0 | D0 | unused | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x2 | C1 | D0 | D1 | C0 | D0 | unused | + * +---------+---------+---------+---------+--------+---------+----------+ + * + * Right: + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | PPI | PPI6 | PPI7 | PPI8 | PPI9 | PPI10 | PPI11 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x4 | D1 | D0 | C2 | D2 | D3 | unused | + * |---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x2 | D1 | D0 | C2 | D1 | D0 | C3 | + * ----------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x2x1 | D1 | D0 | C2 | unused | D0 | C3 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x1 | unused | D0 | C2 | unused | D0 | C3 | + * +---------+---------+---------+---------+--------+---------+----------+ + * | | | | | | | | + * | x1x2 | unused | D0 | C2 | D1 | D0 | C3 | + * +---------+---------+---------+---------+--------+---------+----------+ + * + * ppi mapping per phy : + * + * x4 + x4: + * Left : port0 - PPI range {0, 1, 2, 3, 4} + * Right: port2 - PPI range {6, 7, 8, 9, 10} + * + * x4 + x2x2: + * Left: port0 - PPI range {0, 1, 2, 3, 4} + * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} + * + * x2x2 + x4: + * Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} + * Right: port2 - PPI range {6, 7, 8, 9, 10} + * + * x2x2 + x2x2: + * Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} + * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} + */ + +struct phy_reg { + u32 reg; + u32 val; +}; + +static const struct phy_reg common_init_regs[] = { + /* for TGL-U, use 0x80000000 */ + {0x00000040, 0x80000000}, + {0x00000044, 0x00a80880}, + {0x00000044, 0x00b80880}, + {0x00000010, 0x0000078c}, + {0x00000344, 0x2f4401e2}, + {0x00000544, 0x924401e2}, + {0x00000744, 0x594401e2}, + {0x00000944, 0x624401e2}, + {0x00000b44, 0xfc4401e2}, + {0x00000d44, 0xc54401e2}, + {0x00000f44, 0x034401e2}, + {0x00001144, 0x8f4401e2}, + {0x00001344, 0x754401e2}, + {0x00001544, 0xe94401e2}, + {0x00001744, 0xcb4401e2}, + {0x00001944, 0xfa4401e2} +}; + +static const struct phy_reg x1_port0_config_regs[] = { + {0x00000694, 0xc80060fa}, + {0x00000680, 0x3d4f78ea}, + {0x00000690, 0x10a0140b}, + {0x000006a8, 0xdf04010a}, + {0x00000700, 0x57050060}, + {0x00000710, 0x0030001c}, + {0x00000738, 0x5f004444}, + {0x0000073c, 0x78464204}, + {0x00000748, 0x7821f940}, + {0x0000074c, 0xb2000433}, + {0x00000494, 0xfe6030fa}, + {0x00000480, 0x29ef5ed0}, + {0x00000490, 0x10a0540b}, + {0x000004a8, 0x7a01010a}, + {0x00000500, 0xef053460}, + {0x00000510, 0xe030101c}, + {0x00000538, 0xdf808444}, + {0x0000053c, 0xc8422204}, + {0x00000540, 0x0180088c}, + {0x00000574, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x1_port1_config_regs[] = { + {0x00000c94, 0xc80060fa}, + {0x00000c80, 0xcf47abea}, + {0x00000c90, 0x10a0840b}, + {0x00000ca8, 0xdf04010a}, + {0x00000d00, 0x57050060}, + {0x00000d10, 0x0030001c}, + {0x00000d38, 0x5f004444}, + {0x00000d3c, 0x78464204}, + {0x00000d48, 0x7821f940}, + {0x00000d4c, 0xb2000433}, + {0x00000a94, 0xc91030fa}, + {0x00000a80, 0x5a166ed0}, + {0x00000a90, 0x10a0540b}, + {0x00000aa8, 0x5d060100}, + {0x00000b00, 0xef053460}, + {0x00000b10, 0xa030101c}, + {0x00000b38, 0xdf808444}, + {0x00000b3c, 0xc8422204}, + {0x00000b40, 0x0180088c}, + {0x00000b74, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x1_port2_config_regs[] = { + {0x00001294, 0x28f000fa}, + {0x00001280, 0x08130cea}, + {0x00001290, 0x10a0140b}, + {0x000012a8, 0xd704010a}, + {0x00001300, 0x8d050060}, + {0x00001310, 0x0030001c}, + {0x00001338, 0xdf008444}, + {0x0000133c, 0x78422204}, + {0x00001348, 0x7821f940}, + {0x0000134c, 0x5a000433}, + {0x00001094, 0x2d20b0fa}, + {0x00001080, 0xade75dd0}, + {0x00001090, 0x10a0540b}, + {0x000010a8, 0xb101010a}, + {0x00001100, 0x33053460}, + {0x00001110, 0x0030101c}, + {0x00001138, 0xdf808444}, + {0x0000113c, 0xc8422204}, + {0x00001140, 0x8180088c}, + {0x00001174, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x1_port3_config_regs[] = { + {0x00001894, 0xc80060fa}, + {0x00001880, 0x0f90fd6a}, + {0x00001890, 0x10a0840b}, + {0x000018a8, 0xdf04010a}, + {0x00001900, 0x57050060}, + {0x00001910, 0x0030001c}, + {0x00001938, 0x5f004444}, + {0x0000193c, 0x78464204}, + {0x00001948, 0x7821f940}, + {0x0000194c, 0xb2000433}, + {0x00001694, 0x3050d0fa}, + {0x00001680, 0x0ef6d050}, + {0x00001690, 0x10a0540b}, + {0x000016a8, 0xe301010a}, + {0x00001700, 0x69053460}, + {0x00001710, 0xa030101c}, + {0x00001738, 0xdf808444}, + {0x0000173c, 0xc8422204}, + {0x00001740, 0x0180088c}, + {0x00001774, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port0_config_regs[] = { + {0x00000694, 0xc80060fa}, + {0x00000680, 0x3d4f78ea}, + {0x00000690, 0x10a0140b}, + {0x000006a8, 0xdf04010a}, + {0x00000700, 0x57050060}, + {0x00000710, 0x0030001c}, + {0x00000738, 0x5f004444}, + {0x0000073c, 0x78464204}, + {0x00000748, 0x7821f940}, + {0x0000074c, 0xb2000433}, + {0x00000494, 0xc80060fa}, + {0x00000480, 0x29ef5ed8}, + {0x00000490, 0x10a0540b}, + {0x000004a8, 0x7a01010a}, + {0x00000500, 0xef053460}, + {0x00000510, 0xe030101c}, + {0x00000538, 0xdf808444}, + {0x0000053c, 0xc8422204}, + {0x00000540, 0x0180088c}, + {0x00000574, 0x00000000}, + {0x00000294, 0xc80060fa}, + {0x00000280, 0xcb45b950}, + {0x00000290, 0x10a0540b}, + {0x000002a8, 0x8c01010a}, + {0x00000300, 0xef053460}, + {0x00000310, 0x8030101c}, + {0x00000338, 0x41808444}, + {0x0000033c, 0x32422204}, + {0x00000340, 0x0180088c}, + {0x00000374, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port1_config_regs[] = { + {0x00000c94, 0xc80060fa}, + {0x00000c80, 0xcf47abea}, + {0x00000c90, 0x10a0840b}, + {0x00000ca8, 0xdf04010a}, + {0x00000d00, 0x57050060}, + {0x00000d10, 0x0030001c}, + {0x00000d38, 0x5f004444}, + {0x00000d3c, 0x78464204}, + {0x00000d48, 0x7821f940}, + {0x00000d4c, 0xb2000433}, + {0x00000a94, 0xc80060fa}, + {0x00000a80, 0x5a166ed8}, + {0x00000a90, 0x10a0540b}, + {0x00000aa8, 0x7a01010a}, + {0x00000b00, 0xef053460}, + {0x00000b10, 0xa030101c}, + {0x00000b38, 0xdf808444}, + {0x00000b3c, 0xc8422204}, + {0x00000b40, 0x0180088c}, + {0x00000b74, 0x00000000}, + {0x00000894, 0xc80060fa}, + {0x00000880, 0x4d4f21d0}, + {0x00000890, 0x10a0540b}, + {0x000008a8, 0x5601010a}, + {0x00000900, 0xef053460}, + {0x00000910, 0x8030101c}, + {0x00000938, 0xdf808444}, + {0x0000093c, 0xc8422204}, + {0x00000940, 0x0180088c}, + {0x00000974, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port2_config_regs[] = { + {0x00001294, 0xc80060fa}, + {0x00001280, 0x08130cea}, + {0x00001290, 0x10a0140b}, + {0x000012a8, 0xd704010a}, + {0x00001300, 0x8d050060}, + {0x00001310, 0x0030001c}, + {0x00001338, 0xdf008444}, + {0x0000133c, 0x78422204}, + {0x00001348, 0x7821f940}, + {0x0000134c, 0x5a000433}, + {0x00001094, 0xc80060fa}, + {0x00001080, 0xade75dd8}, + {0x00001090, 0x10a0540b}, + {0x000010a8, 0xb101010a}, + {0x00001100, 0x33053460}, + {0x00001110, 0x0030101c}, + {0x00001138, 0xdf808444}, + {0x0000113c, 0xc8422204}, + {0x00001140, 0x8180088c}, + {0x00001174, 0x00000000}, + {0x00000e94, 0xc80060fa}, + {0x00000e80, 0x0fbf16d0}, + {0x00000e90, 0x10a0540b}, + {0x00000ea8, 0x7a01010a}, + {0x00000f00, 0xf5053460}, + {0x00000f10, 0xc030101c}, + {0x00000f38, 0xdf808444}, + {0x00000f3c, 0xc8422204}, + {0x00000f40, 0x8180088c}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x2_port3_config_regs[] = { + {0x00001894, 0xc80060fa}, + {0x00001880, 0x0f90fd6a}, + {0x00001890, 0x10a0840b}, + {0x000018a8, 0xdf04010a}, + {0x00001900, 0x57050060}, + {0x00001910, 0x0030001c}, + {0x00001938, 0x5f004444}, + {0x0000193c, 0x78464204}, + {0x00001948, 0x7821f940}, + {0x0000194c, 0xb2000433}, + {0x00001694, 0xc80060fa}, + {0x00001680, 0x0ef6d058}, + {0x00001690, 0x10a0540b}, + {0x000016a8, 0x7a01010a}, + {0x00001700, 0x69053460}, + {0x00001710, 0xa030101c}, + {0x00001738, 0xdf808444}, + {0x0000173c, 0xc8422204}, + {0x00001740, 0x0180088c}, + {0x00001774, 0x00000000}, + {0x00001494, 0xc80060fa}, + {0x00001480, 0xf9d34bd0}, + {0x00001490, 0x10a0540b}, + {0x000014a8, 0x7a01010a}, + {0x00001500, 0x1b053460}, + {0x00001510, 0x0030101c}, + {0x00001538, 0xdf808444}, + {0x0000153c, 0xc8422204}, + {0x00001540, 0x8180088c}, + {0x00001574, 0x00000000}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port0_config_regs[] = { + {0x00000694, 0xc80060fa}, + {0x00000680, 0x3d4f78fa}, + {0x00000690, 0x10a0140b}, + {0x000006a8, 0xdf04010a}, + {0x00000700, 0x57050060}, + {0x00000710, 0x0030001c}, + {0x00000738, 0x5f004444}, + {0x0000073c, 0x78464204}, + {0x00000748, 0x7821f940}, + {0x0000074c, 0xb2000433}, + {0x00000494, 0xfe6030fa}, + {0x00000480, 0x29ef5ed8}, + {0x00000490, 0x10a0540b}, + {0x000004a8, 0x7a01010a}, + {0x00000500, 0xef053460}, + {0x00000510, 0xe030101c}, + {0x00000538, 0xdf808444}, + {0x0000053c, 0xc8422204}, + {0x00000540, 0x0180088c}, + {0x00000574, 0x00000004}, + {0x00000294, 0x23e030fa}, + {0x00000280, 0xcb45b950}, + {0x00000290, 0x10a0540b}, + {0x000002a8, 0x8c01010a}, + {0x00000300, 0xef053460}, + {0x00000310, 0x8030101c}, + {0x00000338, 0x41808444}, + {0x0000033c, 0x32422204}, + {0x00000340, 0x0180088c}, + {0x00000374, 0x00000004}, + {0x00000894, 0x5620b0fa}, + {0x00000880, 0x4d4f21dc}, + {0x00000890, 0x10a0540b}, + {0x000008a8, 0x5601010a}, + {0x00000900, 0xef053460}, + {0x00000910, 0x8030101c}, + {0x00000938, 0xdf808444}, + {0x0000093c, 0xc8422204}, + {0x00000940, 0x0180088c}, + {0x00000974, 0x00000004}, + {0x00000a94, 0xc91030fa}, + {0x00000a80, 0x5a166ecc}, + {0x00000a90, 0x10a0540b}, + {0x00000aa8, 0x5d01010a}, + {0x00000b00, 0xef053460}, + {0x00000b10, 0xa030101c}, + {0x00000b38, 0xdf808444}, + {0x00000b3c, 0xc8422204}, + {0x00000b40, 0x0180088c}, + {0x00000b74, 0x00000004}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port1_config_regs[] = { + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port2_config_regs[] = { + {0x00001294, 0x28f000fa}, + {0x00001280, 0x08130cfa}, + {0x00001290, 0x10c0140b}, + {0x000012a8, 0xd704010a}, + {0x00001300, 0x8d050060}, + {0x00001310, 0x0030001c}, + {0x00001338, 0xdf008444}, + {0x0000133c, 0x78422204}, + {0x00001348, 0x7821f940}, + {0x0000134c, 0x5a000433}, + {0x00001094, 0x2d20b0fa}, + {0x00001080, 0xade75dd8}, + {0x00001090, 0x10a0540b}, + {0x000010a8, 0xb101010a}, + {0x00001100, 0x33053460}, + {0x00001110, 0x0030101c}, + {0x00001138, 0xdf808444}, + {0x0000113c, 0xc8422204}, + {0x00001140, 0x8180088c}, + {0x00001174, 0x00000004}, + {0x00000e94, 0xd308d0fa}, + {0x00000e80, 0x0fbf16d0}, + {0x00000e90, 0x10a0540b}, + {0x00000ea8, 0x2c01010a}, + {0x00000f00, 0xf5053460}, + {0x00000f10, 0xc030101c}, + {0x00000f38, 0xdf808444}, + {0x00000f3c, 0xc8422204}, + {0x00000f40, 0x8180088c}, + {0x00000f74, 0x00000004}, + {0x00001494, 0x136850fa}, + {0x00001480, 0xf9d34bdc}, + {0x00001490, 0x10a0540b}, + {0x000014a8, 0x5a01010a}, + {0x00001500, 0x1b053460}, + {0x00001510, 0x0030101c}, + {0x00001538, 0xdf808444}, + {0x0000153c, 0xc8422204}, + {0x00001540, 0x8180088c}, + {0x00001574, 0x00000004}, + {0x00001694, 0x3050d0fa}, + {0x00001680, 0x0ef6d04c}, + {0x00001690, 0x10a0540b}, + {0x000016a8, 0xe301010a}, + {0x00001700, 0x69053460}, + {0x00001710, 0xa030101c}, + {0x00001738, 0xdf808444}, + {0x0000173c, 0xc8422204}, + {0x00001740, 0x0180088c}, + {0x00001774, 0x00000004}, + {0x00000000, 0x00000000} +}; + +static const struct phy_reg x4_port3_config_regs[] = { + {0x00000000, 0x00000000} +}; + +static const struct phy_reg *x1_config_regs[4] = { + x1_port0_config_regs, + x1_port1_config_regs, + x1_port2_config_regs, + x1_port3_config_regs +}; + +static const struct phy_reg *x2_config_regs[4] = { + x2_port0_config_regs, + x2_port1_config_regs, + x2_port2_config_regs, + x2_port3_config_regs +}; + +static const struct phy_reg *x4_config_regs[4] = { + x4_port0_config_regs, + x4_port1_config_regs, + x4_port2_config_regs, + x4_port3_config_regs +}; + +static const struct phy_reg **config_regs[3] = { + x1_config_regs, + x2_config_regs, + x4_config_regs +}; + +static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 val; + int ret; + + val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN; + writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + + ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), + val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK, + 200, MCD_PHY_POWER_STATUS_TIMEOUT); + if (ret) + dev_err(dev, "PHY%d powerup ack timeout", id); + + return ret; +} + +static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 val; + int ret; + + writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), + val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK), + 200, MCD_PHY_POWER_STATUS_TIMEOUT); + if (ret) + dev_err(dev, "PHY%d powerdown ack timeout", id); + + return ret; +} + +static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert) +{ + void __iomem *isys_base = isys->pdata->base; + u32 val; + + val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); + if (assert) + val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET; + else + val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET); + + writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); +} + +static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u32 val; + int ret; + + ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), + val, val & CSI_REG_HUB_GPREG_PHY_READY, + 200, MCD_PHY_POWER_STATUS_TIMEOUT); + if (ret) + dev_err(dev, "PHY%d ready ack timeout", id); + + return ret; +} + +static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys) +{ + struct ipu6_bus_device *adev = isys->adev; + struct ipu6_device *isp = adev->isp; + void __iomem *isp_base = isp->base; + struct sensor_async_sd *s_asd; + struct v4l2_async_connection *asc; + void __iomem *phy_base; + unsigned int i; + u8 phy_id; + + list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { + s_asd = container_of(asc, struct sensor_async_sd, asc); + phy_id = s_asd->csi2.port / 4; + phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); + + for (i = 0; i < ARRAY_SIZE(common_init_regs); i++) + writel(common_init_regs[i].val, + phy_base + common_init_regs[i].reg); + } +} + +static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg) +{ + int phy_port; + int ret; + + if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1)) + return -EINVAL; + + /* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */ + /* normalize driver port number */ + phy_port = cfg->port % 4; + + /* swap port number only for A and B */ + if (phy_port == 0) + phy_port = 1; + else if (phy_port == 1) + phy_port = 0; + + ret = phy_port; + + /* check validity per lane configuration */ + if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2)) + ret = -EINVAL; + else if ((cfg->nlanes == 2 || cfg->nlanes == 1) && + !(phy_port >= 0 && phy_port <= 3)) + ret = -EINVAL; + + return ret; +} + +static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_bus_device *adev = isys->adev; + const struct phy_reg **phy_config_regs; + struct ipu6_device *isp = adev->isp; + void __iomem *isp_base = isp->base; + struct sensor_async_sd *s_asd; + struct ipu6_isys_csi2_config cfg; + struct v4l2_async_connection *asc; + u8 phy_port, phy_id; + unsigned int i; + void __iomem *phy_base; + + list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { + s_asd = container_of(asc, struct sensor_async_sd, asc); + cfg.port = s_asd->csi2.port; + cfg.nlanes = s_asd->csi2.nlanes; + phy_port = ipu6_isys_driver_port_to_phy_port(&cfg); + if (phy_port < 0) { + dev_err(dev, "invalid port %d for lane %d", cfg.port, + cfg.nlanes); + return -ENXIO; + } + + phy_id = cfg.port / 4; + phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); + dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id, + cfg.nlanes); + + phy_config_regs = config_regs[cfg.nlanes / 2]; + cfg.port = phy_port; + for (i = 0; phy_config_regs[cfg.port][i].reg; i++) + writel(phy_config_regs[cfg.port][i].val, + phy_base + phy_config_regs[cfg.port][i].reg); + } + + return 0; +} + +#define CSI_MCD_PHY_NUM 2 +static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM]; + +int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, + struct ipu6_isys_csi2_config *cfg, + const struct ipu6_isys_csi2_timing *timing, + bool on) +{ + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + u8 port, phy_id; + refcount_t *ref; + int ret; + + port = cfg->port; + phy_id = port / 4; + + ref = &phy_power_ref_count[phy_id]; + + dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port, + cfg->nlanes); + + if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { + dev_warn(dev, "invalid port ID %d\n", port); + return -EINVAL; + } + + if (on) { + if (refcount_read(ref)) { + dev_dbg(dev, "for phy %d is already UP", phy_id); + refcount_inc(ref); + return 0; + } + + ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id); + if (ret) + return ret; + + ipu6_isys_mcd_phy_reset(isys, phy_id, 0); + ipu6_isys_mcd_phy_common_init(isys); + + ret = ipu6_isys_mcd_phy_config(isys); + if (ret) + return ret; + + ipu6_isys_mcd_phy_reset(isys, phy_id, 1); + ret = ipu6_isys_mcd_phy_ready(isys, phy_id); + if (ret) + return ret; + + refcount_set(ref, 1); + return 0; + } + + if (!refcount_dec_and_test(ref)) + return 0; + + return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id); +} From patchwork Tue Oct 24 11:29:20 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737693 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id EB379C07545 for ; Tue, 24 Oct 2023 11:21:02 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231234AbjJXLVC (ORCPT ); Tue, 24 Oct 2023 07:21:02 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:47470 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231466AbjJXLVB (ORCPT ); Tue, 24 Oct 2023 07:21:01 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id D676F10C3 for ; Tue, 24 Oct 2023 04:20:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146453; x=1729682453; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=0g/fBcFoLA1o/xumc6VKQto5d89RUyQNchLtJmjSP+U=; b=MNc/P8O850+01wiIwYwDGW4REjgn6nSaNfgm84hcgqDqQ6UtoK4dNlYg /0XHz80vwdTCuEDBC2P2mJi6PfsgLN+uAUpomMYTZ6TpoDiQ9m6m/pzE3 rkAN/LXJ7hElno7/jhZ8JQPgE3+35a5EQ+4oXtaya8RQsauV+0dRPWODN 0kkU6uWIi7bmtNgnWwBIsxCEsOt7dt62mp7wYQafWPjCu5Xv6wwcPWmel A0f/14yuRZFTSkMnWHh75oj3diOPGQRRIs85LqANRr26jJqKWBaHS6nww xL7MTfnFfE83OUANQ8YW9vKda12iLQA8PqTIvMFezg673hIzQs8s+AXim g==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258794" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258794" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:20:53 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069925" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069925" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:48 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 11/15] media: intel/ipu6: input system video capture nodes Date: Tue, 24 Oct 2023 19:29:20 +0800 Message-ID: <20231024112924.3934228-12-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao Register v4l2 video device and setup the vb2 queue to support basic video capture. Video streaming callback will trigger the input system driver to construct a input system stream configuration for firmware based on data type and stream ID and then queue buffers to firmware to do capture. Signed-off-by: Bingbu Cao --- .../media/pci/intel/ipu6/ipu6-isys-queue.c | 846 +++++++++++ .../media/pci/intel/ipu6/ipu6-isys-queue.h | 94 ++ .../media/pci/intel/ipu6/ipu6-isys-video.c | 1239 +++++++++++++++++ .../media/pci/intel/ipu6/ipu6-isys-video.h | 129 ++ 4 files changed, 2308 insertions(+) create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.h create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.c create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.h diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c new file mode 100644 index 000000000000..81727d4026a2 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include + +#include "ipu6-isys.h" + +static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + bool use_fmt = false; + unsigned int i; + u32 size; + + /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ + if (!*num_planes) { + use_fmt = true; + *num_planes = av->mpix.num_planes; + } + + for (i = 0; i < *num_planes; i++) { + size = av->mpix.plane_fmt[i].sizeimage; + if (use_fmt) { + sizes[i] = size; + } else if (sizes[i] < size) { + dev_err(dev, "%s: queue setup: plane %d size %u < %u\n", + av->vdev.name, i, sizes[i], size); + return -EINVAL; + } + + alloc_devs[i] = aq->dev; + } + + return 0; +} + +int ipu6_isys_buf_prepare(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + + dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", + av->vdev.name, av->mpix.plane_fmt[0].sizeimage, + vb2_plane_size(vb, 0)); + + if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0)) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline * + av->mpix.height); + vb->planes[0].data_offset = 0; + + return 0; +} + +static int buf_prepare(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + + return aq->buf_prepare(vb); +} + +/* + * Queue a buffer list back to incoming or active queues. The buffers + * are removed from the buffer list. + */ +void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state) +{ + struct ipu6_isys_buffer *ib, *ib_safe; + unsigned long flags; + bool first = true; + + if (!bl) + return; + + WARN_ON_ONCE(!bl->nbufs); + WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE && + op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING); + + list_for_each_entry_safe(ib, ib_safe, &bl->head, head) { + struct ipu6_isys_video *av; + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct ipu6_isys_queue *aq = + vb2_queue_to_isys_queue(vb->vb2_queue); + struct device *dev; + + if (WARN_ON_ONCE(ib->type != IPU6_ISYS_VIDEO_BUFFER)) + continue; + + av = ipu6_isys_queue_to_video(aq); + dev = &av->isys->adev->auxdev.dev; + spin_lock_irqsave(&aq->lock, flags); + list_del(&ib->head); + if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE) + list_add(&ib->head, &aq->active); + else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING) + list_add_tail(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_SET_STATE) + vb2_buffer_done(vb, state); + + if (first) { + dev_dbg(dev, + "queue buf list %p flags %lx, s %d, %d bufs\n", + bl, op_flags, state, bl->nbufs); + first = false; + } + + bl->nbufs--; + } + + WARN_ON(bl->nbufs); +} + +/* + * flush_firmware_streamon_fail() - Flush in cases where requests may + * have been queued to firmware and the *firmware streamon fails for a + * reason or another. + */ +static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; + unsigned long flags; + + lockdep_assert_held(&stream->mutex); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_buffer *ib, *ib_safe; + + spin_lock_irqsave(&aq->lock, flags); + list_for_each_entry_safe(ib, ib_safe, &aq->active, head) { + struct vb2_buffer *vb = + ipu6_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + if (av->streaming) { + dev_dbg(dev, + "%s: queue buffer %u back to incoming\n", + av->vdev.name, vb->index); + /* Queue already streaming, return to driver. */ + list_add(&ib->head, &aq->incoming); + continue; + } + /* Queue not yet streaming, return to user. */ + dev_dbg(dev, "%s: return %u back to videobuf2\n", + av->vdev.name, vb->index); + vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib), + VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&aq->lock, flags); + } +} + +/* + * Attempt obtaining a buffer list from the incoming queues, a list of buffers + * that contains one entry from each video buffer queue. If a buffer can't be + * obtained from every queue, the buffers are returned back to the queue. + */ +static int buffer_list_get(struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_queue *aq; + unsigned long flags; + unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING; + + bl->nbufs = 0; + INIT_LIST_HEAD(&bl->head); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_buffer *ib; + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->incoming)) { + spin_unlock_irqrestore(&aq->lock, flags); + if (!list_empty(&bl->head)) + ipu6_isys_buffer_list_queue(bl, buf_flag, 0); + return -ENODATA; + } + + ib = list_last_entry(&aq->incoming, + struct ipu6_isys_buffer, head); + + dev_dbg(dev, "buffer: %s: buffer %u\n", + ipu6_isys_queue_to_video(aq)->vdev.name, + ipu6_isys_buffer_to_vb2_buffer(ib)->index); + list_del(&ib->head); + list_add(&ib->head, &bl->head); + spin_unlock_irqrestore(&aq->lock, flags); + + bl->nbufs++; + } + + dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs); + + return 0; +} + +void +ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + + set->output_pins[aq->fw_output].addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; +} + +/* + * Convert a buffer list to a isys fw ABI framebuffer set. The + * buffer list is not modified. + */ +#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) +void +ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_isys_buffer *ib; + + WARN_ON(!bl->nbufs); + + set->send_irq_sof = 1; + set->send_resp_sof = 1; + set->send_irq_eof = 0; + set->send_resp_eof = 0; + + if (stream->streaming) + set->send_irq_capture_ack = 0; + else + set->send_irq_capture_ack = 1; + set->send_irq_capture_done = 0; + + set->send_resp_capture_ack = 1; + set->send_resp_capture_done = 1; + if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { + set->send_resp_capture_ack = 0; + set->send_resp_capture_done = 0; + } + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct ipu6_isys_queue *aq = + vb2_queue_to_isys_queue(vb->vb2_queue); + + if (WARN_ON_ONCE(ib->type != IPU6_ISYS_VIDEO_BUFFER)) + continue; + + if (aq->fill_frame_buf_set) + aq->fill_frame_buf_set(vb, set); + } +} + +/* Start streaming for real. The buffer list must be available. */ +static int ipu6_isys_stream_start(struct ipu6_isys_video *av, + struct ipu6_isys_buffer_list *bl, bool error) +{ + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu6_isys_buffer_list __bl; + int ret; + + mutex_lock(&stream->isys->stream_mutex); + ret = ipu6_isys_video_set_streaming(av, 1, bl); + mutex_unlock(&stream->isys->stream_mutex); + if (ret) + goto out_requeue; + + stream->streaming = 1; + + bl = &__bl; + + do { + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct isys_fw_msgs *msg; + u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; + + ret = buffer_list_get(stream, bl); + if (ret < 0) + break; + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); + ipu6_fw_isys_dump_frame_buff_set(dev, buf, + stream->nr_output_pins); + ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, + 0); + + ret = ipu6_fw_isys_complex_cmd(stream->isys, + stream->stream_handle, buf, + msg->dma_addr, sizeof(*buf), + send_type); + } while (!WARN_ON(ret)); + + return 0; + +out_requeue: + if (bl && bl->nbufs) + ipu6_isys_buffer_list_queue(bl, + (IPU6_ISYS_BUFFER_LIST_FL_INCOMING | + error) ? + IPU6_ISYS_BUFFER_LIST_FL_SET_STATE : + 0, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + flush_firmware_streamon_fail(stream); + + return ret; +} + +static void buf_queue(struct vb2_buffer *vb) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_buffer *ib = vb2_buffer_to_ipu6_isys_buffer(vb); + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pipeline *media_pipe = + media_entity_pipeline(&av->vdev.entity); + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_buffer_list bl; + struct isys_fw_msgs *msg; + unsigned long flags; + dma_addr_t dma; + unsigned int i; + int ret; + + dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name); + + for (i = 0; i < vb->num_planes; i++) { + dma = vb2_dma_contig_plane_dma_addr(vb, i); + dev_dbg(dev, "iova: plane %u iova %pad\n", i, &dma); + } + + spin_lock_irqsave(&aq->lock, flags); + list_add(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (!media_pipe || !vb->vb2_queue->start_streaming_called) { + dev_dbg(dev, "media pipeline is not ready for %s\n", + av->vdev.name); + return; + } + + mutex_lock(&stream->mutex); + + if (stream->nr_streaming != stream->nr_queues) { + dev_dbg(dev, "not streaming yet, adding to incoming\n"); + goto out; + } + + /* + * We just put one buffer to the incoming list of this queue + * (above). Let's see whether all queues in the pipeline would + * have a buffer. + */ + ret = buffer_list_get(stream, &bl); + if (ret < 0) { + dev_warn(dev, "No buffers available\n"); + goto out; + } + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out; + } + + buf = &msg->fw_msg.frame; + + ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); + + ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); + + if (!stream->streaming) { + ret = ipu6_isys_stream_start(av, &bl, true); + if (ret) + dev_err(dev, "stream start failed.\n"); + goto out; + } + + /* + * We must queue the buffers in the buffer list to the + * appropriate video buffer queues BEFORE passing them to the + * firmware since we could get a buffer event back before we + * have queued them ourselves to the active queue. + */ + ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + + ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, + buf, msg->dma_addr, sizeof(*buf), + IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); + if (ret < 0) + dev_err(dev, "send stream capture failed\n"); + +out: + mutex_unlock(&stream->mutex); +} + +int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) +{ + struct v4l2_mbus_framefmt format; + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pad *remote_pad = + media_pad_remote_pad_first(av->vdev.entity.pads); + struct v4l2_subdev *sd; + u32 r_stream; + int ret; + + if (!remote_pad) + return -ENOTCONN; + + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index); + + ret = ipu6_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream, + &format); + + if (ret) { + dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", + sd->entity.name, remote_pad->index, r_stream); + return ret; + } + + if (format.width != av->mpix.width || + format.height != av->mpix.height) { + dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", + av->mpix.width, av->mpix.height, + format.width, format.height); + return -EINVAL; + } + + if (format.field != av->mpix.field) { + dev_dbg(dev, "wrong field value 0x%8.8x (0x%8.8x expected)\n", + av->mpix.field, format.field); + return -EINVAL; + } + + if (format.code != av->pfmt->code) { + dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n", + av->pfmt->code, format.code); + return -EINVAL; + } + + return 0; +} + +static void return_buffers(struct ipu6_isys_queue *aq, + enum vb2_buffer_state state) +{ + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_buffer *ib; + bool need_reset = false; + unsigned long flags; + + spin_lock_irqsave(&aq->lock, flags); + while (!list_empty(&aq->incoming)) { + struct vb2_buffer *vb; + + ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer, + head); + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + dev_dbg(dev, "%s: stop_streaming incoming %u\n", + ipu6_isys_queue_to_video(vb2_queue_to_isys_queue + (vb->vb2_queue))->vdev.name, + vb->index); + + spin_lock_irqsave(&aq->lock, flags); + } + + /* + * Something went wrong (FW crash / HW hang / not all buffers + * returned from isys) if there are still buffers queued in active + * queue. We have to clean up places a bit. + */ + while (!list_empty(&aq->active)) { + struct vb2_buffer *vb; + + ib = list_first_entry(&aq->active, struct ipu6_isys_buffer, + head); + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + dev_warn(dev, "%s: cleaning active queue %u\n", + ipu6_isys_queue_to_video(vb2_queue_to_isys_queue + (vb->vb2_queue))->vdev.name, + vb->index); + + spin_lock_irqsave(&aq->lock, flags); + need_reset = true; + } + + spin_unlock_irqrestore(&aq->lock, flags); + + if (need_reset) { + mutex_lock(&av->isys->mutex); + av->isys->need_reset = true; + mutex_unlock(&av->isys->mutex); + } +} + +static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av) +{ + video_device_pipeline_stop(&av->vdev); + ipu6_isys_put_stream(av->stream); + av->stream = NULL; +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_buffer_list __bl, *bl = NULL; + struct ipu6_isys_stream *stream; + struct media_entity *source_entity = NULL; + int nr_queues, ret; + + dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", + av->vdev.name, av->mpix.width, av->mpix.height, + av->pfmt->css_pixelformat); + + ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues); + if (ret < 0) { + dev_err(dev, "failed to setup video\n"); + goto out_return_buffers; + } + + ret = aq->link_fmt_validate(aq); + if (ret) { + dev_err(dev, + "%s: link format validation failed (%d)\n", + av->vdev.name, ret); + goto out_pipeline_stop; + } + + ret = ipu6_isys_fw_open(av->isys); + if (ret) + goto out_pipeline_stop; + + stream = av->stream; + mutex_lock(&stream->mutex); + if (!stream->nr_streaming) { + ret = ipu6_isys_video_prepare_stream(av, source_entity, + nr_queues); + if (ret) + goto out_fw_close; + } + + stream->nr_streaming++; + dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, + stream->nr_queues); + list_add(&aq->node, &stream->queues); + + ipu6_isys_set_csi2_streams_status(av, true); + + ipu6_isys_configure_stream_watermark(av, true); + ipu6_isys_update_stream_watermark(av, true); + + if (stream->nr_streaming != stream->nr_queues) + goto out; + + bl = &__bl; + ret = buffer_list_get(stream, bl); + if (ret < 0) { + dev_dbg(dev, + "no buffer available, postponing streamon\n"); + goto out; + } + + ret = ipu6_isys_stream_start(av, bl, false); + if (ret) + goto out_stream_start; + +out: + mutex_unlock(&stream->mutex); + + return 0; + +out_stream_start: + list_del(&aq->node); + stream->nr_streaming--; + +out_fw_close: + mutex_unlock(&stream->mutex); + ipu6_isys_fw_close(av->isys); + +out_pipeline_stop: + ipu6_isys_stream_cleanup(av); + +out_return_buffers: + return_buffers(aq, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct ipu6_isys_stream *stream = av->stream; + + ipu6_isys_set_csi2_streams_status(av, false); + + mutex_lock(&stream->mutex); + + ipu6_isys_update_stream_watermark(av, false); + + mutex_lock(&av->isys->stream_mutex); + if (stream->nr_streaming == stream->nr_queues && stream->streaming) + ipu6_isys_video_set_streaming(av, 0, NULL); + mutex_unlock(&av->isys->stream_mutex); + + stream->nr_streaming--; + list_del(&aq->node); + stream->streaming = 0; + mutex_unlock(&stream->mutex); + + ipu6_isys_stream_cleanup(av); + + return_buffers(aq, VB2_BUF_STATE_ERROR); + + ipu6_isys_fw_close(av->isys); +} + +static unsigned int +get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info) +{ + u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0]; + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + + /* + * The timestamp is invalid as no TSC in some FPGA platform, + * so get the sequence from pipeline directly in this case. + */ + if (time == 0) + return atomic_read(&stream->sequence) - 1; + + for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) + if (time == stream->seq[i].timestamp) { + dev_dbg(dev, "sof: using seq nr %u for ts %llu\n", + stream->seq[i].sequence, time); + return stream->seq[i].sequence; + } + + for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) + dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n", + stream->seq[i].sequence, stream->seq[i].timestamp); + + return 0; +} + +static u64 get_sof_ns_delta(struct ipu6_isys_video *av, + struct ipu6_fw_isys_resp_info_abi *info) +{ + struct ipu6_bus_device *adev = av->isys->adev; + struct ipu6_device *isp = adev->isp; + u64 delta, tsc_now; + + ipu6_buttress_tsc_read(isp, &tsc_now); + if (!tsc_now) + return 0; + + delta = tsc_now - ((u64)info->timestamp[1] << 32 | info->timestamp[0]); + + return ipu6_buttress_tsc_ticks_to_ns(delta, isp); +} + +void ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, + struct ipu6_fw_isys_resp_info_abi *info) +{ + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + u64 ns; + u32 sequence; + + ns = ktime_get_ns() - get_sof_ns_delta(av, info); + sequence = get_sof_sequence_by_timestamp(stream, info); + + vbuf->vb2_buf.timestamp = ns; + vbuf->sequence = sequence; + + dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n", + av->vdev.name, ktime_get_ns(), sequence); + dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index, + vbuf->vb2_buf.timestamp); +} + +void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) +{ + struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); + + if (atomic_read(&ib->str2mmio_flag)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + /* + * Operation on buffer is ended with error and will be reported + * to the userspace when it is de-queued + */ + atomic_set(&ib->str2mmio_flag, 0); + } else { + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } +} + +void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info) +{ + struct ipu6_isys_queue *aq = stream->output_pins[info->pin_id].aq; + struct ipu6_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu6_isys_buffer *ib; + struct vb2_buffer *vb; + unsigned long flags; + bool first = true; + struct vb2_v4l2_buffer *buf; + + dev_dbg(dev, "buffer: %s: received buffer %8.8x\n", + ipu6_isys_queue_to_video(aq)->vdev.name, info->pin.addr); + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->active)) { + spin_unlock_irqrestore(&aq->lock, flags); + dev_err(dev, "active queue empty\n"); + return; + } + + list_for_each_entry_reverse(ib, &aq->active, head) { + dma_addr_t addr; + + vb = ipu6_isys_buffer_to_vb2_buffer(ib); + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (info->pin.addr != addr) { + if (first) + dev_err(dev, "Unexpected buffer address %pad\n", + &addr); + first = false; + continue; + } + + if (info->error_info.error == + IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) { + /* + * Check for error message: + * 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' + */ + atomic_set(&ib->str2mmio_flag, 1); + } + dev_dbg(dev, "buffer: found buffer %pad\n", &addr); + + buf = to_vb2_v4l2_buffer(vb); + buf->field = V4L2_FIELD_NONE; + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + ipu6_isys_buf_calc_sequence_time(ib, info); + + ipu6_isys_queue_buf_done(ib); + + return; + } + + dev_err(dev, "Failed to find a matching video buffer"); + + spin_unlock_irqrestore(&aq->lock, flags); +} + +static const struct vb2_ops ipu6_isys_queue_ops = { + .queue_setup = queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = buf_prepare, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .buf_queue = buf_queue, +}; + +int ipu6_isys_queue_init(struct ipu6_isys_queue *aq) +{ + struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys; + struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); + int ret; + + /* no support for userptr */ + if (!aq->vbq.io_modes) + aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; + + aq->vbq.drv_priv = aq; + aq->vbq.ops = &ipu6_isys_queue_ops; + aq->vbq.lock = &av->mutex; + aq->vbq.mem_ops = &vb2_dma_contig_memops; + aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + ret = vb2_queue_init(&aq->vbq); + if (ret) + return ret; + + aq->dev = &isys->adev->auxdev.dev; + aq->vbq.dev = &isys->adev->auxdev.dev; + spin_lock_init(&aq->lock); + INIT_LIST_HEAD(&aq->active); + INIT_LIST_HEAD(&aq->incoming); + + return 0; +} + +void ipu6_isys_queue_cleanup(struct ipu6_isys_queue *aq) +{ + vb2_queue_release(&aq->vbq); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h new file mode 100644 index 000000000000..bce8ca06cb9f --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2023 Intel Corporation */ + +#ifndef IPU6_ISYS_QUEUE_H +#define IPU6_ISYS_QUEUE_H + +#include + +struct ipu6_isys_video; +struct ipu6_isys_stream; +struct ipu6_fw_isys_resp_info_abi; +struct ipu6_fw_isys_frame_buff_set_abi; + +enum ipu6_isys_buffer_type { + IPU6_ISYS_VIDEO_BUFFER, +}; + +struct ipu6_isys_queue { + struct vb2_queue vbq; + struct list_head node; + struct device *dev; + /* + * @lock: serialise access to queued and pre_streamon_queued + */ + spinlock_t lock; + struct list_head active; + struct list_head incoming; + unsigned int fw_output; + int (*buf_prepare)(struct vb2_buffer *vb); + void (*fill_frame_buf_set)(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set); + int (*link_fmt_validate)(struct ipu6_isys_queue *aq); +}; + +struct ipu6_isys_buffer { + struct list_head head; + enum ipu6_isys_buffer_type type; + atomic_t str2mmio_flag; +}; + +struct ipu6_isys_video_buffer { + struct vb2_v4l2_buffer vb_v4l2; + struct ipu6_isys_buffer ib; +}; + +#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0) +#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1) +#define IPU6_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2) + +struct ipu6_isys_buffer_list { + struct list_head head; + unsigned int nbufs; +}; + +#define vb2_queue_to_isys_queue(__vb2) \ + container_of(__vb2, struct ipu6_isys_queue, vbq) + +#define ipu6_isys_to_isys_video_buffer(__ib) \ + container_of(__ib, struct ipu6_isys_video_buffer, ib) + +#define vb2_buffer_to_ipu6_isys_video_buffer(__vb) \ + container_of(to_vb2_v4l2_buffer(__vb), \ + struct ipu6_isys_video_buffer, vb_v4l2) + +#define ipu6_isys_buffer_to_vb2_buffer(__ib) \ + (&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf) + +#define vb2_buffer_to_ipu6_isys_buffer(__vb) \ + (&vb2_buffer_to_ipu6_isys_video_buffer(__vb)->ib) + +int ipu6_isys_buf_prepare(struct vb2_buffer *vb); + +void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state); +void +ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu6_fw_isys_frame_buff_set_abi *set); +void +ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, + struct ipu6_isys_stream *stream, + struct ipu6_isys_buffer_list *bl); +int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq); + +void +ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, + struct ipu6_fw_isys_resp_info_abi *info); +void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib); +void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info); +int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); +void ipu6_isys_queue_cleanup(struct ipu6_isys_queue *aq); + +#endif /* IPU6_ISYS_QUEUE_H */ diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c new file mode 100644 index 000000000000..f81934a301b4 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c @@ -0,0 +1,1239 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2023 Intel Corporation + */ + +#include +#include +#include + +#include +#include +#include + +#include "ipu6-bus.h" +#include "ipu6-cpd.h" +#include "ipu6-fw-com.h" +#include "ipu6-isys.h" +#include "ipu6-platform-buttress-regs.h" +#include "ipu6-platform-isys-csi2-reg.h" +#include "ipu6-platform-regs.h" + +const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { + {V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGRBG12, 16, 12, MEDIA_BUS_FMT_SGRBG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SRGGB12, 16, 12, MEDIA_BUS_FMT_SRGGB12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SBGGR10, 16, 10, MEDIA_BUS_FMT_SBGGR10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGBRG10, 16, 10, MEDIA_BUS_FMT_SGBRG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGRBG10, 16, 10, MEDIA_BUS_FMT_SGRBG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SRGGB10, 16, 10, MEDIA_BUS_FMT_SRGGB10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SBGGR8, 8, 8, MEDIA_BUS_FMT_SBGGR8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SGBRG8, 8, 8, MEDIA_BUS_FMT_SGBRG8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SGRBG8, 8, 8, MEDIA_BUS_FMT_SGRBG8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8, + IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SGRBG12P, 12, 12, MEDIA_BUS_FMT_SGRBG12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SRGGB12P, 12, 12, MEDIA_BUS_FMT_SRGGB12_1X12, + IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SBGGR10P, 10, 10, MEDIA_BUS_FMT_SBGGR10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_SGBRG10P, 10, 10, MEDIA_BUS_FMT_SGBRG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_SGRBG10P, 10, 10, MEDIA_BUS_FMT_SGRBG10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10, + IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, + {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, + {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, + IPU6_FW_ISYS_FRAME_FORMAT_RGB565}, + {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, + IPU6_FW_ISYS_FRAME_FORMAT_RGBA888}, +}; + +static int video_open(struct file *file) +{ + struct ipu6_isys_video *av = video_drvdata(file); + struct ipu6_isys *isys = av->isys; + struct ipu6_bus_device *adev = isys->adev; + + mutex_lock(&isys->mutex); + if (isys->need_reset) { + mutex_unlock(&isys->mutex); + dev_warn(&adev->auxdev.dev, "isys power cycle required\n"); + return -EIO; + } + mutex_unlock(&isys->mutex); + + return v4l2_fh_open(file); +} + +static int video_release(struct file *file) +{ + return vb2_fop_release(file); +} + +static const struct ipu6_isys_pixelformat * +ipu6_isys_get_pixelformat(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { + const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i]; + + if (pfmt->pixelformat == pixelformat) + return pfmt; + } + + return &ipu6_isys_pfmts[0]; +} + +int ipu6_isys_vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + strscpy(cap->driver, IPU6_ISYS_NAME, sizeof(cap->driver)); + strscpy(cap->card, av->isys->media_dev.model, sizeof(cap->card)); + + return 0; +} + +int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + unsigned int i, found = 0; + + if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts)) + return -EINVAL; + + if (!f->mbus_code) { + f->flags = 0; + f->pixelformat = ipu6_isys_pfmts[f->index].pixelformat; + f->mbus_code = ipu6_isys_pfmts[f->index].code; + return 0; + } + + for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { + if (f->mbus_code != ipu6_isys_pfmts[i].code) + continue; + + if (f->index == found) { + f->flags = 0; + f->pixelformat = ipu6_isys_pfmts[i].pixelformat; + return 0; + } + found++; + } + + return -EINVAL; +} + +static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = IPU6_ISYS_MIN_WIDTH; + fsize->stepwise.max_width = IPU6_ISYS_MAX_WIDTH; + fsize->stepwise.min_height = IPU6_ISYS_MIN_HEIGHT; + fsize->stepwise.max_height = IPU6_ISYS_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 2; + + return 0; +} + +static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *fmt) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + fmt->fmt.pix_mp = av->mpix; + + return 0; +} + +static const struct ipu6_isys_pixelformat * +ipu6_isys_video_try_fmt_vid_mplane(struct ipu6_isys_video *av, + struct v4l2_pix_format_mplane *mpix) +{ + const struct ipu6_isys_pixelformat *pfmt = + ipu6_isys_get_pixelformat(mpix->pixelformat); + + mpix->pixelformat = pfmt->pixelformat; + mpix->num_planes = 1; + + mpix->width = clamp(mpix->width, IPU6_ISYS_MIN_WIDTH, + IPU6_ISYS_MAX_WIDTH); + mpix->height = clamp(mpix->height, IPU6_ISYS_MIN_HEIGHT, + IPU6_ISYS_MAX_HEIGHT); + + if (pfmt->bpp != pfmt->bpp_packed) + mpix->plane_fmt[0].bytesperline = + mpix->width * DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE); + else + mpix->plane_fmt[0].bytesperline = + DIV_ROUND_UP((unsigned int)mpix->width * pfmt->bpp, + BITS_PER_BYTE); + + mpix->plane_fmt[0].bytesperline = ALIGN(mpix->plane_fmt[0].bytesperline, + av->isys->line_align); + + /* + * (height + 1) * bytesperline due to a hardware issue: the DMA unit + * is a power of two, and a line should be transferred as few units + * as possible. The result is that up to line length more data than + * the image size may be transferred to memory after the image. + * Another limitation is the GDA allocation unit size. For low + * resolution it gives a bigger number. Use larger one to avoid + * memory corruption. + */ + mpix->plane_fmt[0].sizeimage = + max(max(mpix->plane_fmt[0].sizeimage, + mpix->plane_fmt[0].bytesperline * mpix->height + + max(mpix->plane_fmt[0].bytesperline, + av->isys->pdata->ipdata->isys_dma_overshoot)), 1U); + + memset(mpix->plane_fmt[0].reserved, 0, + sizeof(mpix->plane_fmt[0].reserved)); + + mpix->field = V4L2_FIELD_NONE; + + mpix->colorspace = V4L2_COLORSPACE_RAW; + mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + mpix->quantization = V4L2_QUANTIZATION_DEFAULT; + mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return pfmt; +} + +static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + if (av->aq.vbq.streaming) + return -EBUSY; + + av->pfmt = ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); + av->mpix = f->fmt.pix_mp; + + return 0; +} + +static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu6_isys_video *av = video_drvdata(file); + + ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); + + return 0; +} + +static int link_validate(struct media_link *link) +{ + struct ipu6_isys_video *av = + container_of(link->sink, struct ipu6_isys_video, pad); + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_subdev_state *s_state; + struct v4l2_subdev *s_sd; + struct v4l2_mbus_framefmt *s_fmt; + struct media_pad *s_pad; + u32 s_stream; + int ret = -EPIPE; + + if (!link->source->entity) + return ret; + + s_sd = media_entity_to_v4l2_subdev(link->source->entity); + s_state = v4l2_subdev_get_unlocked_active_state(s_sd); + if (!s_state) + return ret; + + dev_dbg(dev, "validating link \"%s\":%u -> \"%s\"\n", + link->source->entity->name, link->source->index, + link->sink->entity->name); + + s_pad = media_pad_remote_pad_first(&av->pad); + s_stream = ipu6_isys_get_src_stream_by_src_pad(s_sd, s_pad->index); + + v4l2_subdev_lock_state(s_state); + + s_fmt = v4l2_subdev_state_get_stream_format(s_state, s_pad->index, + s_stream); + if (!s_fmt) { + dev_err(dev, "failed to get source pad format\n"); + goto unlock; + } + + if (s_fmt->width != av->mpix.width || + s_fmt->height != av->mpix.height || s_fmt->code != av->pfmt->code) { + dev_err(dev, "format mismatch %dx%d,%x != %dx%d,%x\n", + s_fmt->width, s_fmt->height, s_fmt->code, + av->mpix.width, av->mpix.height, av->pfmt->code); + goto unlock; + } + + v4l2_subdev_unlock_state(s_state); + + return 0; +unlock: + v4l2_subdev_unlock_state(s_state); + + return ret; +} + +static void get_stream_opened(struct ipu6_isys_video *av) +{ + unsigned long flags; + + spin_lock_irqsave(&av->isys->streams_lock, flags); + av->isys->stream_opened++; + spin_unlock_irqrestore(&av->isys->streams_lock, flags); +} + +static void put_stream_opened(struct ipu6_isys_video *av) +{ + unsigned long flags; + + spin_lock_irqsave(&av->isys->streams_lock, flags); + av->isys->stream_opened--; + spin_unlock_irqrestore(&av->isys->streams_lock, flags); +} + +static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, + struct ipu6_fw_isys_stream_cfg_data_abi *cfg) +{ + struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); + struct ipu6_fw_isys_input_pin_info_abi *input_pin; + struct ipu6_fw_isys_output_pin_info_abi *output_pin; + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_queue *aq = &av->aq; + struct v4l2_mbus_framefmt fmt; + struct v4l2_rect v4l2_crop; + struct ipu6_isys *isys = av->isys; + struct device *dev = &isys->adev->auxdev.dev; + int input_pins = cfg->nof_input_pins++; + int output_pins; + u32 src_stream; + int ret; + + src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); + ret = ipu6_isys_get_stream_pad_fmt(sd, src_pad->index, src_stream, + &fmt); + if (ret < 0) { + dev_err(dev, "can't get stream format (%d)\n", ret); + return ret; + } + + ret = ipu6_isys_get_stream_pad_crop(sd, src_pad->index, src_stream, + &v4l2_crop); + if (ret < 0) { + dev_err(dev, "can't get stream crop (%d)\n", ret); + return ret; + } + + input_pin = &cfg->input_pins[input_pins]; + input_pin->input_res.width = fmt.width; + input_pin->input_res.height = fmt.height; + input_pin->dt = av->dt; + input_pin->bits_per_pix = av->pfmt->bpp_packed; + input_pin->mapped_dt = 0x40; /* invalid mipi data type */ + input_pin->mipi_decompression = 0; + input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; + input_pin->mipi_store_mode = av->pfmt->bpp == av->pfmt->bpp_packed ? + IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : + IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; + input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + stream->output_pins[output_pins].pin_ready = ipu6_isys_queue_buf_ready; + stream->output_pins[output_pins].aq = aq; + + output_pin = &cfg->output_pins[output_pins]; + output_pin->input_pin_id = input_pins; + output_pin->output_res.width = av->mpix.width; + output_pin->output_res.height = av->mpix.height; + + output_pin->stride = av->mpix.plane_fmt[0].bytesperline; + if (av->pfmt->bpp != av->pfmt->bpp_packed) + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; + else + output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; + output_pin->ft = av->pfmt->css_pixelformat; + output_pin->send_irq = 1; + memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); + output_pin->s2m_pixel_soc_pixel_remapping = + S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + output_pin->csi_be_soc_pixel_remapping = + CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; + + output_pin->snoopable = true; + output_pin->error_handling_enable = false; + output_pin->sensor_type = isys->sensor_type++; + if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) + isys->sensor_type = isys->pdata->ipdata->sensor_type_start; + + return 0; +} + +static int start_stream_firmware(struct ipu6_isys_video *av, + struct ipu6_isys_buffer_list *bl) +{ + struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; + struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &av->isys->adev->auxdev.dev; + struct isys_fw_msgs *msg = NULL; + struct ipu6_isys_queue *aq; + int ret, retout, tout; + u16 send_type; + + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + stream_cfg = &msg->fw_msg.stream; + stream_cfg->src = stream->stream_source; + stream_cfg->vc = stream->vc; + stream_cfg->isl_use = 0; + stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); + + ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); + if (ret < 0) { + ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); + return ret; + } + } + + ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg); + + stream->nr_output_pins = stream_cfg->nof_output_pins; + + reinit_completion(&stream->stream_open_completion); + + ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, + stream_cfg, msg->dma_addr, + sizeof(*stream_cfg), + IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); + if (ret < 0) { + dev_err(dev, "can't open stream (%d)\n", ret); + ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); + return ret; + } + + get_stream_opened(av); + + tout = wait_for_completion_timeout(&stream->stream_open_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + + ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); + + if (!tout) { + dev_err(dev, "stream open time out\n"); + ret = -ETIMEDOUT; + goto out_put_stream_opened; + } + if (stream->error) { + dev_err(dev, "stream open error: %d\n", stream->error); + ret = -EIO; + goto out_put_stream_opened; + } + dev_dbg(dev, "start stream: open complete\n"); + + if (bl) { + msg = ipu6_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out_put_stream_opened; + } + buf = &msg->fw_msg.frame; + ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); + ipu6_isys_buffer_list_queue(bl, + IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + } + + reinit_completion(&stream->stream_start_completion); + + if (bl) { + send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; + ipu6_fw_isys_dump_frame_buff_set(dev, buf, + stream_cfg->nof_output_pins); + ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, + buf, msg->dma_addr, + sizeof(*buf), send_type); + } else { + send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + send_type); + } + + if (ret < 0) { + dev_err(dev, "can't start streaming (%d)\n", ret); + goto out_stream_close; + } + + tout = wait_for_completion_timeout(&stream->stream_start_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) { + dev_err(dev, "stream start time out\n"); + ret = -ETIMEDOUT; + goto out_stream_close; + } + if (stream->error) { + dev_err(dev, "stream start error: %d\n", stream->error); + ret = -EIO; + goto out_stream_close; + } + dev_dbg(dev, "start stream: complete\n"); + + return 0; + +out_stream_close: + reinit_completion(&stream->stream_close_completion); + + retout = ipu6_fw_isys_simple_cmd(av->isys, + stream->stream_handle, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + if (retout < 0) { + dev_dbg(dev, "can't close stream (%d)\n", retout); + goto out_put_stream_opened; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_err(dev, "stream close time out\n"); + else if (stream->error) + dev_err(dev, "stream close error: %d\n", stream->error); + else + dev_dbg(dev, "stream close complete\n"); + +out_put_stream_opened: + put_stream_opened(av); + + return ret; +} + +static void stop_streaming_firmware(struct ipu6_isys_video *av) +{ + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu6_isys_stream *stream = av->stream; + int ret, tout; + + reinit_completion(&stream->stream_stop_completion); + + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); + + if (ret < 0) { + dev_err(dev, "can't stop stream (%d)\n", ret); + return; + } + + tout = wait_for_completion_timeout(&stream->stream_stop_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_warn(dev, "stream stop time out\n"); + else if (stream->error) + dev_warn(dev, "stream stop error: %d\n", stream->error); + else + dev_dbg(dev, "stop stream: complete\n"); +} + +static void close_streaming_firmware(struct ipu6_isys_video *av) +{ + struct ipu6_isys_stream *stream = av->stream; + struct device *dev = &av->isys->adev->auxdev.dev; + int ret, tout; + + reinit_completion(&stream->stream_close_completion); + + ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); + if (ret < 0) { + dev_err(dev, "can't close stream (%d)\n", ret); + return; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, + IPU6_FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_warn(dev, "stream close time out\n"); + else if (stream->error) + dev_warn(dev, "stream close error: %d\n", stream->error); + else + dev_dbg(dev, "close stream: complete\n"); + + put_stream_opened(av); +} + +int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, + struct media_entity *source_entity, + int nr_queues) +{ + struct ipu6_isys_stream *stream = av->stream; + struct ipu6_isys_csi2 *csi2; + + if (WARN_ON(stream->nr_streaming)) + return -EINVAL; + + stream->nr_queues = nr_queues; + atomic_set(&stream->sequence, 0); + + stream->seq_index = 0; + memset(stream->seq, 0, sizeof(stream->seq)); + + if (WARN_ON(!list_empty(&stream->queues))) + return -EINVAL; + + stream->stream_source = stream->asd->source; + csi2 = ipu6_isys_subdev_to_csi2(stream->asd); + csi2->receiver_errors = 0; + stream->source_entity = source_entity; + + dev_dbg(&av->isys->adev->auxdev.dev, + "prepare stream: external entity %s\n", + stream->source_entity->name); + + return 0; +} + +void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, + bool state) +{ + struct ipu6_isys *isys = av->isys; + struct ipu6_isys_csi2 *csi2 = NULL; + struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; + struct device *dev = &isys->adev->auxdev.dev; + struct v4l2_mbus_framefmt format; + struct v4l2_subdev *esd; + struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; + unsigned int bpp, lanes; + s64 link_freq = 0; + u64 pixel_rate = 0; + int ret; + + if (!state) + return; + + esd = media_entity_to_v4l2_subdev(av->stream->source_entity); + + av->watermark.width = av->mpix.width; + av->watermark.height = av->mpix.height; + av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift; + av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size; + + ret = v4l2_g_ctrl(esd->ctrl_handler, &hb); + if (!ret && hb.value >= 0) + av->watermark.hblank = hb.value; + else + av->watermark.hblank = 0; + + csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd); + link_freq = ipu6_isys_csi2_get_link_freq(csi2); + if (link_freq > 0) { + lanes = csi2->nlanes; + ret = ipu6_isys_get_stream_pad_fmt(&csi2->asd.sd, 0, + av->source_stream, &format); + if (!ret) { + bpp = ipu6_isys_mbus_code_to_bpp(format.code); + pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); + } + } + + av->watermark.pixel_rate = pixel_rate; + + if (!pixel_rate) { + mutex_lock(&iwake_watermark->mutex); + iwake_watermark->force_iwake_disable = true; + mutex_unlock(&iwake_watermark->mutex); + dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n", + av->stream->source_entity->name); + } +} + +static void calculate_stream_datarate(struct ipu6_isys_video *av) +{ + struct video_stream_watermark *watermark = &av->watermark; + u32 bpp = av->pfmt->bpp; + u32 pages_per_line, pb_bytes_per_line, pixels_per_line, bytes_per_line; + u64 line_time_ns, stream_data_rate; + u16 shift, size; + + shift = watermark->sram_gran_shift; + size = watermark->sram_gran_size; + + pixels_per_line = watermark->width + watermark->hblank; + line_time_ns = div_u64(pixels_per_line * NSEC_PER_SEC, + watermark->pixel_rate); + bytes_per_line = watermark->width * bpp / 8; + pages_per_line = DIV_ROUND_UP(bytes_per_line, size); + pb_bytes_per_line = pages_per_line << shift; + stream_data_rate = div64_u64(pb_bytes_per_line * 1000, line_time_ns); + + watermark->stream_data_rate = stream_data_rate; +} + +void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state) +{ + struct isys_iwake_watermark *iwake_watermark = + &av->isys->iwake_watermark; + + if (!av->watermark.pixel_rate) + return; + + if (state) { + calculate_stream_datarate(av); + mutex_lock(&iwake_watermark->mutex); + list_add(&av->watermark.stream_node, + &iwake_watermark->video_list); + mutex_unlock(&iwake_watermark->mutex); + } else { + av->watermark.stream_data_rate = 0; + mutex_lock(&iwake_watermark->mutex); + list_del(&av->watermark.stream_node); + mutex_unlock(&iwake_watermark->mutex); + } + + update_watermark_setting(av->isys); +} + +void ipu6_isys_put_stream(struct ipu6_isys_stream *stream) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + unsigned int i; + unsigned long flags; + + if (!stream) { + dev_err(dev, "no available stream\n"); + return; + } + + spin_lock_irqsave(&stream->isys->streams_lock, flags); + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (&stream->isys->streams[i] == stream) { + if (stream->isys->streams_ref_count[i] > 0) + stream->isys->streams_ref_count[i]--; + else + dev_warn(dev, "invalid stream %d\n", i); + + break; + } + } + spin_unlock_irqrestore(&stream->isys->streams_lock, flags); +} + +static struct ipu6_isys_stream * +ipu6_isys_get_stream(struct ipu6_isys_video *av, struct ipu6_isys_subdev *asd) +{ + struct ipu6_isys_stream *stream = NULL; + struct ipu6_isys *isys = av->isys; + unsigned long flags; + unsigned int i; + u8 vc = av->vc; + + if (!isys) + return NULL; + + spin_lock_irqsave(&isys->streams_lock, flags); + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (isys->streams_ref_count[i] && isys->streams[i].vc == vc && + isys->streams[i].asd == asd) { + isys->streams_ref_count[i]++; + stream = &isys->streams[i]; + break; + } + } + + if (!stream) { + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (!isys->streams_ref_count[i]) { + isys->streams_ref_count[i]++; + stream = &isys->streams[i]; + stream->vc = vc; + stream->asd = asd; + break; + } + } + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle) +{ + unsigned long flags; + struct ipu6_isys_stream *stream = NULL; + + if (!isys) + return NULL; + + if (stream_handle >= IPU6_ISYS_MAX_STREAMS) { + dev_err(&isys->adev->auxdev.dev, + "stream_handle %d is invalid\n", stream_handle); + return NULL; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + if (isys->streams_ref_count[stream_handle] > 0) { + isys->streams_ref_count[stream_handle]++; + stream = &isys->streams[stream_handle]; + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc) +{ + struct ipu6_isys_stream *stream = NULL; + unsigned long flags; + unsigned int i; + + if (!isys) + return NULL; + + if (source < 0) { + dev_err(&stream->isys->adev->auxdev.dev, + "query stream with invalid port number\n"); + return NULL; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { + if (!isys->streams_ref_count[i]) + continue; + + if (isys->streams[i].stream_source == source && + isys->streams[i].vc == vc) { + stream = &isys->streams[i]; + isys->streams_ref_count[i]++; + break; + } + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +static u64 get_stream_mask_by_pipeline(struct ipu6_isys_video *av) +{ + struct media_pipeline *pipeline = + media_entity_pipeline(&av->vdev.entity); + struct media_entity *entity; + unsigned int i; + u64 stream_mask = 0; + + for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) { + entity = &av->isys->av[i].vdev.entity; + if (pipeline == media_entity_pipeline(entity)) + stream_mask |= BIT_ULL(av->isys->av[i].source_stream); + } + + return stream_mask; +} + +int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + struct ipu6_isys_buffer_list *bl) +{ + struct v4l2_subdev_krouting *routing; + struct ipu6_isys_stream *stream = av->stream; + struct v4l2_subdev_state *subdev_state; + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_subdev *sd = NULL; + struct v4l2_subdev *ssd = NULL; + struct media_pad *r_pad; + struct media_pad *s_pad = NULL; + u32 sink_pad, sink_stream; + u64 r_stream; + u64 stream_mask = 0; + int ret = 0; + + dev_dbg(dev, "set stream: %d\n", state); + + if (WARN(!stream->source_entity, "No source entity for stream\n")) + return -ENODEV; + + ssd = media_entity_to_v4l2_subdev(stream->source_entity); + sd = &stream->asd->sd; + r_pad = media_pad_remote_pad_first(&av->pad); + r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); + + subdev_state = v4l2_subdev_lock_and_get_active_state(sd); + routing = &subdev_state->routing; + ret = v4l2_subdev_routing_find_opposite_end(routing, r_pad->index, + r_stream, &sink_pad, + &sink_stream); + v4l2_subdev_unlock_state(subdev_state); + if (ret) + return ret; + + s_pad = media_pad_remote_pad_first(&stream->asd->pad[sink_pad]); + + stream_mask = get_stream_mask_by_pipeline(av); + if (!state) { + stop_streaming_firmware(av); + + /* stop external sub-device now. */ + dev_dbg(dev, "disable streams 0x%llx of %s\n", stream_mask, + ssd->name); + ret = v4l2_subdev_disable_streams(ssd, s_pad->index, + stream_mask); + if (ret) { + dev_err(dev, "disable streams of %s failed with %d\n", + ssd->name, ret); + return ret; + } + + /* stop sub-device which connects with video */ + dev_dbg(dev, "stream off entity %s pad:%d\n", sd->name, + r_pad->index); + ret = v4l2_subdev_call(sd, video, s_stream, state); + if (ret) { + dev_err(dev, "stream off %s failed with %d\n", sd->name, + ret); + return ret; + } + close_streaming_firmware(av); + } else { + ret = start_stream_firmware(av, bl); + if (ret) { + dev_err(dev, "start stream of firmware failed\n"); + goto out_clear_stream_watermark; + } + + /* start sub-device which connects with video */ + dev_dbg(dev, "stream on %s pad %d\n", sd->name, r_pad->index); + ret = v4l2_subdev_call(sd, video, s_stream, state); + if (ret) { + dev_err(dev, "stream on %s failed with %d\n", sd->name, + ret); + goto out_media_entity_stop_streaming_firmware; + } + + /* start external sub-device now. */ + dev_dbg(dev, "enable streams 0x%llx of %s\n", stream_mask, + ssd->name); + ret = v4l2_subdev_enable_streams(ssd, s_pad->index, + stream_mask); + if (ret) { + dev_err(dev, + "enable streams 0x%llx of %s failed with %d\n", + stream_mask, stream->source_entity->name, ret); + goto out_media_entity_stop_streaming; + } + } + + av->streaming = state; + + return 0; + +out_media_entity_stop_streaming: + v4l2_subdev_disable_streams(sd, r_pad->index, BIT(r_stream)); + +out_media_entity_stop_streaming_firmware: + stop_streaming_firmware(av); + +out_clear_stream_watermark: + ipu6_isys_update_stream_watermark(av, 0); + + return ret; +} + +static const struct v4l2_ioctl_ops ioctl_ops_mplane = { + .vidioc_querycap = ipu6_isys_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt, + .vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes, + .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane, + .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, +}; + +static const struct media_entity_operations entity_ops = { + .link_validate = link_validate, +}; + +static const struct v4l2_file_operations isys_fops = { + .owner = THIS_MODULE, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = video_open, + .release = video_release, +}; + +int ipu6_isys_fw_open(struct ipu6_isys *isys) +{ + struct ipu6_bus_device *adev = isys->adev; + const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; + int ret; + + ret = pm_runtime_resume_and_get(&adev->auxdev.dev); + if (ret < 0) + return ret; + + mutex_lock(&isys->mutex); + + if (isys->ref_count++) + goto unlock; + + ipu6_configure_spc(adev->isp, &ipdata->hw_variant, + IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base, + adev->pkg_dir, adev->pkg_dir_dma_addr); + + /* + * Buffers could have been left to wrong queue at last closure. + * Move them now back to empty buffer queue. + */ + ipu6_cleanup_fw_msg_bufs(isys); + + if (isys->fwcom) { + /* + * Something went wrong in previous shutdown. As we are now + * restarting isys we can safely delete old context. + */ + dev_info(&adev->auxdev.dev, "Clearing old context\n"); + ipu6_fw_isys_cleanup(isys); + } + + ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams); + if (ret < 0) + goto out; + +unlock: + mutex_unlock(&isys->mutex); + + return 0; + +out: + isys->ref_count--; + mutex_unlock(&isys->mutex); + pm_runtime_put(&adev->auxdev.dev); + + return ret; +} + +void ipu6_isys_fw_close(struct ipu6_isys *isys) +{ + mutex_lock(&isys->mutex); + + isys->ref_count--; + if (!isys->ref_count) { + ipu6_fw_isys_close(isys); + if (isys->fwcom) { + isys->need_reset = true; + dev_warn(&isys->adev->auxdev.dev, + "failed to close fw isys\n"); + } + } + + mutex_unlock(&isys->mutex); + + if (isys->need_reset) + pm_runtime_put_sync(&isys->adev->auxdev.dev); + else + pm_runtime_put(&isys->adev->auxdev.dev); +} + +int ipu6_isys_setup_video(struct ipu6_isys_video *av, + struct media_entity **source_entity, + int *nr_queues) +{ + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc_entry entry; + struct v4l2_subdev_route *route = NULL; + struct v4l2_subdev_route *r; + struct v4l2_subdev_state *state; + struct ipu6_isys_subdev *asd; + struct v4l2_subdev *remote_sd; + struct media_pipeline *pipeline; + struct media_pad *source_pad, *remote_pad; + int ret = -EINVAL; + + remote_pad = media_pad_remote_pad_first(&av->pad); + if (!remote_pad) { + dev_dbg(dev, "failed to get remote pad\n"); + return -ENODEV; + } + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + asd = to_ipu6_isys_subdev(remote_sd); + source_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]); + if (!source_pad) { + dev_dbg(dev, "No external source entity\n"); + return -ENODEV; + } + + *source_entity = source_pad->entity; + + /* Find the root */ + state = v4l2_subdev_lock_and_get_active_state(remote_sd); + for_each_active_route(&state->routing, r) { + if (r->source_pad != remote_pad->index) + continue; + + route = r; + break; + } + + if (!route) { + v4l2_subdev_unlock_state(state); + dev_dbg(dev, "Failed to find route\n"); + return -ENODEV; + } + v4l2_subdev_unlock_state(state); + av->source_stream = route->sink_stream; + + ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, + to_ipu6_isys_csi2(remote_sd), + *source_entity, &entry, + nr_queues); + if (ret == -ENOIOCTLCMD) { + av->vc = 0; + av->dt = ipu6_isys_mbus_code_to_mipi(av->pfmt->code); + *nr_queues = 1; + } else if (!ret) { + dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", + entry.stream, entry.length, entry.bus.csi2.vc, + entry.bus.csi2.dt); + + av->vc = entry.bus.csi2.vc; + av->dt = entry.bus.csi2.dt; + } else { + dev_err(dev, "failed to get remote frame desc\n"); + return ret; + } + + pipeline = media_entity_pipeline(&av->vdev.entity); + if (!pipeline) + ret = video_device_pipeline_alloc_start(&av->vdev); + else + ret = video_device_pipeline_start(&av->vdev, pipeline); + if (ret < 0) { + dev_dbg(dev, "media pipeline start failed\n"); + return ret; + } + + av->stream = ipu6_isys_get_stream(av, asd); + if (!av->stream) { + video_device_pipeline_stop(&av->vdev); + dev_err(dev, "no available stream for firmware\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Do everything that's needed to initialise things related to video + * buffer queue, video node, and the related media entity. The caller + * is expected to assign isys field and set the name of the video + * device. + */ +int ipu6_isys_video_init(struct ipu6_isys_video *av) +{ + struct v4l2_format format = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .fmt.pix_mp = { + .width = 1920, + .height = 1080, + }, + }; + int ret; + + mutex_init(&av->mutex); + av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC | + V4L2_CAP_VIDEO_CAPTURE_MPLANE; + av->vdev.vfl_dir = VFL_DIR_RX; + + ret = ipu6_isys_queue_init(&av->aq); + if (ret) + goto out_free_watermark; + + av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad); + if (ret) + goto out_ipu6_isys_queue_cleanup; + + av->vdev.entity.ops = &entity_ops; + av->vdev.release = video_device_release_empty; + av->vdev.fops = &isys_fops; + av->vdev.v4l2_dev = &av->isys->v4l2_dev; + if (!av->vdev.ioctl_ops) + av->vdev.ioctl_ops = &ioctl_ops_mplane; + av->vdev.queue = &av->aq.vbq; + av->vdev.lock = &av->mutex; + + ipu6_isys_video_try_fmt_vid_mplane(av, &format.fmt.pix_mp); + av->mpix = format.fmt.pix_mp; + + set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags); + video_set_drvdata(&av->vdev, av); + + ret = video_register_device(&av->vdev, VFL_TYPE_VIDEO, -1); + if (ret) + goto out_media_entity_cleanup; + + return ret; + +out_media_entity_cleanup: + video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + +out_ipu6_isys_queue_cleanup: + ipu6_isys_queue_cleanup(&av->aq); + +out_free_watermark: + mutex_destroy(&av->mutex); + + return ret; +} + +void ipu6_isys_video_cleanup(struct ipu6_isys_video *av) +{ + video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + mutex_destroy(&av->mutex); + ipu6_isys_queue_cleanup(&av->aq); +} diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h new file mode 100644 index 000000000000..24255fdb1558 --- /dev/null +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h @@ -0,0 +1,129 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013 - 2023 Intel Corporation */ + +#ifndef IPU6_ISYS_VIDEO_H +#define IPU6_ISYS_VIDEO_H + +#include +#include + +#include "ipu6-isys-queue.h" + +#define IPU6_ISYS_OUTPUT_PINS 11 +#define IPU6_ISYS_MAX_PARALLEL_SOF 2 +#define NR_OF_VIDEO_DEVICE 31 + +struct ipu6_isys; +struct ipu6_fw_isys_stream_cfg_data_abi; + +struct ipu6_isys_pixelformat { + u32 pixelformat; + u32 bpp; + u32 bpp_packed; + u32 code; + u32 css_pixelformat; +}; + +struct sequence_info { + unsigned int sequence; + u64 timestamp; +}; + +struct output_pin_data { + void (*pin_ready)(struct ipu6_isys_stream *stream, + struct ipu6_fw_isys_resp_info_abi *info); + struct ipu6_isys_queue *aq; +}; + +/* + * Align with firmware stream. Each stream represents a CSI virtual channel. + * May map to multiple video devices + */ +struct ipu6_isys_stream { + struct mutex mutex; + struct media_entity *source_entity; + atomic_t sequence; + unsigned int seq_index; + struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; + int stream_source; + int stream_handle; + unsigned int nr_output_pins; + struct ipu6_isys_subdev *asd; + + int nr_queues; /* Number of capture queues */ + int nr_streaming; + int streaming; /* Has streaming been really started? */ + struct list_head queues; + struct completion stream_open_completion; + struct completion stream_close_completion; + struct completion stream_start_completion; + struct completion stream_stop_completion; + struct ipu6_isys *isys; + + struct output_pin_data output_pins[IPU6_ISYS_OUTPUT_PINS]; + int error; + u8 vc; +}; + +struct video_stream_watermark { + u32 width; + u32 height; + u32 hblank; + u32 frame_rate; + u64 pixel_rate; + u64 stream_data_rate; + u16 sram_gran_shift; + u16 sram_gran_size; + struct list_head stream_node; +}; + +struct ipu6_isys_video { + struct ipu6_isys_queue aq; + /* Serialise access to other fields in the struct. */ + struct mutex mutex; + struct media_pad pad; + struct video_device vdev; + struct v4l2_pix_format_mplane mpix; + const struct ipu6_isys_pixelformat *pfmt; + struct ipu6_isys *isys; + struct ipu6_isys_stream *stream; + unsigned int streaming; + struct video_stream_watermark watermark; + u32 source_stream; + u8 vc; + u8 dt; +}; + +#define ipu6_isys_queue_to_video(__aq) \ + container_of(__aq, struct ipu6_isys_video, aq) + +extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[]; +extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[]; + +int ipu6_isys_vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap); + +int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f); +int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, + struct media_entity *source_entity, + int nr_queues); +int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, + struct ipu6_isys_buffer_list *bl); +int ipu6_isys_fw_open(struct ipu6_isys *isys); +void ipu6_isys_fw_close(struct ipu6_isys *isys); +int ipu6_isys_setup_video(struct ipu6_isys_video *av, + struct media_entity **source_entity, int *nr_queues); +int ipu6_isys_video_init(struct ipu6_isys_video *av); +void ipu6_isys_video_cleanup(struct ipu6_isys_video *av); +void ipu6_isys_put_stream(struct ipu6_isys_stream *stream); +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle); +struct ipu6_isys_stream * +ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc); + +void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, + bool state); +void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); + +#endif /* IPU6_ISYS_VIDEO_H */ From patchwork Tue Oct 24 11:29:22 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737692 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9AA39C00A8F for ; Tue, 24 Oct 2023 11:21:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230421AbjJXLVQ (ORCPT ); Tue, 24 Oct 2023 07:21:16 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33210 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231283AbjJXLVP (ORCPT ); Tue, 24 Oct 2023 07:21:15 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 02182D7A for ; Tue, 24 Oct 2023 04:21:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146473; x=1729682473; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=h/qN5ewvL24oaMpWLZF0PZxsA8uF091V9uG4gu10by8=; b=C9wN1tXfZlgLQWv1HxxeWZEOCaK+BEFtnZQdsRT3bI7mZEjN1ozndWhc FKIAi+wiXG0VLFMEJhUZW4eNlW1sFmhj9j6SGg1nzVr00G/1MvACFo1hO ZR643ymbtjObrsPAhLBdzOnvO1YkEO8p3MHy+uP3na3sIuihwJQNgiR+h xbt+TLBact/brZHda/63tq1jYzy70ldt/fSzuAUIs+XyWMpNVy5Nh0ZtF GjI7zU+t4FTjKlh3129TgXI7NqpefY8LWoYfihWPd4aKULZiC1mLVs2R5 HhArInjRv31IMOS41rMafm7kYMLJo9AUjJ82Nc1CCIEl4y/JNlgAWn0HG Q==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258814" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258814" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:21:00 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069948" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069948" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:20:57 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 13/15] MAINTAINERS: add maintainers for Intel IPU6 input system driver Date: Tue, 24 Oct 2023 19:29:22 +0800 Message-ID: <20231024112924.3934228-14-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao Update MAINTAINERS file for Intel IPU6 input system driver. Signed-off-by: Bingbu Cao --- MAINTAINERS | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2894f0777537..b4b0626d43d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10626,6 +10626,16 @@ F: Documentation/admin-guide/media/ipu3_rcb.svg F: Documentation/userspace-api/media/v4l/metafmt-intel-ipu3.rst F: drivers/staging/media/ipu3/ +INTEL IPU6 INPUT SYSTEM DRIVER +M: Sakari Ailus +M: Bingbu Cao +R: Tianshu Qiu +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media_tree.git +F: Documentation/admin-guide/media/ipu6-isys.rst +F: drivers/media/pci/intel/ipu6/ + INTEL ISHTP ECLITE DRIVER M: Sumesh K Naduvalath L: platform-driver-x86@vger.kernel.org From patchwork Tue Oct 24 11:29:23 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Cao, Bingbu" X-Patchwork-Id: 737691 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0FAC6C00A8F for ; Tue, 24 Oct 2023 11:21:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231437AbjJXLVY (ORCPT ); Tue, 24 Oct 2023 07:21:24 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38474 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S231348AbjJXLVV (ORCPT ); Tue, 24 Oct 2023 07:21:21 -0400 Received: from mgamail.intel.com (mgamail.intel.com [192.55.52.43]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 3EA2ED7F for ; Tue, 24 Oct 2023 04:21:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=intel.com; i=@intel.com; q=dns/txt; s=Intel; t=1698146474; x=1729682474; h=from:to:cc:subject:date:message-id:in-reply-to: references:mime-version:content-transfer-encoding; bh=BaiIKICYfaLhCBuNIn17XZdvpen5g9uAP455+vEhdFw=; b=TBQ8yluVjGfHLzqwQS6nOEBzhIbclIMvts5tc9NENTTdryK4r0yN8Pw3 QhDyl7E+JJKbSR/mqCmkXF3iSlamrcbQWH3rPYCOxq6dN/UjTqa1zcd5O +PS9y+biasTsrZMtA5Bc9tHt4KJu3HmVOCai0XH+oYL27x2REH+rF6O0T +Pdfas5A5GtYTuqpGAXGp8BdYClEsbwbqkuGN1JJszrSY/Wr0LWpw1pZ7 YkkZ0P+UQrGU8+ytSKWSA3tH89z6yNSFCJi+JVSoG96p3e2547RMFwh4w cB9w5iYRvpVEF7zJAVaGKl7vhZxTlyzvU6xAcHRKaicTjczmQ+USP11Pp Q==; X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="473258825" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="473258825" Received: from fmsmga007.fm.intel.com ([10.253.24.52]) by fmsmga105.fm.intel.com with ESMTP/TLS/ECDHE-RSA-AES256-GCM-SHA384; 24 Oct 2023 04:21:05 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=McAfee;i="6600,9927,10872"; a="762069974" X-IronPort-AV: E=Sophos;i="6.03,247,1694761200"; d="scan'208";a="762069974" Received: from icg-kernel3.bj.intel.com ([172.16.126.174]) by fmsmga007.fm.intel.com with ESMTP; 24 Oct 2023 04:21:00 -0700 From: bingbu.cao@intel.com To: linux-media@vger.kernel.org, sakari.ailus@linux.intel.com, laurent.pinchart@ideasonboard.com Cc: andriy.shevchenko@linux.intel.com, hdegoede@redhat.com, ilpo.jarvinen@linux.intel.com, andreaskleist@gmail.com, claus.stovgaard@gmail.com, tfiga@chromium.org, senozhatsky@chromium.org, tomi.valkeinen@ideasonboard.com, bingbu.cao@intel.com, bingbu.cao@linux.intel.com, tian.shu.qiu@intel.com, hongju.wang@intel.com Subject: [PATCH v2 14/15] Documentation: add Intel IPU6 ISYS driver admin-guide doc Date: Tue, 24 Oct 2023 19:29:23 +0800 Message-ID: <20231024112924.3934228-15-bingbu.cao@intel.com> X-Mailer: git-send-email 2.42.0 In-Reply-To: <20231024112924.3934228-1-bingbu.cao@intel.com> References: <20231024112924.3934228-1-bingbu.cao@intel.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Bingbu Cao This document mainly describe the functionality of IPU6 and IPU6 isys driver, and gives an example that how user can do imaging capture with tools. Signed-off-by: Bingbu Cao --- Documentation/admin-guide/media/ipu6-isys.rst | 159 +++ .../admin-guide/media/ipu6_isys_graph.svg | 338 +++++ .../admin-guide/media/ipu6_isys_multi.svg | 1124 +++++++++++++++++ .../admin-guide/media/v4l-drivers.rst | 1 + 4 files changed, 1622 insertions(+) create mode 100644 Documentation/admin-guide/media/ipu6-isys.rst create mode 100644 Documentation/admin-guide/media/ipu6_isys_graph.svg create mode 100644 Documentation/admin-guide/media/ipu6_isys_multi.svg diff --git a/Documentation/admin-guide/media/ipu6-isys.rst b/Documentation/admin-guide/media/ipu6-isys.rst new file mode 100644 index 000000000000..fb4d355e1522 --- /dev/null +++ b/Documentation/admin-guide/media/ipu6-isys.rst @@ -0,0 +1,159 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. include:: + +=============================================================== +Intel Image Processing Unit 6 (IPU6) Input System driver +=============================================================== + +Copyright |copy| 2023 Intel Corporation + +Introduction +============ + +This file documents the Intel IPU6 (6th generation Image Processing Unit) +Input System (MIPI CSI2 receiver) drivers located under +drivers/media/pci/intel/ipu6. + +The Intel IPU6 can be found in certain Intel Chipsets but not in all SKUs: + +* TigerLake +* JasperLake +* AlderLake +* RaptorLake +* MeteorLake + +Intel IPU6 is made up of two components - Input System (ISYS) and Processing +System (PSYS). + +The Input System mainly works as MIPI CSI2 receiver which receives and +processes the imaging data from the sensors and outputs the frames to memory. + +There are 2 driver modules - intel_ipu6 and intel_ipu6_isys. intel_ipu6 is an +IPU6 common driver which does PCI configuration, firmware loading and parsing, +firmware authentication, DMA mapping and IPU-MMU (internal Memory mapping Unit) +configuration. intel_ipu6_isys implements V4L2, Media Controller and V4L2 +sub-device interfaces. The IPU6 ISYS driver supports camera sensors connected +to the IPU6 ISYS through V4L2 sub-device sensor drivers. + +.. Note:: See Documentation/driver-api/media/drivers/ipu6.rst for more + information about the IPU6 hardware. + + +Input system driver +=================== + +The input System driver mainly configures CSI2 DPHY, constructs the firmware +stream configuration, sends commands to firmware, gets response from hardware +and firmware and then returns buffers to user. +The ISYS is represented as several V4L2 sub-devices - 'Intel IPU6 CSI2 $port', +which provide V4L2 subdev interfaces to the user space, there are also several +video nodes for each CSI-2 stream capture - 'Intel IPU6 ISYS capture $num' which +provide interface to user to set formats, queue buffers and streaming. + +.. kernel-figure:: ipu6_isys_graph.svg + :alt: ipu6 isys media graph without multiple streams support + + ipu6 isys media graph without multiple streams support + +.. kernel-figure:: ipu6_isys_multi.svg + :alt: ipu6 isys media graph with multiple streams support + + ipu6 isys media graph with multiple streams support + +Capturing frames by IPU6 ISYS +------------------------------------ + +IPU6 ISYS is used to capture frames from the camera sensors connected to the +CSI2 ports. The supported input formats of ISYS are listed in table below: + +.. tabularcolumns:: |p{0.8cm}|p{4.0cm}|p{4.0cm}| + +.. flat-table:: + :header-rows: 1 + + * - IPU6 ISYS supported input formats + + * - RGB565, RGB888 + + * - UYVY8, YUYV8 + + * - RAW8, RAW10, RAW12 + +Here is an example of IPU6 ISYS raw capture on Dell XPS 9315 laptop. On this +machine, ov01a10 sensor is connected to IPU ISYS CSI2 port 2, which can +generate images at sBGGR10 with resolution 1280x800. + +Using the media controller APIs, we can configure ov01a10 sensor by +media-ctl [#f1]_ and yavta [#f2]_ to transmit frames to IPU6 ISYS. + +.. code-block:: none + + # Example 1 capture frame from ov01a10 camera sensor + # This example assumes /dev/media0 as the IPU ISYS media device + export MDEV=/dev/media0 + + # Establish the link for the media devices using media-ctl + media-ctl -d $MDEV -l "\"ov01a10 3-0036\":0 -> \"Intel IPU6 CSI2 2\":0[1]" + + # Set the format for the media devices + media-ctl -d $MDEV -V "ov01a10:0 [fmt:SBGGR10/1280x800]" + media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:0 [fmt:SBGGR10/1280x800]" + media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:1 [fmt:SBGGR10/1280x800]" + +Once the media pipeline is configured, desired sensor specific settings +(such as exposure and gain settings) can be set, using the yavta tool. + +e.g + +.. code-block:: none + + # and that ov01a10 sensor is connected to i2c bus 3 with address 0x36 + export SDEV=$(media-ctl -d $MDEV -e "ov01a10 3-0036") + + yavta -w 0x009e0903 400 $SDEV + yavta -w 0x009e0913 1000 $SDEV + yavta -w 0x009e0911 2000 $SDEV + +Once the desired sensor settings are set, frame captures can be done as below. + +e.g + +.. code-block:: none + + yavta --data-prefix -u -c10 -n5 -I -s 1280x800 --file=/tmp/frame-#.bin \ + -f SBGGR10 $(media-ctl -d $MDEV -e "Intel IPU6 ISYS Capture 0") + +With the above command, 10 frames are captured at 1280x800 resolution with +sBGGR10 format. The captured frames are available as /tmp/frame-#.bin files. + +Here is another example of IPU6 ISYS RAW and metadata capture from camera +sensor ov2740 on Lenovo X1 Yoga laptop. + +.. code-block:: none + + media-ctl -l "\"ov2740 14-0036\":0 -> \"Intel IPU6 CSI2 1\":0[1]" + media-ctl -l "\"Intel IPU6 CSI2 1\":1 -> \"Intel IPU6 ISYS Capture 0\":0[5]" + media-ctl -l "\"Intel IPU6 CSI2 1\":2 -> \"Intel IPU6 ISYS Capture 1\":0[5]" + + # set routing + media-ctl -v -R "\"Intel IPU6 CSI2 1\" [0/0->1/0[1],0/1->2/1[1]]" + + media-ctl -v "\"Intel IPU6 CSI2 1\":0/0 [fmt:SGRBG10/1932x1092]" + media-ctl -v "\"Intel IPU6 CSI2 1\":0/1 [fmt:GENERIC_8/97x1]" + media-ctl -v "\"Intel IPU6 CSI2 1\":1/0 [fmt:SGRBG10/1932x1092]" + media-ctl -v "\"Intel IPU6 CSI2 1\":2/1 [fmt:GENERIC_8/97x1]" + + CAPTURE_DEV=$(media-ctl -e "Intel IPU6 ISYS Capture 0") + ./yavta --data-prefix -c100 -n5 -I -s1932x1092 --file=/tmp/frame-#.bin \ + -f SGRBG10 ${CAPTURE_DEV} + + CAPTURE_META=$(media-ctl -e "Intel IPU6 ISYS Capture 1") + ./yavta --data-prefix -c100 -n5 -I -s97x1 -B meta-capture \ + --file=/tmp/meta-#.bin -f GENERIC_8 ${CAPTURE_META} + +References +========== + +.. [#f1] https://git.ideasonboard.org/?p=media-ctl.git;a=summary +.. [#f2] https://git.ideasonboard.org/yavta.git diff --git a/Documentation/admin-guide/media/ipu6_isys_graph.svg b/Documentation/admin-guide/media/ipu6_isys_graph.svg new file mode 100644 index 000000000000..661aee18dbe2 --- /dev/null +++ b/Documentation/admin-guide/media/ipu6_isys_graph.svg @@ -0,0 +1,338 @@ + + + + + + +board + + + +n00000001 + +Intel IPU6 ISYS Capture 0 +/dev/video0 + + + +n00000005 + +Intel IPU6 ISYS Capture 1 +/dev/video1 + + + +n00000009 + +Intel IPU6 ISYS Capture 2 +/dev/video2 + + + +n0000000d + +Intel IPU6 ISYS Capture 3 +/dev/video3 + + + +n00000011 + +0 + +Intel IPU6 CSI2 0 +/dev/v4l-subdev0 + +1 + + + +n00000011:port1->n00000001 + + + + + +n00000011:port1->n00000005 + + + + + +n00000011:port1->n00000009 + + + + + +n00000011:port1->n0000000d + + + + + +n00000014 + +0 + +Intel IPU6 CSI2 1 +/dev/v4l-subdev1 + +1 + + + +n00000014:port1->n00000001 + + + + + +n00000014:port1->n00000005 + + + + + +n00000014:port1->n00000009 + + + + + +n00000014:port1->n0000000d + + + + + +n00000017 + +0 + +Intel IPU6 CSI2 2 +/dev/v4l-subdev2 + +1 + + + +n00000017:port1->n00000001 + + + + + +n00000017:port1->n00000005 + + + + + +n00000017:port1->n00000009 + + + + + +n00000017:port1->n0000000d + + + + + +n0000001a + +0 + +Intel IPU6 CSI2 3 +/dev/v4l-subdev3 + +1 + + + +n0000001a:port1->n00000001 + + + + + +n0000001a:port1->n00000005 + + + + + +n0000001a:port1->n00000009 + + + + + +n0000001a:port1->n0000000d + + + + + +n0000001d + +0 + +Intel IPU6 CSI2 4 +/dev/v4l-subdev4 + +1 + + + +n0000001d:port1->n00000001 + + + + + +n0000001d:port1->n00000005 + + + + + +n0000001d:port1->n00000009 + + + + + +n0000001d:port1->n0000000d + + + + + +n00000020 + +0 + +Intel IPU6 CSI2 5 +/dev/v4l-subdev5 + +1 + + + +n00000020:port1->n00000001 + + + + + +n00000020:port1->n00000005 + + + + + +n00000020:port1->n00000009 + + + + + +n00000020:port1->n0000000d + + + + + +n00000023 + +0 + +Intel IPU6 CSI2 6 +/dev/v4l-subdev6 + +1 + + + +n00000023:port1->n00000001 + + + + + +n00000023:port1->n00000005 + + + + + +n00000023:port1->n00000009 + + + + + +n00000023:port1->n0000000d + + + + + +n00000026 + +0 + +Intel IPU6 CSI2 7 +/dev/v4l-subdev7 + +1 + + + +n00000026:port1->n00000001 + + + + + +n00000026:port1->n00000005 + + + + + +n00000026:port1->n00000009 + + + + + +n00000026:port1->n0000000d + + + + + +n00000069 + + + +ov01a10 3-0036 +/dev/v4l-subdev8 + +0 + + + +n00000069:port0->n00000017:port0 + + + + + diff --git a/Documentation/admin-guide/media/ipu6_isys_multi.svg b/Documentation/admin-guide/media/ipu6_isys_multi.svg new file mode 100644 index 000000000000..5bd748d15c70 --- /dev/null +++ b/Documentation/admin-guide/media/ipu6_isys_multi.svg @@ -0,0 +1,1124 @@ + + + + + + +board + + + +n00000001 + +Intel IPU6 ISYS Capture 0 +/dev/video0 + + + +n00000005 + +Intel IPU6 ISYS Capture 1 +/dev/video1 + + + +n00000009 + +Intel IPU6 ISYS Capture ... + + + +n00000075 + +Intel IPU6 ISYS Capture 29 +/dev/video29 + + + +n00000079 + +Intel IPU6 ISYS Capture 30 +/dev/video30 + + + +n0000007d + +0 + +Intel IPU6 CSI2 0 +/dev/v4l-subdev0 + +1 + +2 + +3 + +4 + +5 + +6 + +7 + +8 + + + +n0000007d:port1->n00000001 + + + + + +n0000007d:port2->n00000001 + + + + + +n0000007d:port3->n00000001 + + + + + +n0000007d:port4->n00000001 + + + + + +n0000007d:port5->n00000001 + + + + + +n0000007d:port6->n00000001 + + + + + +n0000007d:port7->n00000001 + + + + + +n0000007d:port8->n00000001 + + + + + +n0000007d:port1->n00000005 + + + + + +n0000007d:port2->n00000005 + + + + + +n0000007d:port3->n00000005 + + + + + +n0000007d:port4->n00000005 + + + + + +n0000007d:port5->n00000005 + + + + + +n0000007d:port6->n00000005 + + + + + +n0000007d:port7->n00000005 + + + + + +n0000007d:port8->n00000005 + + + + + +n0000007d:port1->n00000009 + + + + + +n0000007d:port2->n00000009 + + + + + +n0000007d:port3->n00000009 + + + + + +n0000007d:port4->n00000009 + + + + + +n0000007d:port5->n00000009 + + + + + +n0000007d:port6->n00000009 + + + + + +n0000007d:port7->n00000009 + + + + + +n0000007d:port8->n00000009 + + + + + +n0000007d:port1->n00000075 + + + + + +n0000007d:port2->n00000075 + + + + + +n0000007d:port3->n00000075 + + + + + +n0000007d:port4->n00000075 + + + + + +n0000007d:port5->n00000075 + + + + + +n0000007d:port6->n00000075 + + + + + +n0000007d:port7->n00000075 + + + + + +n0000007d:port8->n00000075 + + + + + +n0000007d:port1->n00000079 + + + + + +n0000007d:port2->n00000079 + + + + + +n0000007d:port3->n00000079 + + + + + +n0000007d:port4->n00000079 + + + + + +n0000007d:port5->n00000079 + + + + + +n0000007d:port6->n00000079 + + + + + +n0000007d:port7->n00000079 + + + + + +n0000007d:port8->n00000079 + + + + + +n00000087 + +0 + +Intel IPU6 CSI2 1 +/dev/v4l-subdev1 + +1 + +2 + +3 + +4 + +5 + +6 + +7 + +8 + + + +n00000087:port1->n00000001 + + + + + +n00000087:port2->n00000001 + + + + + +n00000087:port3->n00000001 + + + + + +n00000087:port4->n00000001 + + + + + +n00000087:port5->n00000001 + + + + + +n00000087:port6->n00000001 + + + + + +n00000087:port7->n00000001 + + + + + +n00000087:port8->n00000001 + + + + + +n00000087:port1->n00000005 + + + + + +n00000087:port2->n00000005 + + + + + +n00000087:port3->n00000005 + + + + + +n00000087:port4->n00000005 + + + + + +n00000087:port5->n00000005 + + + + + +n00000087:port6->n00000005 + + + + + +n00000087:port7->n00000005 + + + + + +n00000087:port8->n00000005 + + + + + +n00000087:port1->n00000009 + + + + + +n00000087:port2->n00000009 + + + + + +n00000087:port3->n00000009 + + + + + +n00000087:port4->n00000009 + + + + + +n00000087:port5->n00000009 + + + + + +n00000087:port6->n00000009 + + + + + +n00000087:port7->n00000009 + + + + + +n00000087:port8->n00000009 + + + + + +n00000087:port1->n00000075 + + + + + +n00000087:port2->n00000075 + + + + + +n00000087:port3->n00000075 + + + + + +n00000087:port4->n00000075 + + + + + +n00000087:port5->n00000075 + + + + + +n00000087:port6->n00000075 + + + + + +n00000087:port7->n00000075 + + + + + +n00000087:port8->n00000075 + + + + + +n00000087:port1->n00000079 + + + + + +n00000087:port2->n00000079 + + + + + +n00000087:port3->n00000079 + + + + + +n00000087:port4->n00000079 + + + + + +n00000087:port5->n00000079 + + + + + +n00000087:port6->n00000079 + + + + + +n00000087:port7->n00000079 + + + + + +n00000087:port8->n00000079 + + + + + +n00000091 + +0 + +Intel IPU6 CSI2 2 +/dev/v4l-subdev2 + +1 + +2 + +3 + +4 + +5 + +6 + +7 + +8 + + + +n00000091:port1->n00000001 + + + + + +n00000091:port2->n00000001 + + + + + +n00000091:port3->n00000001 + + + + + +n00000091:port4->n00000001 + + + + + +n00000091:port5->n00000001 + + + + + +n00000091:port6->n00000001 + + + + + +n00000091:port7->n00000001 + + + + + +n00000091:port8->n00000001 + + + + + +n00000091:port1->n00000005 + + + + + +n00000091:port2->n00000005 + + + + + +n00000091:port3->n00000005 + + + + + +n00000091:port4->n00000005 + + + + + +n00000091:port5->n00000005 + + + + + +n00000091:port6->n00000005 + + + + + +n00000091:port7->n00000005 + + + + + +n00000091:port8->n00000005 + + + + + +n00000091:port1->n00000009 + + + + + +n00000091:port2->n00000009 + + + + + +n00000091:port3->n00000009 + + + + + +n00000091:port4->n00000009 + + + + + +n00000091:port5->n00000009 + + + + + +n00000091:port6->n00000009 + + + + + +n00000091:port7->n00000009 + + + + + +n00000091:port8->n00000009 + + + + + +n00000091:port1->n00000075 + + + + + +n00000091:port2->n00000075 + + + + + +n00000091:port3->n00000075 + + + + + +n00000091:port4->n00000075 + + + + + +n00000091:port5->n00000075 + + + + + +n00000091:port6->n00000075 + + + + + +n00000091:port7->n00000075 + + + + + +n00000091:port8->n00000075 + + + + + +n00000091:port1->n00000079 + + + + + +n00000091:port2->n00000079 + + + + + +n00000091:port3->n00000079 + + + + + +n00000091:port4->n00000079 + + + + + +n00000091:port5->n00000079 + + + + + +n00000091:port6->n00000079 + + + + + +n00000091:port7->n00000079 + + + + + +n00000091:port8->n00000079 + + + + + +n0000009b + +0 + +Intel IPU6 CSI2 3 +/dev/v4l-subdev3 + +1 + +2 + +3 + +4 + +5 + +6 + +7 + +8 + + + +n0000009b:port1->n00000001 + + + + + +n0000009b:port2->n00000001 + + + + + +n0000009b:port3->n00000001 + + + + + +n0000009b:port4->n00000001 + + + + + +n0000009b:port5->n00000001 + + + + + +n0000009b:port6->n00000001 + + + + + +n0000009b:port7->n00000001 + + + + + +n0000009b:port8->n00000001 + + + + + +n0000009b:port1->n00000005 + + + + + +n0000009b:port2->n00000005 + + + + + +n0000009b:port3->n00000005 + + + + + +n0000009b:port4->n00000005 + + + + + +n0000009b:port5->n00000005 + + + + + +n0000009b:port6->n00000005 + + + + + +n0000009b:port7->n00000005 + + + + + +n0000009b:port8->n00000005 + + + + + +n0000009b:port1->n00000009 + + + + + +n0000009b:port2->n00000009 + + + + + +n0000009b:port3->n00000009 + + + + + +n0000009b:port4->n00000009 + + + + + +n0000009b:port5->n00000009 + + + + + +n0000009b:port6->n00000009 + + + + + +n0000009b:port7->n00000009 + + + + + +n0000009b:port8->n00000009 + + + + + +n0000009b:port1->n00000075 + + + + + +n0000009b:port2->n00000075 + + + + + +n0000009b:port3->n00000075 + + + + + +n0000009b:port4->n00000075 + + + + + +n0000009b:port5->n00000075 + + + + + +n0000009b:port6->n00000075 + + + + + +n0000009b:port7->n00000075 + + + + + +n0000009b:port8->n00000075 + + + + + +n0000009b:port1->n00000079 + + + + + +n0000009b:port2->n00000079 + + + + + +n0000009b:port3->n00000079 + + + + + +n0000009b:port4->n00000079 + + + + + +n0000009b:port5->n00000079 + + + + + +n0000009b:port6->n00000079 + + + + + +n0000009b:port7->n00000079 + + + + + +n0000009b:port8->n00000079 + + + + + +n00000865 + + + +ov01a10 3-0036 +/dev/v4l-subdev4 + +0 + + + +n00000865:port0->n00000091:port0 + + + + + diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst index 1c41f87c3917..f6328a242cbe 100644 --- a/Documentation/admin-guide/media/v4l-drivers.rst +++ b/Documentation/admin-guide/media/v4l-drivers.rst @@ -16,6 +16,7 @@ Video4Linux (V4L) driver-specific documentation imx imx7 ipu3 + ipu6-isys ivtv omap3isp omap4_camera