[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <20250608144916.222835-6-Bin.Du@amd.com>
Date: Sun, 8 Jun 2025 22:49:12 +0800
From: Bin Du <Bin.Du@....com>
To: <mchehab@...nel.org>, <hverkuil@...all.nl>,
<laurent.pinchart+renesas@...asonboard.com>, <bryan.odonoghue@...aro.org>,
<sakari.ailus@...ux.intel.com>, <prabhakar.mahadev-lad.rj@...renesas.com>,
<linux-media@...r.kernel.org>, <linux-kernel@...r.kernel.org>
CC: <pratap.nirujogi@....com>, <benjamin.chan@....com>, <king.li@....com>,
<gjorgji.rosikopulos@....com>, <Phil.Jawich@....com>,
<Dominic.Antony@....com>, <Svetoslav.Stoilov@....com>, <bin.du@....com>, "Bin
Du" <Bin.Du@....com>
Subject: [PATCH v1 5/9] media: platform: amd: isp4 subdev and firmware loading handling added
Isp4 sub-device is implementing v4l2 sub-device interface. It has one
capture video node, and supports only preview stream. It manages
firmware states, stream configuration and mipi phy configuration.
This change also adds interrupt handling and notification for isp
firmware to isp-subdevice.
Signed-off-by: Bin Du <Bin.Du@....com>
Signed-off-by: Svetoslav Stoilov <Svetoslav.Stoilov@....com>
Change-Id: I87d5877226c37a3c4271552ee3bb7316e13de0be
---
drivers/media/platform/amd/isp4/Makefile | 3 +-
drivers/media/platform/amd/isp4/isp4.c | 240 +++-
drivers/media/platform/amd/isp4/isp4.h | 4 +-
drivers/media/platform/amd/isp4/isp4_subdev.c | 1213 +++++++++++++++++
drivers/media/platform/amd/isp4/isp4_subdev.h | 145 ++
5 files changed, 1595 insertions(+), 10 deletions(-)
create mode 100644 drivers/media/platform/amd/isp4/isp4_subdev.c
create mode 100644 drivers/media/platform/amd/isp4/isp4_subdev.h
diff --git a/drivers/media/platform/amd/isp4/Makefile b/drivers/media/platform/amd/isp4/Makefile
index c76b8a327be6..d2f1523ad07a 100644
--- a/drivers/media/platform/amd/isp4/Makefile
+++ b/drivers/media/platform/amd/isp4/Makefile
@@ -3,9 +3,10 @@
# Copyright (C) 2025 Advanced Micro Devices, Inc.
obj-$(CONFIG_AMD_ISP4) += amd_capture.o
-amd_capture-objs := isp4.o \
+amd_capture-objs := isp4_subdev.o \
isp4_phy.o \
isp4_interface.o \
+ isp4.o \
isp4_hw.o \
ccflags-y += -I$(srctree)/drivers/media/platform/amd/isp4
diff --git a/drivers/media/platform/amd/isp4/isp4.c b/drivers/media/platform/amd/isp4/isp4.c
index d0be90c5ec3b..2dc7ea3b02e8 100644
--- a/drivers/media/platform/amd/isp4/isp4.c
+++ b/drivers/media/platform/amd/isp4/isp4.c
@@ -7,11 +7,17 @@
#include <linux/vmalloc.h>
#include <media/v4l2-ioctl.h>
-#include "isp4.h"
+#include "amdgpu_object.h"
-#define VIDEO_BUF_NUM 5
+#include "isp4.h"
+#include "isp4_hw.h"
#define ISP4_DRV_NAME "amd_isp_capture"
+#define ISP4_FW_RESP_RB_IRQ_STATUS_MASK \
+ (ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK | \
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT10_INT_MASK | \
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT11_INT_MASK | \
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK)
/* interrupt num */
static const u32 isp4_ringbuf_interrupt_num[] = {
@@ -24,23 +30,207 @@ static const u32 isp4_ringbuf_interrupt_num[] = {
#define to_isp4_device(dev) \
((struct isp4_device *)container_of(dev, struct isp4_device, v4l2_dev))
+static int isp4_create_links(struct isp4_device *isp4_dev,
+ struct v4l2_subdev *sensor_sdev)
+{
+ struct v4l2_subdev *isp4_sdev = &isp4_dev->isp_sdev.sdev;
+ struct device *dev = &isp4_dev->pdev->dev;
+ int ret;
+
+ ret = media_create_pad_link(&sensor_sdev->entity,
+ 0, &isp4_sdev->entity, 0,
+ MEDIA_LNK_FL_ENABLED |
+ MEDIA_LNK_FL_IMMUTABLE);
+ if (ret)
+ dev_err(dev, "create sensor to isp link fail:%d\n", ret);
+
+ return ret;
+}
+
+static int isp4_register_subdev_and_create_links(struct isp4_device *isp_dev,
+ struct v4l2_subdev *sdev)
+{
+ struct device *dev = &isp_dev->pdev->dev;
+ int ret;
+
+ ret = isp4_create_links(isp_dev, sdev);
+ if (ret)
+ dev_err(dev, "fail create isp link:%d\n", ret);
+
+ ret = v4l2_device_register_subdev_nodes(&isp_dev->v4l2_dev);
+ if (ret != 0) {
+ dev_warn(dev, "register subdev as nodes fail:%d\n", ret);
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int isp4_camera_sensor_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sensor_sdev,
+ struct v4l2_async_connection *asd)
+{
+ struct isp4_device *isp_dev = to_isp4_device(notifier->v4l2_dev);
+ struct device *dev = &isp_dev->pdev->dev;
+ int ret;
+
+ ret = isp4_register_subdev_and_create_links(isp_dev, sensor_sdev);
+ if (ret)
+ dev_err(dev, "register sensor subdev fail:%d\n",
+ ret);
+ else
+ dev_dbg(dev, "register sensor subdev suc\n");
+ return ret;
+}
+
+static void isp4_camera_sensor_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sensor_sdev,
+ struct v4l2_async_connection *asd)
+{
+}
+
+static const struct v4l2_async_notifier_operations isp4_camera_sensor_ops = {
+ .bound = isp4_camera_sensor_bound,
+ .unbind = isp4_camera_sensor_unbind,
+};
+
+static void isp4_wake_up_resp_thread(struct isp4_subdev *isp, u32 index)
+{
+ if (isp && index < ISP4SD_MAX_FW_RESP_STREAM_NUM) {
+ struct isp4sd_thread_handler *thread_ctx =
+ &isp->fw_resp_thread[index];
+
+ thread_ctx->wq_cond = 1;
+ wake_up_interruptible(&thread_ctx->waitq);
+ }
+}
+
+static void isp4_resp_interrupt_notify(struct isp4_subdev *isp, u32 intr_status)
+{
+ bool wake = (isp->ispif.status == ISP4IF_STATUS_FW_RUNNING);
+
+ u32 intr_ack = 0;
+
+ /* global response */
+ if (intr_status &
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK) {
+ if (wake)
+ isp4_wake_up_resp_thread(isp, 0);
+
+ intr_ack |= ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK;
+ }
+
+ /* stream 1 response */
+ if (intr_status &
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK) {
+ if (wake)
+ isp4_wake_up_resp_thread(isp, 1);
+
+ intr_ack |= ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK;
+ }
+
+ /* stream 2 response */
+ if (intr_status &
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT10_INT_MASK) {
+ if (wake)
+ isp4_wake_up_resp_thread(isp, 2);
+
+ intr_ack |= ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT10_ACK_MASK;
+ }
+
+ /* stream 3 response */
+ if (intr_status &
+ ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT11_INT_MASK) {
+ if (wake)
+ isp4_wake_up_resp_thread(isp, 3);
+
+ intr_ack |= ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT11_ACK_MASK;
+ }
+
+ /* clear ISP_SYS interrupts */
+ isp4hw_wreg(ISP4_GET_ISP_REG_BASE(isp), ISP_SYS_INT0_ACK, intr_ack);
+}
+
static irqreturn_t isp4_irq_handler(int irq, void *arg)
{
- struct isp4_device *isp_dev = dev_get_drvdata((struct device *)arg);
+ struct isp4_device *isp_dev = dev_get_drvdata(arg);
+ struct isp4_subdev *isp = NULL;
+ u32 isp_sys_irq_status = 0x0;
+ u32 r1;
if (!isp_dev)
- goto error_drv_data;
+ return IRQ_HANDLED;
+
+ isp = &isp_dev->isp_sdev;
+ /* check ISP_SYS interrupts status */
+ r1 = isp4hw_rreg(ISP4_GET_ISP_REG_BASE(isp), ISP_SYS_INT0_STATUS);
+
+ isp_sys_irq_status = r1 & ISP4_FW_RESP_RB_IRQ_STATUS_MASK;
+
+ isp4_resp_interrupt_notify(isp, isp_sys_irq_status);
-error_drv_data:
return IRQ_HANDLED;
}
+static int isp4_parse_fwnode_init_async_nf(struct isp4_device *isp_dev)
+{
+ struct isp4_subdev *isp_sdev = &isp_dev->isp_sdev;
+ struct device *dev = &isp_dev->pdev->dev;
+ struct v4l2_async_connection *asd;
+ struct fwnode_handle *fwnode;
+ struct fwnode_handle *ep;
+ int ret;
+
+ fwnode = dev_fwnode(dev);
+
+ ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+ if (!ep)
+ return -ENXIO;
+
+ ret = fwnode_property_read_u32(ep, "phy-id", &isp_sdev->phy_id);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_u64(ep, "phy-bit-rate",
+ &isp_sdev->phy_bit_rate);
+ if (ret)
+ return ret;
+
+ isp_sdev->phy_num_data_lanes =
+ fwnode_property_count_u32(ep, "data-lanes");
+
+ fwnode_handle_put(ep);
+
+ v4l2_async_nf_init(&isp_dev->notifier, &isp_dev->v4l2_dev);
+
+ asd = v4l2_async_nf_add_fwnode(&isp_dev->notifier, ep,
+ struct v4l2_async_connection);
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ goto err_async_nf_cleanup;
+ }
+
+ isp_dev->notifier.ops = &isp4_camera_sensor_ops;
+ ret = v4l2_async_nf_register(&isp_dev->notifier);
+ if (ret) {
+ dev_err(dev, "v4l2_async_nf_register fail:%d", ret);
+ goto err_async_nf_cleanup;
+ }
+
+ return 0;
+
+err_async_nf_cleanup:
+ v4l2_async_nf_cleanup(&isp_dev->notifier);
+ return ret;
+}
+
/*
* amd capture module
*/
static int isp4_capture_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ struct isp4_subdev *isp_sdev;
struct isp4_device *isp_dev;
int i, irq, ret;
@@ -52,6 +242,17 @@ static int isp4_capture_probe(struct platform_device *pdev)
isp_dev->pdev = pdev;
dev->init_name = ISP4_DRV_NAME;
+ isp_sdev = &isp_dev->isp_sdev;
+ isp_sdev->mmio = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(isp_sdev->mmio))
+ return dev_err_probe(dev, PTR_ERR(isp_sdev->mmio),
+ "isp ioremap fail\n");
+
+ isp_sdev->isp_phy_mmio = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(isp_sdev->isp_phy_mmio))
+ return dev_err_probe(dev, PTR_ERR(isp_sdev->isp_phy_mmio),
+ "isp phy mmio ioremap fail\n");
+
for (i = 0; i < ARRAY_SIZE(isp4_ringbuf_interrupt_num); i++) {
irq = platform_get_irq(pdev, isp4_ringbuf_interrupt_num[i]);
if (irq < 0)
@@ -90,10 +291,23 @@ static int isp4_capture_probe(struct platform_device *pdev)
dev_dbg(dev, "AMD ISP v4l2 device registered\n");
+ ret = isp4sd_init(&isp_dev->isp_sdev, &isp_dev->v4l2_dev,
+ isp_dev->pltf_data->adev);
+ if (ret) {
+ dev_err(dev, "fail init isp4 sub dev %d\n", ret);
+ goto err_unreg_v4l2;
+ }
+
+ ret = isp4_parse_fwnode_init_async_nf(isp_dev);
+ if (ret) {
+ dev_err(dev, "fail to parse fwnode %d\n", ret);
+ goto err_unreg_v4l2;
+ }
+
ret = media_device_register(&isp_dev->mdev);
if (ret) {
dev_err(dev, "fail to register media device %d\n", ret);
- goto err_unreg_v4l2;
+ goto err_isp4_deinit;
}
platform_set_drvdata(pdev, isp_dev);
@@ -103,6 +317,10 @@ static int isp4_capture_probe(struct platform_device *pdev)
return 0;
+err_isp4_deinit:
+ v4l2_async_nf_unregister(&isp_dev->notifier);
+ v4l2_async_nf_cleanup(&isp_dev->notifier);
+ isp4sd_deinit(&isp_dev->isp_sdev);
err_unreg_v4l2:
v4l2_device_unregister(&isp_dev->v4l2_dev);
@@ -112,11 +330,17 @@ static int isp4_capture_probe(struct platform_device *pdev)
static void isp4_capture_remove(struct platform_device *pdev)
{
struct isp4_device *isp_dev = platform_get_drvdata(pdev);
- struct device *dev = &pdev->dev;
+
+ v4l2_async_nf_unregister(&isp_dev->notifier);
+ v4l2_async_nf_cleanup(&isp_dev->notifier);
+ v4l2_device_unregister_subdev(&isp_dev->isp_sdev.sdev);
media_device_unregister(&isp_dev->mdev);
+ media_entity_cleanup(&isp_dev->isp_sdev.sdev.entity);
v4l2_device_unregister(&isp_dev->v4l2_dev);
- dev_dbg(dev, "AMD ISP v4l2 device unregistered\n");
+ dev_dbg(&pdev->dev, "AMD ISP v4l2 device unregistered\n");
+
+ isp4sd_deinit(&isp_dev->isp_sdev);
}
static struct platform_driver isp4_capture_drv = {
diff --git a/drivers/media/platform/amd/isp4/isp4.h b/drivers/media/platform/amd/isp4/isp4.h
index 27a7362ce6f9..596431b4a5c2 100644
--- a/drivers/media/platform/amd/isp4/isp4.h
+++ b/drivers/media/platform/amd/isp4/isp4.h
@@ -7,9 +7,9 @@
#define _ISP4_H_
#include <linux/mutex.h>
-#include <media/v4l2-device.h>
#include <media/videobuf2-memops.h>
#include <media/videobuf2-vmalloc.h>
+#include "isp4_subdev.h"
#define ISP4_GET_ISP_REG_BASE(isp4sd) (((isp4sd))->mmio)
@@ -25,11 +25,13 @@ struct isp4_platform_data {
struct isp4_device {
struct v4l2_device v4l2_dev;
+ struct isp4_subdev isp_sdev;
struct media_device mdev;
struct isp4_platform_data *pltf_data;
struct platform_device *pdev;
struct notifier_block i2c_nb;
+ struct v4l2_async_notifier notifier;
};
#endif /* isp4.h */
diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.c b/drivers/media/platform/amd/isp4/isp4_subdev.c
new file mode 100644
index 000000000000..4b28fbd6867b
--- /dev/null
+++ b/drivers/media/platform/amd/isp4/isp4_subdev.c
@@ -0,0 +1,1213 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/mutex.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+
+#include "isp4_fw_cmd_resp.h"
+#include "isp4_hw.h"
+#include "isp4_interface.h"
+#include "isp4_phy.h"
+#include "isp4_subdev.h"
+
+#define ISP4SD_MAX_CMD_RESP_BUF_SIZE (4 * 1024)
+#define ISP4SD_MIN_BUF_CNT_BEF_START_STREAM 4
+
+#define ISP4SD_PERFORMANCE_STATE_LOW 0
+#define ISP4SD_PERFORMANCE_STATE_HIGH 1
+
+#define ISP4SD_FW_CMD_TIMEOUT_IN_MS 500
+#define ISP4SD_WAIT_RESP_IRQ_TIMEOUT 5 /* ms */
+/* align 32KB */
+#define ISP4SD_META_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000)
+
+#define to_isp4_subdev(v4l2_sdev) \
+ container_of(v4l2_sdev, struct isp4_subdev, sdev)
+
+#define to_isp4_vdev(isp4_vid) \
+ container_of(isp4_vid, struct isp4_subdev, isp_vdev)
+
+static const char *isp4sd_entity_name = "amd isp4";
+
+struct isp4sd_mbus_image_format_remap {
+ u32 mbus_code;
+ enum isp4fw_image_format image_format;
+};
+
+static const struct isp4sd_mbus_image_format_remap
+ isp4sd_image_formats[] = {
+ {
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8,
+ .image_format = IMAGE_FORMAT_NV12,
+ },
+ {
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+ .image_format = IMAGE_FORMAT_YUV422INTERLEAVED,
+ },
+};
+
+static void isp4sd_module_enable(struct isp4_subdev *isp_subdev, bool enable)
+{
+ if (isp_subdev->enable_gpio) {
+ gpiod_set_value(isp_subdev->enable_gpio, enable ? 1 : 0);
+ dev_dbg(isp_subdev->dev, "%s isp_subdev module\n",
+ enable ? "enable" : "disable");
+ }
+}
+
+static int isp4sd_setup_fw_mem_pool(struct isp4_subdev *isp_subdev)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4fw_cmd_send_buffer buf_type;
+ struct device *dev = isp_subdev->dev;
+ int ret;
+
+ if (!ispif->fw_mem_pool) {
+ dev_err(dev, "fail to alloc mem pool\n");
+ return -ENOMEM;
+ }
+
+ memset(&buf_type, 0, sizeof(buf_type));
+ buf_type.buffer_type = BUFFER_TYPE_MEM_POOL;
+ buf_type.buffer.buf_tags = 0;
+ buf_type.buffer.vmid_space.bit.vmid = 0;
+ buf_type.buffer.vmid_space.bit.space = ADDR_SPACE_TYPE_GPU_VA;
+ isp4if_split_addr64(ispif->fw_mem_pool->gpu_mc_addr,
+ &buf_type.buffer.buf_base_a_lo,
+ &buf_type.buffer.buf_base_a_hi);
+ buf_type.buffer.buf_size_a = (u32)ispif->fw_mem_pool->mem_size;
+
+ ret = isp4if_send_command(ispif, CMD_ID_SEND_BUFFER,
+ &buf_type, sizeof(buf_type));
+ if (ret) {
+ dev_err(dev, "send fw mem pool 0x%llx(%u) fail %d\n",
+ ispif->fw_mem_pool->gpu_mc_addr,
+ buf_type.buffer.buf_size_a,
+ ret);
+ return ret;
+ }
+
+ dev_dbg(dev, "send fw mem pool 0x%llx(%u) suc\n",
+ ispif->fw_mem_pool->gpu_mc_addr,
+ buf_type.buffer.buf_size_a);
+
+ return 0;
+};
+
+static int isp4sd_set_stream_path(struct isp4_subdev *isp_subdev)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4fw_cmd_set_stream_cfg cmd = {0};
+ struct device *dev = isp_subdev->dev;
+
+ cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id = SENSOR_ID_ON_MIPI0;
+ cmd.stream_cfg.mipi_pipe_path_cfg.b_enable = true;
+ cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id = MIPI0_ISP_PIPELINE_ID;
+
+ cmd.stream_cfg.b_enable_tnr = true;
+ dev_dbg(dev, "isp4fw_sensor_id %d, pipeId 0x%x EnableTnr %u\n",
+ cmd.stream_cfg.mipi_pipe_path_cfg.isp4fw_sensor_id,
+ cmd.stream_cfg.isp_pipe_path_cfg.isp_pipe_id,
+ cmd.stream_cfg.b_enable_tnr);
+
+ return isp4if_send_command(ispif, CMD_ID_SET_STREAM_CONFIG,
+ &cmd, sizeof(cmd));
+}
+
+static int isp4sd_send_meta_buf(struct isp4_subdev *isp_subdev)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4fw_cmd_send_buffer buf_type = { 0 };
+ struct isp4sd_sensor_info *sensor_info;
+ struct device *dev = isp_subdev->dev;
+ u32 i;
+
+ sensor_info = &isp_subdev->sensor_info;
+ for (i = 0; i < ISP4IF_MAX_STREAM_META_BUF_COUNT; i++) {
+ int ret;
+
+ if (!sensor_info->meta_info_buf[i]) {
+ dev_err(dev, "fail for no meta info buf(%u)\n", i);
+ return -ENOMEM;
+ }
+ buf_type.buffer_type = BUFFER_TYPE_META_INFO;
+ buf_type.buffer.buf_tags = 0;
+ buf_type.buffer.vmid_space.bit.vmid = 0;
+ buf_type.buffer.vmid_space.bit.space = ADDR_SPACE_TYPE_GPU_VA;
+ isp4if_split_addr64(sensor_info->meta_info_buf[i]->gpu_mc_addr,
+ &buf_type.buffer.buf_base_a_lo,
+ &buf_type.buffer.buf_base_a_hi);
+ buf_type.buffer.buf_size_a =
+ (u32)sensor_info->meta_info_buf[i]->mem_size;
+ ret = isp4if_send_command(ispif, CMD_ID_SEND_BUFFER,
+ &buf_type,
+ sizeof(buf_type));
+ if (ret) {
+ dev_err(dev, "send meta info(%u) fail\n", i);
+ return ret;
+ }
+ }
+
+ dev_dbg(dev, "send meta info suc\n");
+ return 0;
+}
+
+static int isp4sd_enum_mbus_code(struct v4l2_subdev *isp_subdev,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_mbus_code_enum *code_enum)
+{
+ if (code_enum->index >= ARRAY_SIZE(isp4sd_image_formats))
+ return -EINVAL;
+
+ code_enum->code = isp4sd_image_formats[code_enum->index].mbus_code;
+
+ return 0;
+}
+
+static bool isp4sd_get_str_out_prop(struct isp4_subdev *isp_subdev,
+ struct isp4fw_image_prop *out_prop,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct v4l2_mbus_framefmt *format = NULL;
+ struct device *dev = isp_subdev->dev;
+ bool ret;
+
+ format = v4l2_subdev_state_get_format(state, pad, 0);
+ if (!format) {
+ dev_err(dev, "fail get subdev state format\n");
+ return false;
+ }
+
+ switch (format->code) {
+ case MEDIA_BUS_FMT_YUYV8_1_5X8:
+ out_prop->image_format = IMAGE_FORMAT_NV12;
+ out_prop->width = format->width;
+ out_prop->height = format->height;
+ out_prop->luma_pitch = format->width;
+ out_prop->chroma_pitch = out_prop->width;
+ ret = true;
+ break;
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ out_prop->image_format = IMAGE_FORMAT_YUV422INTERLEAVED;
+ out_prop->width = format->width;
+ out_prop->height = format->height;
+ out_prop->luma_pitch = format->width * 2;
+ out_prop->chroma_pitch = 0;
+ ret = true;
+ break;
+ default:
+ dev_err(dev, "fail for bad image format:0x%x\n",
+ format->code);
+ ret = false;
+ break;
+ }
+
+ if (!out_prop->width || !out_prop->height)
+ ret = false;
+ return ret;
+}
+
+static int isp4sd_kickoff_stream(struct isp4_subdev *isp_subdev, u32 w, u32 h)
+{
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+
+ if (sensor_info->status == ISP4SD_START_STATUS_STARTED) {
+ return 0;
+ } else if (sensor_info->status == ISP4SD_START_STATUS_START_FAIL) {
+ dev_err(dev, "fail for previous start fail\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "w:%u,h:%u\n", w, h);
+
+ sensor_info->status = ISP4SD_START_STATUS_START_FAIL;
+
+ if (isp4sd_send_meta_buf(isp_subdev)) {
+ dev_err(dev, "fail to send meta buf\n");
+ return -EINVAL;
+ };
+
+ sensor_info->status = ISP4SD_START_STATUS_NOT_START;
+
+ if (!sensor_info->start_stream_cmd_sent &&
+ sensor_info->buf_sent_cnt >=
+ ISP4SD_MIN_BUF_CNT_BEF_START_STREAM) {
+ int ret = isp4if_send_command(ispif, CMD_ID_START_STREAM,
+ NULL, 0);
+ if (ret) {
+ dev_err(dev, "fail to start stream\n");
+ return ret;
+ }
+
+ sensor_info->start_stream_cmd_sent = true;
+ } else {
+ dev_dbg(dev,
+ "no send START_STREAM, start_sent %u, buf_sent %u\n",
+ sensor_info->start_stream_cmd_sent,
+ sensor_info->buf_sent_cnt);
+ }
+
+ return 0;
+}
+
+static int isp4sd_setup_output(struct isp4_subdev *isp_subdev,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
+ struct isp4sd_output_info *output_info =
+ &isp_subdev->sensor_info.output_info;
+ struct isp4fw_cmd_set_out_ch_prop cmd_ch_prop = {0};
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4fw_cmd_enable_out_ch cmd_ch_en = {0};
+ struct isp4fw_image_prop *out_prop;
+ struct device *dev = isp_subdev->dev;
+ int ret;
+
+ if (output_info->start_status == ISP4SD_START_STATUS_STARTED)
+ return 0;
+
+ if (output_info->start_status == ISP4SD_START_STATUS_START_FAIL) {
+ dev_err(dev, "fail for previous start fail\n");
+ return -EINVAL;
+ }
+
+ out_prop = &cmd_ch_prop.image_prop;
+ cmd_ch_prop.ch = ISP_PIPE_OUT_CH_PREVIEW;
+ cmd_ch_en.ch = ISP_PIPE_OUT_CH_PREVIEW;
+ cmd_ch_en.is_enable = true;
+
+ if (!isp4sd_get_str_out_prop(isp_subdev, out_prop, state, pad)) {
+ dev_err(dev, "fail to get out prop\n");
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "channel: w:h=%u:%u,lp:%u,cp%u\n",
+ cmd_ch_prop.image_prop.width, cmd_ch_prop.image_prop.height,
+ cmd_ch_prop.image_prop.luma_pitch,
+ cmd_ch_prop.image_prop.chroma_pitch);
+
+ ret = isp4if_send_command(ispif, CMD_ID_SET_OUT_CHAN_PROP,
+ &cmd_ch_prop,
+ sizeof(cmd_ch_prop));
+ if (ret) {
+ output_info->start_status = ISP4SD_START_STATUS_START_FAIL;
+ dev_err(dev, "fail to set out prop\n");
+ return ret;
+ };
+
+ ret = isp4if_send_command(ispif, CMD_ID_ENABLE_OUT_CHAN,
+ &cmd_ch_en, sizeof(cmd_ch_en));
+
+ if (ret) {
+ output_info->start_status = ISP4SD_START_STATUS_START_FAIL;
+ dev_err(dev, "fail to enable channel\n");
+ return ret;
+ }
+
+ if (!sensor_info->start_stream_cmd_sent) {
+ ret = isp4sd_kickoff_stream(isp_subdev, out_prop->width,
+ out_prop->height);
+ if (ret) {
+ dev_err(dev, "kickoff stream fail %d\n", ret);
+ return ret;
+ }
+ /* sensor_info->start_stream_cmd_sent will be set to true
+ * 1. in isp4sd_kickoff_stream, if app first send buffer then
+ * start stream
+ * 2. in isp_set_stream_buf, if app first start stream, then
+ * send buffer
+ * because ISP FW has the requirement, host needs to send buffer
+ * before send start stream cmd
+ */
+ if (sensor_info->start_stream_cmd_sent) {
+ sensor_info->status = ISP4SD_START_STATUS_STARTED;
+ output_info->start_status = ISP4SD_START_STATUS_STARTED;
+ dev_dbg(dev, "kickoff stream suc,start cmd sent\n");
+ } else {
+ dev_dbg(dev, "kickoff stream suc,start cmd not sent\n");
+ }
+ } else {
+ dev_dbg(dev, "stream running, no need kickoff\n");
+ output_info->start_status = ISP4SD_START_STATUS_STARTED;
+ }
+
+ dev_dbg(dev, "setup output suc\n");
+ return 0;
+}
+
+static int isp4sd_alloc_meta_buf(struct isp4_subdev *isp_subdev)
+{
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+ u32 i;
+
+ /* TODO: check alloc method */
+ for (i = 0; i < ISP4IF_MAX_STREAM_META_BUF_COUNT; i++) {
+ if (!sensor_info->meta_info_buf[i]) {
+ sensor_info->meta_info_buf[i] =
+ ispif->metainfo_buf_pool[i];
+ if (sensor_info->meta_info_buf[i]) {
+ dev_dbg(dev, "valid %u meta_info_buf ok\n", i);
+ } else {
+ dev_err(dev,
+ "invalid %u meta_info_buf fail\n", i);
+ return -ENOMEM;
+ }
+ }
+ }
+
+ return 0;
+}
+
+static int isp4sd_init_stream(struct isp4_subdev *isp_subdev)
+{
+ struct device *dev = isp_subdev->dev;
+ int ret;
+
+ ret = isp4sd_setup_fw_mem_pool(isp_subdev);
+ if (ret) {
+ dev_err(dev, "fail to setup fw mem pool\n");
+ return ret;
+ }
+
+ ret = isp4sd_alloc_meta_buf(isp_subdev);
+ if (ret) {
+ dev_err(dev, "fail to alloc fw driver shared buf\n");
+ return ret;
+ }
+
+ ret = isp4sd_set_stream_path(isp_subdev);
+ if (ret) {
+ dev_err(dev, "fail to setup stream path\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void isp4sd_reset_stream_info(struct isp4_subdev *isp_subdev,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
+ struct v4l2_mbus_framefmt *format = NULL;
+ struct isp4sd_output_info *str_info;
+ int i;
+
+ format = v4l2_subdev_state_get_format(state,
+ pad,
+ 0);
+
+ if (!format) {
+ pr_err("fail reset stream info for not get format\n");
+
+ } else {
+ memset(format, 0, sizeof(*format));
+ format->code = MEDIA_BUS_FMT_YUYV8_1_5X8;
+ }
+
+ for (i = 0; i < ISP4IF_MAX_STREAM_META_BUF_COUNT; i++)
+ sensor_info->meta_info_buf[i] = NULL;
+
+ str_info = &sensor_info->output_info;
+ str_info->start_status = ISP4SD_START_STATUS_NOT_START;
+}
+
+static bool isp4sd_is_stream_running(struct isp4_subdev *isp_subdev)
+{
+ struct isp4sd_sensor_info *sif;
+ enum isp4sd_start_status stat;
+
+ sif = &isp_subdev->sensor_info;
+ stat = sif->output_info.start_status;
+ if (stat == ISP4SD_START_STATUS_STARTED)
+ return true;
+
+ return false;
+}
+
+static void isp4sd_reset_camera_info(struct isp4_subdev *isp_subdev,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct isp4sd_sensor_info *info = &isp_subdev->sensor_info;
+
+ info->status = ISP4SD_START_STATUS_NOT_START;
+ isp4sd_reset_stream_info(isp_subdev, state, pad);
+
+ info->start_stream_cmd_sent = false;
+}
+
+static int isp4sd_uninit_stream(struct isp4_subdev *isp_subdev,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+ bool running;
+
+ running = isp4sd_is_stream_running(isp_subdev);
+
+ if (running) {
+ dev_dbg(dev, "fail for stream is still running\n");
+ return -EINVAL;
+ }
+
+ isp4sd_reset_camera_info(isp_subdev, state, pad);
+
+ isp4if_clear_cmdq(ispif);
+ return 0;
+}
+
+static void isp4sd_fw_resp_cmd_done(struct isp4_subdev *isp_subdev,
+ enum isp4if_stream_id stream_id,
+ struct isp4fw_resp_cmd_done *para)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4if_cmd_element *ele =
+ isp4if_rm_cmd_from_cmdq(ispif, para->cmd_seq_num, para->cmd_id);
+ struct device *dev = isp_subdev->dev;
+
+ dev_dbg(dev, "stream %d,cmd (0x%08x)(%d),seq %u, ele %p\n",
+ stream_id,
+ para->cmd_id, para->cmd_status, para->cmd_seq_num,
+ ele);
+
+ if (!ele)
+ return;
+
+ if (ele->wq) {
+ dev_dbg(dev, "signal event %p\n", ele->wq);
+ if (ele->wq_cond)
+ *ele->wq_cond = 1;
+ wake_up(ele->wq);
+ }
+
+ kfree(ele);
+}
+
+static struct isp4fw_meta_info *
+isp4sd_get_meta_by_mc(struct isp4_subdev *isp_subdev,
+ u64 mc)
+{
+ u32 i;
+
+ for (i = 0; i < ISP4IF_MAX_STREAM_META_BUF_COUNT; i++) {
+ struct isp4if_gpu_mem_info *meta_info_buf =
+ isp_subdev->sensor_info.meta_info_buf[i];
+
+ if (meta_info_buf) {
+ if (mc == meta_info_buf->gpu_mc_addr)
+ return meta_info_buf->sys_addr;
+ }
+ }
+ return NULL;
+};
+
+static struct isp4if_img_buf_node *
+isp4sd_preview_done(struct isp4_subdev *isp_subdev,
+ struct isp4fw_meta_info *meta)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4if_img_buf_node *prev = NULL;
+ struct device *dev = isp_subdev->dev;
+
+ if (!meta) {
+ dev_err(dev, "fail bad param for preview done\n");
+ return prev;
+ }
+
+ if (meta->preview.enabled &&
+ (meta->preview.status == BUFFER_STATUS_SKIPPED ||
+ meta->preview.status == BUFFER_STATUS_DONE ||
+ meta->preview.status == BUFFER_STATUS_DIRTY)) {
+ struct isp4sd_output_info *str_info;
+
+ str_info = &isp_subdev->sensor_info.output_info;
+ prev = isp4if_dequeue_buffer(ispif);
+
+ if (!prev)
+ dev_err(dev, "fail null prev buf\n");
+
+ } else if (meta->preview.enabled) {
+ dev_err(dev, "fail bad preview status %u\n",
+ meta->preview.status);
+ }
+
+ return prev;
+}
+
+static void isp4sd_send_meta_info(struct isp4_subdev *isp_subdev,
+ u64 meta_info_mc)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4fw_cmd_send_buffer buf_type;
+ struct device *dev = isp_subdev->dev;
+
+ if (isp_subdev->sensor_info.status != ISP4SD_START_STATUS_STARTED) {
+ dev_warn(dev, "not working status %i, meta_info 0x%llx\n",
+ isp_subdev->sensor_info.status, meta_info_mc);
+ return;
+ }
+
+ if (meta_info_mc) {
+ memset(&buf_type, 0, sizeof(buf_type));
+ buf_type.buffer_type = BUFFER_TYPE_META_INFO;
+ buf_type.buffer.buf_tags = 0;
+ buf_type.buffer.vmid_space.bit.vmid = 0;
+ buf_type.buffer.vmid_space.bit.space = ADDR_SPACE_TYPE_GPU_VA;
+ isp4if_split_addr64(meta_info_mc,
+ &buf_type.buffer.buf_base_a_lo,
+ &buf_type.buffer.buf_base_a_hi);
+
+ buf_type.buffer.buf_size_a = ISP4SD_META_BUF_SIZE;
+ if (isp4if_send_command(ispif, CMD_ID_SEND_BUFFER,
+ &buf_type, sizeof(buf_type))) {
+ dev_err(dev, "fail send meta_info 0x%llx\n",
+ meta_info_mc);
+ } else {
+ dev_dbg(dev, "resend meta_info 0x%llx\n", meta_info_mc);
+ }
+ }
+}
+
+static void isp4sd_fw_resp_frame_done(struct isp4_subdev *isp_subdev,
+ enum isp4if_stream_id stream_id,
+ struct isp4fw_resp_param_package *para)
+{
+ struct isp4if_img_buf_node *prev = NULL;
+ struct device *dev = isp_subdev->dev;
+ struct isp4fw_meta_info *meta;
+ u64 mc = 0;
+
+ mc = isp4if_join_addr64(para->package_addr_lo, para->package_addr_hi);
+ meta = isp4sd_get_meta_by_mc(isp_subdev, mc);
+ if (mc == 0 || !meta) {
+ dev_err(dev, "fail to get meta from mc %llx\n", mc);
+ return;
+ }
+
+ dev_dbg(dev, "ts:%llu,streamId:%d,poc:%u,preview_en:%u,(%i)\n",
+ ktime_get_ns(), stream_id, meta->poc,
+ meta->preview.enabled,
+ meta->preview.status);
+
+ prev = isp4sd_preview_done(isp_subdev, meta);
+
+ isp4if_dealloc_buffer_node(prev);
+
+ if (isp_subdev->sensor_info.status == ISP4SD_START_STATUS_STARTED)
+ isp4sd_send_meta_info(isp_subdev, mc);
+
+ dev_dbg(dev, "stream_id:%d, status:%d\n", stream_id,
+ isp_subdev->sensor_info.status);
+}
+
+static void isp4sd_fw_resp_func(struct isp4_subdev *isp_subdev,
+ enum isp4if_stream_id stream_id)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+ struct isp4fw_resp resp;
+
+ if (ispif->status < ISP4IF_STATUS_FW_RUNNING)
+ return;
+
+ while (true) {
+ s32 ret;
+
+ ret = isp4if_f2h_resp(ispif, stream_id, &resp);
+ if (ret)
+ break;
+
+ switch (resp.resp_id) {
+ case RESP_ID_CMD_DONE:
+ isp4sd_fw_resp_cmd_done(isp_subdev, stream_id,
+ &resp.param.cmd_done);
+ break;
+ case RESP_ID_NOTI_FRAME_DONE:
+ isp4sd_fw_resp_frame_done(isp_subdev, stream_id,
+ &resp.param.frame_done);
+ break;
+ default:
+ dev_err(dev, "-><- fail respid (0x%x)\n",
+ resp.resp_id);
+ break;
+ }
+ }
+}
+
+static s32 isp4sd_fw_resp_thread_wrapper(void *context)
+{
+ struct isp4_subdev_thread_param *para = context;
+ struct isp4sd_thread_handler *thread_ctx;
+ enum isp4if_stream_id stream_id;
+
+ struct isp4_subdev *isp_subdev;
+ struct device *dev;
+ u64 timeout;
+
+ if (!para) {
+ dev_err(dev, "fail null context for resp thread\n");
+ return -EINVAL;
+ }
+
+ isp_subdev = para->isp_subdev;
+ dev = isp_subdev->dev;
+
+ switch (para->idx) {
+ case 0:
+ stream_id = ISP4IF_STREAM_ID_GLOBAL;
+ break;
+ case 1:
+ stream_id = ISP4IF_STREAM_ID_1;
+ break;
+ default:
+ dev_err(dev, "fail invalid %d\n", para->idx);
+ return -EINVAL;
+ }
+
+ thread_ctx = &isp_subdev->fw_resp_thread[para->idx];
+
+ thread_ctx->wq_cond = 0;
+ mutex_init(&thread_ctx->mutex);
+ init_waitqueue_head(&thread_ctx->waitq);
+ timeout = msecs_to_jiffies(ISP4SD_WAIT_RESP_IRQ_TIMEOUT);
+
+ dev_dbg(dev, "[%u] started\n", para->idx);
+
+ while (true) {
+ wait_event_interruptible_timeout(thread_ctx->waitq,
+ thread_ctx->wq_cond != 0,
+ timeout);
+ thread_ctx->wq_cond = 0;
+
+ if (kthread_should_stop()) {
+ dev_dbg(dev, "[%u] quit\n", para->idx);
+ break;
+ }
+
+ mutex_lock(&thread_ctx->mutex);
+ isp4sd_fw_resp_func(isp_subdev, stream_id);
+ mutex_unlock(&thread_ctx->mutex);
+ }
+
+ mutex_destroy(&thread_ctx->mutex);
+
+ return 0;
+}
+
+static int isp4sd_start_resp_proc_threads(struct isp4_subdev *isp_subdev)
+{
+ struct device *dev = isp_subdev->dev;
+ int i;
+
+ for (i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) {
+ struct isp4sd_thread_handler *thread_ctx =
+ &isp_subdev->fw_resp_thread[i];
+
+ isp_subdev->isp_resp_para[i].idx = i;
+ isp_subdev->isp_resp_para[i].isp_subdev = isp_subdev;
+
+ thread_ctx->thread = kthread_run(isp4sd_fw_resp_thread_wrapper,
+ &isp_subdev->isp_resp_para[i],
+ "amd_isp4_thread");
+ if (IS_ERR(thread_ctx->thread)) {
+ dev_err(dev, "create thread [%d] fail\n", i);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int isp4sd_stop_resp_proc_threads(struct isp4_subdev *isp_subdev)
+{
+ int i;
+
+ for (i = 0; i < ISP4SD_MAX_FW_RESP_STREAM_NUM; i++) {
+ struct isp4sd_thread_handler *thread_ctx =
+ &isp_subdev->fw_resp_thread[i];
+
+ if (thread_ctx->thread) {
+ kthread_stop(thread_ctx->thread);
+ thread_ctx->thread = NULL;
+ }
+ }
+
+ return 0;
+}
+
+static u32 isp4sd_get_started_stream_count(struct isp4_subdev *isp_subdev)
+{
+ u32 cnt = 0;
+
+ if (isp_subdev->sensor_info.status == ISP4SD_START_STATUS_STARTED)
+ cnt++;
+ return cnt;
+}
+
+static int isp4sd_pwroff_and_deinit(struct isp4_subdev *isp_subdev)
+{
+ struct isp4sd_sensor_info *sensor_info = &isp_subdev->sensor_info;
+ unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_LOW;
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+
+ struct device *dev = isp_subdev->dev;
+ u32 cnt;
+ int ret;
+
+ mutex_lock(&isp_subdev->ops_mutex);
+
+ if (sensor_info->status == ISP4SD_START_STATUS_STARTED) {
+ dev_err(dev, "fail for stream still running\n");
+ mutex_unlock(&isp_subdev->ops_mutex);
+ return -EINVAL;
+ }
+
+ sensor_info->status = ISP4SD_START_STATUS_NOT_START;
+ cnt = isp4sd_get_started_stream_count(isp_subdev);
+ if (cnt > 0) {
+ dev_dbg(dev, "no need power off isp_subdev\n");
+ mutex_unlock(&isp_subdev->ops_mutex);
+ return 0;
+ }
+
+ isp4if_stop(ispif);
+
+ ret = dev_pm_genpd_set_performance_state(dev, perf_state);
+ if (ret)
+ dev_err(dev,
+ "fail to set isp_subdev performance state %u,ret %d\n",
+ perf_state, ret);
+ isp4sd_stop_resp_proc_threads(isp_subdev);
+ dev_dbg(dev, "isp_subdev stop resp proc streads suc");
+ /* hold ccpu reset */
+ isp4hw_wreg(isp_subdev->mmio, ISP_SOFT_RESET, 0x0);
+ isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0);
+ ret = pm_runtime_put_sync(dev);
+ if (ret)
+ dev_err(dev, "power off isp_subdev fail %d\n", ret);
+ else
+ dev_dbg(dev, "power off isp_subdev suc\n");
+
+ ispif->status = ISP4IF_STATUS_PWR_OFF;
+ isp4if_clear_cmdq(ispif);
+ isp4sd_module_enable(isp_subdev, false);
+
+ msleep(20);
+
+ mutex_unlock(&isp_subdev->ops_mutex);
+
+ return 0;
+}
+
+static int isp4sd_pwron_and_init(struct isp4_subdev *isp_subdev)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+ int ret;
+
+ if (ispif->status == ISP4IF_STATUS_FW_RUNNING) {
+ dev_dbg(dev, "camera already opened, do nothing\n");
+ return 0;
+ }
+
+ mutex_lock(&isp_subdev->ops_mutex);
+
+ isp4sd_module_enable(isp_subdev, true);
+
+ isp_subdev->sensor_info.start_stream_cmd_sent = false;
+ isp_subdev->sensor_info.buf_sent_cnt = 0;
+
+ if (ispif->status < ISP4IF_STATUS_PWR_ON) {
+ unsigned int perf_state = ISP4SD_PERFORMANCE_STATE_HIGH;
+
+ ret = pm_runtime_get_sync(dev);
+ if (ret) {
+ dev_err(dev, "fail to power on isp_subdev ret %d\n",
+ ret);
+ goto err_unlock_and_close;
+ }
+
+ /* ISPPG ISP Power Status */
+ isp4hw_wreg(isp_subdev->mmio, ISP_POWER_STATUS, 0x7FF);
+ ret = dev_pm_genpd_set_performance_state(dev, perf_state);
+ if (ret) {
+ dev_err(dev,
+ "fail to set performance state %u, ret %d\n",
+ perf_state, ret);
+ goto err_unlock_and_close;
+ }
+
+ ispif->status = ISP4IF_STATUS_PWR_ON;
+
+ if (isp4sd_start_resp_proc_threads(isp_subdev)) {
+ dev_err(dev, "isp_start_resp_proc_threads fail");
+ goto err_unlock_and_close;
+ } else {
+ dev_dbg(dev, "create resp threads ok");
+ }
+ }
+
+ isp_subdev->sensor_info.start_stream_cmd_sent = false;
+ isp_subdev->sensor_info.buf_sent_cnt = 0;
+
+ ret = isp4if_start(ispif);
+ if (ret) {
+ dev_err(dev, "fail to start isp_subdev interface\n");
+ goto err_unlock_and_close;
+ }
+
+ mutex_unlock(&isp_subdev->ops_mutex);
+ return 0;
+err_unlock_and_close:
+ mutex_unlock(&isp_subdev->ops_mutex);
+ isp4sd_pwroff_and_deinit(isp_subdev);
+ return -EINVAL;
+}
+
+static int isp4sd_stop_stream(struct isp4_subdev *isp_subdev,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct isp4sd_output_info *output_info =
+ &isp_subdev->sensor_info.output_info;
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+ int ret = 0;
+
+ dev_dbg(dev, "status %i\n", output_info->start_status);
+ mutex_lock(&isp_subdev->ops_mutex);
+
+ if (output_info->start_status == ISP4SD_START_STATUS_STARTED) {
+ struct isp4fw_cmd_enable_out_ch cmd_ch_disable;
+
+ cmd_ch_disable.ch = ISP_PIPE_OUT_CH_PREVIEW;
+ cmd_ch_disable.is_enable = false;
+ ret = isp4if_send_command_sync(ispif,
+ CMD_ID_ENABLE_OUT_CHAN,
+ &cmd_ch_disable,
+ sizeof(cmd_ch_disable),
+ ISP4SD_FW_CMD_TIMEOUT_IN_MS);
+ if (ret)
+ dev_err(dev, "fail to disable stream\n");
+ else
+ dev_dbg(dev, "wait disable stream suc\n");
+
+ ret = isp4if_send_command_sync(ispif, CMD_ID_STOP_STREAM,
+ NULL,
+ 0,
+ ISP4SD_FW_CMD_TIMEOUT_IN_MS);
+ if (ret)
+ dev_err(dev, "fail to stop steam\n");
+ else
+ dev_dbg(dev, "wait stop stream suc\n");
+ }
+
+ isp4if_clear_bufq(ispif);
+
+ output_info->start_status = ISP4SD_START_STATUS_NOT_START;
+ isp4sd_reset_stream_info(isp_subdev, state, pad);
+
+ mutex_unlock(&isp_subdev->ops_mutex);
+
+ isp4sd_uninit_stream(isp_subdev, state, pad);
+
+ return ret;
+}
+
+static int isp4sd_start_stream(struct isp4_subdev *isp_subdev,
+ struct v4l2_subdev_state *state, u32 pad)
+{
+ struct isp4sd_output_info *output_info =
+ &isp_subdev->sensor_info.output_info;
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct device *dev = isp_subdev->dev;
+ int ret;
+
+ mutex_lock(&isp_subdev->ops_mutex);
+
+ if (ispif->status != ISP4IF_STATUS_FW_RUNNING) {
+ mutex_unlock(&isp_subdev->ops_mutex);
+ dev_err(dev, "fail, bad fsm %d", ispif->status);
+ return -EINVAL;
+ }
+
+ ret = isp4sd_init_stream(isp_subdev);
+
+ if (ret) {
+ dev_err(dev, "fail to init isp_subdev stream\n");
+ ret = -EINVAL;
+ goto unlock_and_check_ret;
+ }
+
+ if (output_info->start_status == ISP4SD_START_STATUS_STARTED) {
+ ret = 0;
+ dev_dbg(dev, "stream started, do nothing\n");
+ goto unlock_and_check_ret;
+ } else if (output_info->start_status ==
+ ISP4SD_START_STATUS_START_FAIL) {
+ ret = -EINVAL;
+ dev_err(dev, "stream fail to start before\n");
+ goto unlock_and_check_ret;
+ }
+
+ if (isp4sd_setup_output(isp_subdev, state, pad)) {
+ dev_err(dev, "fail to setup output\n");
+ ret = -EINVAL;
+ } else {
+ ret = 0;
+ dev_dbg(dev, "suc to setup out\n");
+ }
+unlock_and_check_ret:
+ mutex_unlock(&isp_subdev->ops_mutex);
+ if (ret) {
+ isp4sd_stop_stream(isp_subdev, state, pad);
+ dev_err(dev, "start stream fail\n");
+ } else {
+ dev_dbg(dev, "start stream suc\n");
+ }
+
+ return ret;
+}
+
+static int isp4sd_subdev_pre_streamon(struct v4l2_subdev *sd, u32 flags)
+{
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
+ int ret;
+
+ u32 num_data_lanes = isp_subdev->phy_num_data_lanes;
+ u64 phy_bit_rate = isp_subdev->phy_bit_rate;
+ u32 phy_id = isp_subdev->phy_id;
+
+ ret = isp4phy_start(isp_subdev->dev,
+ isp_subdev->isp_phy_mmio, phy_id,
+ phy_bit_rate, num_data_lanes);
+ if (ret) {
+ dev_err(isp_subdev->dev,
+ "fail start phy,lane %d id %u bitrate %llu, %d\n",
+ num_data_lanes, phy_id, phy_bit_rate, ret);
+ return ret;
+ }
+
+ dev_dbg(isp_subdev->dev, "start phy suc,lane %d id %u bit_rate %llu\n",
+ num_data_lanes, phy_id, phy_bit_rate);
+
+ return ret;
+}
+
+static int isp4sd_subdev_post_streamoff(struct v4l2_subdev *sd)
+{
+ struct isp4_subdev *isp_subdev = to_isp4_subdev(sd);
+ int ret;
+
+ dev_dbg(isp_subdev->dev, "stopping phy %u\n", isp_subdev->phy_id);
+ ret = isp4phy_stop(isp_subdev->isp_phy_mmio,
+ isp_subdev->phy_id);
+ if (ret)
+ dev_err(isp_subdev->dev, "fail to stop the Phy:%d", ret);
+
+ return ret;
+}
+
+static int isp4sd_subdev_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ return 0;
+}
+
+static int isp4sd_set_power(struct v4l2_subdev *sd, int on)
+{
+ struct isp4_subdev *ispsd = to_isp4_subdev(sd);
+
+ if (on)
+ return isp4sd_pwron_and_init(ispsd);
+ else
+ return isp4sd_pwroff_and_deinit(ispsd);
+};
+
+static const struct v4l2_subdev_core_ops isp4sd_core_ops = {
+ .s_power = isp4sd_set_power,
+};
+
+static const struct v4l2_subdev_video_ops isp4sd_video_ops = {
+ .s_stream = v4l2_subdev_s_stream_helper,
+ .pre_streamon = isp4sd_subdev_pre_streamon,
+ .post_streamoff = isp4sd_subdev_post_streamoff,
+};
+
+static int isp4sd_set_pad_format(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state,
+ struct v4l2_subdev_format *fmt)
+{
+ struct isp4sd_output_info *steam_info =
+ &(to_isp4_subdev(sd)->sensor_info.output_info);
+ struct v4l2_mbus_framefmt *format;
+
+ format = v4l2_subdev_state_get_format(sd_state, fmt->pad);
+
+ if (!format) {
+ dev_err(sd->dev, "fail to get state format\n");
+ return -EINVAL;
+ }
+
+ *format = fmt->format;
+ switch (format->code) {
+ case MEDIA_BUS_FMT_YUYV8_1_5X8:
+ steam_info->image_size = format->width * format->height * 3 / 2;
+ break;
+ case MEDIA_BUS_FMT_YUYV8_1X16:
+ steam_info->image_size = format->width * format->height * 2;
+ break;
+ default:
+ steam_info->image_size = 0;
+ break;
+ }
+ if (!steam_info->image_size) {
+ dev_err(sd->dev,
+ "fail set pad format,code 0x%x,width %u, height %u\n",
+ format->code, format->width, format->height);
+ return -EINVAL;
+ }
+ dev_dbg(sd->dev,
+ "set pad format suc, code:%x w:%u h:%u size:%u\n", format->code,
+ format->width, format->height, steam_info->image_size);
+
+ return 0;
+}
+
+static int isp4sd_enable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct isp4_subdev *ispsd = to_isp4_subdev(sd);
+
+ return isp4sd_start_stream(ispsd, state, pad);
+}
+
+static int isp4sd_disable_streams(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state, u32 pad,
+ u64 streams_mask)
+{
+ struct isp4_subdev *ispsd = to_isp4_subdev(sd);
+
+ return isp4sd_stop_stream(ispsd, state, pad);
+}
+
+static const struct v4l2_subdev_pad_ops isp4sd_pad_ops = {
+ .enum_mbus_code = isp4sd_enum_mbus_code,
+ .get_fmt = v4l2_subdev_get_fmt,
+ .set_fmt = isp4sd_set_pad_format,
+ .enable_streams = isp4sd_enable_streams,
+ .disable_streams = isp4sd_disable_streams,
+};
+
+static const struct v4l2_subdev_ops isp4sd_subdev_ops = {
+ .core = &isp4sd_core_ops,
+ .video = &isp4sd_video_ops,
+ .pad = &isp4sd_pad_ops,
+};
+
+static const struct v4l2_subdev_internal_ops isp4sd_subdev_internal_ops = {
+ .open = isp4sd_subdev_open,
+};
+
+static int isp4sd_sdev_link_validate(struct media_link *link)
+{
+ return 0;
+}
+
+static const struct media_entity_operations isp4sd_sdev_ent_ops = {
+ .link_validate = isp4sd_sdev_link_validate,
+};
+
+int isp4sd_init(struct isp4_subdev *isp_subdev,
+ struct v4l2_device *v4l2_dev,
+ void *amdgpu_dev)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4sd_sensor_info *sensor_info;
+ struct isp4sd_output_info *output_info;
+ struct device *dev = v4l2_dev->dev;
+ int ret;
+
+ isp_subdev->dev = dev;
+ isp_subdev->amdgpu_dev = amdgpu_dev;
+ v4l2_subdev_init(&isp_subdev->sdev, &isp4sd_subdev_ops);
+ isp_subdev->sdev.owner = THIS_MODULE;
+ isp_subdev->sdev.dev = dev;
+ snprintf(isp_subdev->sdev.name, sizeof(isp_subdev->sdev.name), "%s",
+ dev_name(dev));
+
+ isp_subdev->sdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE;
+ isp_subdev->sdev.entity.name = isp4sd_entity_name;
+ isp_subdev->sdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
+ isp_subdev->sdev.entity.ops = &isp4sd_sdev_ent_ops;
+ isp_subdev->sdev_pad[0].flags = MEDIA_PAD_FL_SINK;
+ isp_subdev->sdev_pad[1].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&isp_subdev->sdev.entity, 2,
+ isp_subdev->sdev_pad);
+ if (ret) {
+ dev_err(dev, "fail to init isp4 subdev entity pad %d\n", ret);
+ return ret;
+ }
+ ret = v4l2_subdev_init_finalize(&isp_subdev->sdev);
+ if (ret < 0) {
+ dev_err(dev, "fail to init finalize isp4 subdev %d\n",
+ ret);
+ return ret;
+ }
+ ret = v4l2_device_register_subdev(v4l2_dev, &isp_subdev->sdev);
+ if (ret) {
+ dev_err(dev, "fail to register isp4 subdev to V4L2 device %d\n",
+ ret);
+ goto err_media_clean_up;
+ }
+
+ output_info = &isp_subdev->sensor_info.output_info;
+ sensor_info = &isp_subdev->sensor_info;
+
+ isp4if_init(ispif, dev, amdgpu_dev, isp_subdev->mmio);
+
+ mutex_init(&isp_subdev->ops_mutex);
+ sensor_info->start_stream_cmd_sent = false;
+ sensor_info->status = ISP4SD_START_STATUS_NOT_START;
+
+ /* create ISP enable gpio control */
+ isp_subdev->enable_gpio = devm_gpiod_get(isp_subdev->dev,
+ "enable_isp",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(isp_subdev->enable_gpio)) {
+ dev_err(dev, "fail to get gpiod %d\n", ret);
+ media_entity_cleanup(&isp_subdev->sdev.entity);
+ return PTR_ERR(isp_subdev->enable_gpio);
+ }
+
+ isp_subdev->host2fw_seq_num = 1;
+ ispif->status = ISP4IF_STATUS_PWR_OFF;
+
+ if (ret)
+ goto err_media_clean_up;
+ return ret;
+
+err_media_clean_up:
+ media_entity_cleanup(&isp_subdev->sdev.entity);
+ return ret;
+}
+
+void isp4sd_deinit(struct isp4_subdev *isp_subdev)
+{
+ struct isp4_interface *ispif = &isp_subdev->ispif;
+ struct isp4sd_output_info *output_info;
+
+ output_info = &isp_subdev->sensor_info.output_info;
+ media_entity_cleanup(&isp_subdev->sdev.entity);
+
+ isp4if_deinit(ispif);
+
+ isp4sd_module_enable(isp_subdev, false);
+
+ ispif->status = ISP4IF_STATUS_PWR_OFF;
+}
diff --git a/drivers/media/platform/amd/isp4/isp4_subdev.h b/drivers/media/platform/amd/isp4/isp4_subdev.h
new file mode 100644
index 000000000000..99ec914a95ce
--- /dev/null
+++ b/drivers/media/platform/amd/isp4/isp4_subdev.h
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (C) 2025 Advanced Micro Devices, Inc.
+ */
+
+#ifndef _ISP4_CONTEXT_H_
+#define _ISP4_CONTEXT_H_
+
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/platform_device.h>
+#include <linux/uaccess.h>
+#include <linux/types.h>
+#include <linux/debugfs.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "isp4_fw_cmd_resp.h"
+#include "isp4_hw_reg.h"
+#include "isp4_interface.h"
+
+/*
+ * one is for none sesnor specefic response which is not used now
+ * another is for sensor specific response
+ */
+#define ISP4SD_MAX_FW_RESP_STREAM_NUM 2
+
+/*
+ * cmd used to register frame done callback, parameter is
+ * struct isp4sd_register_framedone_cb_param *
+ * when a image buffer is filled by ISP, ISP will call the registered callback.
+ * callback func prototype is isp4sd_framedone_cb, cb_ctx can be anything
+ * provided by caller which will be provided back as the first parameter of the
+ * callback function.
+ * both cb_func and cb_ctx are provide by caller, set cb_func to NULL to
+ * unregister the callback
+ */
+
+/* used to indicate the ISP status*/
+enum isp4sd_status {
+ ISP4SD_STATUS_PWR_OFF,
+ ISP4SD_STATUS_PWR_ON,
+ ISP4SD_STATUS_FW_RUNNING,
+ ISP4SD_STATUS_MAX
+};
+
+/*used to indicate the status of sensor, output stream */
+enum isp4sd_start_status {
+ ISP4SD_START_STATUS_NOT_START,
+ ISP4SD_START_STATUS_STARTED,
+ ISP4SD_START_STATUS_START_FAIL,
+};
+
+struct isp4sd_img_buf_node {
+ struct list_head node;
+ struct isp4if_img_buf_info buf_info;
+};
+
+/* this is isp output after processing bayer raw input from sensor */
+struct isp4sd_output_info {
+ enum isp4sd_start_status start_status;
+ u32 image_size;
+};
+
+/* This struct represents the sensor info which is input or source of ISP,
+ * meta_info_buf is the buffer store the fw to driver metainfo response
+ * status is the sensor status
+ * output_info is the isp output info after ISP processing the sensor input,
+ * start_stream_cmd_sent mean if CMD_ID_START_STREAM has sent to fw.
+ * buf_sent_cnt is buffer count app has sent to receive the images
+ */
+struct isp4sd_sensor_info {
+ struct isp4if_gpu_mem_info *
+ meta_info_buf[ISP4IF_MAX_STREAM_META_BUF_COUNT];
+ struct isp4sd_output_info output_info;
+ enum isp4sd_start_status status;
+ bool start_stream_cmd_sent;
+ u32 buf_sent_cnt;
+};
+
+/*
+ * Thread created by driver to receive fw response
+ * thread will be wakeup by fw to driver response interrupt
+ */
+struct isp4sd_thread_handler {
+ struct task_struct *thread;
+ struct mutex mutex; /* mutex */
+ wait_queue_head_t waitq;
+ int wq_cond;
+};
+
+struct isp4_subdev_thread_param {
+ u32 idx;
+ struct isp4_subdev *isp_subdev;
+};
+
+struct isp4_subdev {
+ struct v4l2_subdev sdev;
+ struct isp4_interface ispif;
+
+ /*
+ * sdev_pad[0] is sink pad connected to sensor
+ * sdev_pad[0] is sink pad connected to sensor
+ * sdev_pad[1] is sourc pad connected v4l2 video device
+ */
+ struct media_pad sdev_pad[2];
+
+ enum isp4sd_status isp_status;
+ struct mutex ops_mutex; /* ops_mutex */
+
+ /* Used to store fw cmds sent to FW whose response driver needs
+ * to wait for
+ */
+ struct isp4sd_thread_handler
+ fw_resp_thread[ISP4SD_MAX_FW_RESP_STREAM_NUM];
+
+ u32 host2fw_seq_num;
+
+ struct isp4sd_sensor_info sensor_info;
+
+ /* gpio descriptor */
+ struct gpio_desc *enable_gpio;
+ struct device *dev;
+ void *amdgpu_dev;
+ void __iomem *mmio;
+ struct isp4_subdev_thread_param
+ isp_resp_para[ISP4SD_MAX_FW_RESP_STREAM_NUM];
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_dir;
+ bool enable_fw_log;
+ char *fw_log_output;
+#endif
+ u32 phy_num_data_lanes;
+ u32 phy_id;
+ u64 phy_bit_rate;
+
+ void __iomem *isp_phy_mmio;
+};
+
+int isp4sd_init(struct isp4_subdev *isp_subdev,
+ struct v4l2_device *v4l2_dev,
+ void *amdgpu_dev);
+void isp4sd_deinit(struct isp4_subdev *isp_subdev);
+
+#endif
--
2.34.1
Powered by blists - more mailing lists