Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0+
config VIDEO_AMD_ISP4_CAPTURE
tristate "AMD ISP4 and camera driver"
depends on DRM_AMDGPU && DRM_AMD_ISP
depends on HAS_DMA
depends on VIDEO_DEV
select VIDEOBUF2_CORE
select VIDEOBUF2_MEMOPS
select VIDEOBUF2_V4L2
select VIDEOBUF2_VMALLOC
select VIDEO_V4L2_SUBDEV_API
help
This is support for AMD ISP4 and camera subsystem driver.
Say Y here to enable the ISP4 and camera device for video capture.
To compile this driver as a module, choose M here. The module will
be called amd_isp4_capture.
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0+
#
# Copyright (C) 2025 Advanced Micro Devices, Inc.
obj-$(CONFIG_VIDEO_AMD_ISP4_CAPTURE) += amd_isp4_capture.o
amd_isp4_capture-objs := isp4.o \
isp4_debug.o \
isp4_interface.o \
isp4_subdev.o \
isp4_video.o
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include <linux/irq.h>
#include <linux/pm_runtime.h>
#include <linux/vmalloc.h>
#include <media/v4l2-ioctl.h>
#include "isp4.h"
#include "isp4_debug.h"
#include "isp4_hw_reg.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_WPT12_INT_MASK)
static const struct {
const char *name;
u32 status_mask;
u32 en_mask;
u32 ack_mask;
u32 rb_int_num;
} isp4_irq[ISP4SD_MAX_FW_RESP_STREAM_NUM] = {
/* The IRQ order is aligned with the isp4_subdev.fw_resp_thread order */
{
.name = "isp_irq_global",
.status_mask =
ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK,
.en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK,
.ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK,
.rb_int_num = 4, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12 */
},
{
.name = "isp_irq_stream1",
.status_mask =
ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK,
.en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK,
.ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK,
.rb_int_num = 0, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9 */
},
};
void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable)
{
u32 intr_en;
/* Synchronize ISP_SYS_INT0_EN writes with the IRQ handler's writes */
spin_lock_irq(&isp_subdev->irq_lock);
intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN);
if (enable)
intr_en |= isp4_irq[index].en_mask;
else
intr_en &= ~isp4_irq[index].en_mask;
isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en);
spin_unlock_irq(&isp_subdev->irq_lock);
}
static void isp4_wake_up_resp_thread(struct isp4_subdev *isp_subdev, u32 index)
{
struct isp4sd_thread_handler *thread_ctx =
&isp_subdev->fw_resp_thread[index];
thread_ctx->resp_ready = true;
wake_up_interruptible(&thread_ctx->waitq);
}
static irqreturn_t isp4_irq_handler(int irq, void *arg)
{
struct isp4_subdev *isp_subdev = arg;
u32 intr_ack = 0, intr_en = 0, intr_status;
int seen = 0;
/* Get the ISP_SYS interrupt status */
intr_status = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_STATUS);
intr_status &= ISP4_FW_RESP_RB_IRQ_STATUS_MASK;
/* Find which ISP_SYS interrupts fired */
for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) {
if (intr_status & isp4_irq[i].status_mask) {
intr_ack |= isp4_irq[i].ack_mask;
intr_en |= isp4_irq[i].en_mask;
seen |= BIT(i);
}
}
/*
* Disable the ISP_SYS interrupts that fired. Must be done before waking
* the response threads, since they re-enable interrupts when finished.
* The lock synchronizes RMW of INT0_EN with isp4_enable_interrupt().
*/
spin_lock(&isp_subdev->irq_lock);
intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN) & ~intr_en;
isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en);
spin_unlock(&isp_subdev->irq_lock);
/*
* Clear the ISP_SYS interrupts. This must be done after the interrupts
* are disabled, so that ISP FW won't flag any new interrupts on these
* streams, and thus we don't need to clear interrupts again before
* re-enabling them in the response thread.
*/
isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_ACK, intr_ack);
/*
* The operation `(seen >> i) << i` is logically equivalent to
* `seen &= ~BIT(i)`, with fewer instructions after compilation.
*/
for (int i; (i = ffs(seen)); seen = (seen >> i) << i)
isp4_wake_up_resp_thread(isp_subdev, i - 1);
return IRQ_HANDLED;
}
static int isp4_capture_probe(struct platform_device *pdev)
{
int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM];
struct device *dev = &pdev->dev;
struct isp4_subdev *isp_subdev;
struct isp4_device *isp_dev;
int ret;
isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL);
if (!isp_dev)
return -ENOMEM;
dev->init_name = ISP4_DRV_NAME;
isp_subdev = &isp_dev->isp_subdev;
isp_subdev->mmio = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(isp_subdev->mmio))
return dev_err_probe(dev, PTR_ERR(isp_subdev->mmio),
"isp ioremap fail\n");
for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) {
irq[i] = platform_get_irq(pdev, isp4_irq[i].rb_int_num);
if (irq[i] < 0)
return dev_err_probe(dev, irq[i],
"fail to get irq %d\n",
isp4_irq[i].rb_int_num);
ret = devm_request_irq(dev, irq[i], isp4_irq_handler,
IRQF_NO_AUTOEN, isp4_irq[i].name,
isp_subdev);
if (ret)
return dev_err_probe(dev, ret, "fail to req irq %d\n",
irq[i]);
}
isp_dev->v4l2_dev.mdev = &isp_dev->mdev;
strscpy(isp_dev->mdev.model, "amd_isp41_mdev",
sizeof(isp_dev->mdev.model));
isp_dev->mdev.dev = dev;
media_device_init(&isp_dev->mdev);
snprintf(isp_dev->v4l2_dev.name, sizeof(isp_dev->v4l2_dev.name),
"AMD-V4L2-ROOT");
ret = v4l2_device_register(dev, &isp_dev->v4l2_dev);
if (ret) {
dev_err_probe(dev, ret, "fail register v4l2 device\n");
goto err_clean_media;
}
pm_runtime_set_suspended(dev);
pm_runtime_enable(dev);
spin_lock_init(&isp_subdev->irq_lock);
ret = isp4sd_init(&isp_dev->isp_subdev, &isp_dev->v4l2_dev, irq);
if (ret) {
dev_err_probe(dev, ret, "fail init isp4 sub dev\n");
goto err_pm_disable;
}
ret = media_create_pad_link(&isp_dev->isp_subdev.sdev.entity,
0,
&isp_dev->isp_subdev.isp_vdev.vdev.entity,
0,
MEDIA_LNK_FL_ENABLED |
MEDIA_LNK_FL_IMMUTABLE);
if (ret) {
dev_err_probe(dev, ret, "fail to create pad link\n");
goto err_isp4_deinit;
}
ret = media_device_register(&isp_dev->mdev);
if (ret) {
dev_err_probe(dev, ret, "fail to register media device\n");
goto err_isp4_deinit;
}
platform_set_drvdata(pdev, isp_dev);
isp_debugfs_create(isp_dev);
return 0;
err_isp4_deinit:
isp4sd_deinit(&isp_dev->isp_subdev);
err_pm_disable:
pm_runtime_disable(dev);
v4l2_device_unregister(&isp_dev->v4l2_dev);
err_clean_media:
media_device_cleanup(&isp_dev->mdev);
return ret;
}
static void isp4_capture_remove(struct platform_device *pdev)
{
struct isp4_device *isp_dev = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
isp_debugfs_remove(isp_dev);
media_device_unregister(&isp_dev->mdev);
isp4sd_deinit(&isp_dev->isp_subdev);
pm_runtime_disable(dev);
v4l2_device_unregister(&isp_dev->v4l2_dev);
media_device_cleanup(&isp_dev->mdev);
}
static struct platform_driver isp4_capture_drv = {
.probe = isp4_capture_probe,
.remove = isp4_capture_remove,
.driver = {
.name = ISP4_DRV_NAME,
}
};
module_platform_driver(isp4_capture_drv);
MODULE_ALIAS("platform:" ISP4_DRV_NAME);
MODULE_IMPORT_NS("DMA_BUF");
MODULE_DESCRIPTION("AMD ISP4 Driver");
MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
MODULE_LICENSE("GPL");
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_H_
#define _ISP4_H_
#include <drm/amd/isp.h>
#include "isp4_subdev.h"
struct isp4_device {
struct v4l2_device v4l2_dev;
struct isp4_subdev isp_subdev;
struct media_device mdev;
};
void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable);
#endif /* _ISP4_H_ */
@@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include "isp4.h"
#include "isp4_debug.h"
#include "isp4_hw_reg.h"
#include "isp4_interface.h"
#define ISP4DBG_FW_LOG_RINGBUF_SIZE (2 * 1024 * 1024)
#define ISP4DBG_MACRO_2_STR(X) #X
#define ISP4DBG_ONE_TIME_LOG_LEN 510
#ifdef CONFIG_DEBUG_FS
void isp_debugfs_create(struct isp4_device *isp_dev)
{
isp_dev->isp_subdev.debugfs_dir = debugfs_create_dir("amd_isp4", NULL);
debugfs_create_bool("fw_log_enable", 0644,
isp_dev->isp_subdev.debugfs_dir,
&isp_dev->isp_subdev.enable_fw_log);
isp_dev->isp_subdev.fw_log_output =
devm_kzalloc(isp_dev->isp_subdev.dev,
ISP4DBG_FW_LOG_RINGBUF_SIZE + 32,
GFP_KERNEL);
}
void isp_debugfs_remove(struct isp4_device *isp_dev)
{
debugfs_remove_recursive(isp_dev->isp_subdev.debugfs_dir);
isp_dev->isp_subdev.debugfs_dir = NULL;
}
static u32 isp_fw_fill_rb_log(struct isp4_subdev *isp, void *sys, u32 rb_size)
{
struct isp4_interface *ispif = &isp->ispif;
char *buf = isp->fw_log_output;
struct device *dev = isp->dev;
u32 rd_ptr, wr_ptr;
u32 total_cnt = 0;
u32 offset = 0;
u32 cnt;
if (!sys || !rb_size)
return 0;
guard(mutex)(&ispif->isp4if_mutex);
rd_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_RPTR0);
wr_ptr = isp4hw_rreg(isp->mmio, ISP_LOG_RB_WPTR0);
do {
if (wr_ptr > rd_ptr)
cnt = wr_ptr - rd_ptr;
else if (wr_ptr < rd_ptr)
cnt = rb_size - rd_ptr;
else
goto quit;
if (cnt > rb_size) {
dev_err(dev, "fail bad fw log size %u\n", cnt);
goto quit;
}
memcpy(buf + offset, sys + rd_ptr, cnt);
offset += cnt;
total_cnt += cnt;
rd_ptr = (rd_ptr + cnt) % rb_size;
} while (rd_ptr < wr_ptr);
isp4hw_wreg(isp->mmio, ISP_LOG_RB_RPTR0, rd_ptr);
quit:
return total_cnt;
}
void isp_fw_log_print(struct isp4_subdev *isp)
{
struct isp4_interface *ispif = &isp->ispif;
char *fw_log_buf = isp->fw_log_output;
u32 cnt;
if (!isp->enable_fw_log || !fw_log_buf)
return;
cnt = isp_fw_fill_rb_log(isp, ispif->fw_log_buf->sys_addr,
ispif->fw_log_buf->mem_size);
if (cnt) {
char temp_ch;
char *str;
char *end;
/* line end */
char *le;
str = (char *)fw_log_buf;
end = ((char *)fw_log_buf + cnt);
fw_log_buf[cnt] = 0;
while (str < end) {
le = strchr(str, 0x0A);
if ((le && str + ISP4DBG_ONE_TIME_LOG_LEN >= le) ||
(!le && str + ISP4DBG_ONE_TIME_LOG_LEN >= end)) {
if (le)
*le = 0;
if (*str != '\0')
dev_dbg(isp->dev, "%s", str);
if (le) {
*le = 0x0A;
str = le + 1;
} else {
break;
}
} else {
u32 tmp_len = ISP4DBG_ONE_TIME_LOG_LEN;
temp_ch = str[tmp_len];
str[tmp_len] = 0;
dev_dbg(isp->dev, "%s", str);
str[tmp_len] = temp_ch;
str = &str[tmp_len];
}
}
}
}
#endif
char *isp4dbg_get_buf_src_str(u32 src)
{
switch (src) {
case ISP4FW_BUFFER_SOURCE_STREAM:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_SOURCE_STREAM);
default:
return "Unknown buf source";
}
}
char *isp4dbg_get_buf_done_str(u32 status)
{
switch (status) {
case ISP4FW_BUFFER_STATUS_INVALID:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_INVALID);
case ISP4FW_BUFFER_STATUS_SKIPPED:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_SKIPPED);
case ISP4FW_BUFFER_STATUS_EXIST:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_EXIST);
case ISP4FW_BUFFER_STATUS_DONE:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DONE);
case ISP4FW_BUFFER_STATUS_LACK:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_LACK);
case ISP4FW_BUFFER_STATUS_DIRTY:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_DIRTY);
case ISP4FW_BUFFER_STATUS_MAX:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_STATUS_MAX);
default:
return "Unknown Buf Done Status";
}
}
char *isp4dbg_get_img_fmt_str(int fmt /* enum isp4fw_image_format * */)
{
switch (fmt) {
case ISP4FW_IMAGE_FORMAT_NV12:
return "NV12";
case ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED:
return "YUV422INTERLEAVED";
default:
return "unknown fmt";
}
}
void isp4dbg_show_bufmeta_info(struct device *dev, char *pre,
void *in, void *orig_buf)
{
struct isp4fw_buffer_meta_info *p;
struct isp4if_img_buf_info *orig;
if (!in)
return;
if (!pre)
pre = "";
p = in;
orig = orig_buf;
dev_dbg(dev, "%s(%s) en:%d,stat:%s(%u),src:%s\n", pre,
isp4dbg_get_img_fmt_str(p->image_prop.image_format),
p->enabled, isp4dbg_get_buf_done_str(p->status), p->status,
isp4dbg_get_buf_src_str(p->source));
dev_dbg(dev, "%p,0x%llx(%u) %p,0x%llx(%u) %p,0x%llx(%u)\n",
orig->planes[0].sys_addr, orig->planes[0].mc_addr,
orig->planes[0].len, orig->planes[1].sys_addr,
orig->planes[1].mc_addr, orig->planes[1].len,
orig->planes[2].sys_addr, orig->planes[2].mc_addr,
orig->planes[2].len);
}
char *isp4dbg_get_buf_type(u32 type)
{
/* enum isp4fw_buffer_type */
switch (type) {
case ISP4FW_BUFFER_TYPE_PREVIEW:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_PREVIEW);
case ISP4FW_BUFFER_TYPE_META_INFO:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_META_INFO);
case ISP4FW_BUFFER_TYPE_MEM_POOL:
return ISP4DBG_MACRO_2_STR(ISP4FW_BUFFER_TYPE_MEM_POOL);
default:
return "unknown type";
}
}
char *isp4dbg_get_cmd_str(u32 cmd)
{
switch (cmd) {
case ISP4FW_CMD_ID_START_STREAM:
return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_START_STREAM);
case ISP4FW_CMD_ID_STOP_STREAM:
return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_STOP_STREAM);
case ISP4FW_CMD_ID_SEND_BUFFER:
return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SEND_BUFFER);
case ISP4FW_CMD_ID_SET_STREAM_CONFIG:
return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_STREAM_CONFIG);
case ISP4FW_CMD_ID_SET_OUT_CHAN_PROP:
return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_SET_OUT_CHAN_PROP);
case ISP4FW_CMD_ID_ENABLE_OUT_CHAN:
return ISP4DBG_MACRO_2_STR(ISP4FW_CMD_ID_ENABLE_OUT_CHAN);
default:
return "unknown cmd";
}
}
char *isp4dbg_get_resp_str(u32 cmd)
{
switch (cmd) {
case ISP4FW_RESP_ID_CMD_DONE:
return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_CMD_DONE);
case ISP4FW_RESP_ID_NOTI_FRAME_DONE:
return ISP4DBG_MACRO_2_STR(ISP4FW_RESP_ID_NOTI_FRAME_DONE);
default:
return "unknown respid";
}
}
char *isp4dbg_get_if_stream_str(u32 stream /* enum fw_cmd_resp_stream_id */)
{
switch (stream) {
case ISP4IF_STREAM_ID_GLOBAL:
return "STREAM_GLOBAL";
case ISP4IF_STREAM_ID_1:
return "STREAM1";
default:
return "unknown streamID";
}
}
char *isp4dbg_get_out_ch_str(int ch /* enum isp4fw_pipe_out_ch */)
{
switch ((enum isp4fw_pipe_out_ch)ch) {
case ISP4FW_ISP_PIPE_OUT_CH_PREVIEW:
return "prev";
default:
return "unknown channel";
}
}
@@ -0,0 +1,41 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_DEBUG_H_
#define _ISP4_DEBUG_H_
#include <linux/dev_printk.h>
#include <linux/printk.h>
#include "isp4_subdev.h"
#ifdef CONFIG_DEBUG_FS
struct isp4_device;
void isp_debugfs_create(struct isp4_device *isp_dev);
void isp_debugfs_remove(struct isp4_device *isp_dev);
void isp_fw_log_print(struct isp4_subdev *isp);
#else
/* to avoid checkpatch warning */
#define isp_debugfs_create(cam) ((void)(cam))
#define isp_debugfs_remove(cam) ((void)(cam))
#define isp_fw_log_print(isp) ((void)(isp))
#endif /* CONFIG_DEBUG_FS */
void isp4dbg_show_bufmeta_info(struct device *dev, char *pre, void *p,
void *orig_buf /* struct sys_img_buf_handle */);
char *isp4dbg_get_img_fmt_str(int fmt /* enum _image_format_t */);
char *isp4dbg_get_out_ch_str(int ch /* enum _isp_pipe_out_ch_t */);
char *isp4dbg_get_cmd_str(u32 cmd);
char *isp4dbg_get_buf_type(u32 type);/* enum _buffer_type_t */
char *isp4dbg_get_resp_str(u32 resp);
char *isp4dbg_get_buf_src_str(u32 src);
char *isp4dbg_get_buf_done_str(u32 status);
char *isp4dbg_get_if_stream_str(u32 stream);
#endif /* _ISP4_DEBUG_H_ */
@@ -0,0 +1,318 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_FW_CMD_RESP_H_
#define _ISP4_FW_CMD_RESP_H_
/*
* Two types of command/response channel.
* Type Global Command has one command/response channel.
* Type Stream Command has one command/response channel.
*----------- ------------
*| | --------------------------- | |
*| | ---->| Global Command |----> | |
*| | --------------------------- | |
*| | | |
*| | | |
*| | --------------------------- | |
*| | ---->| Stream Command |----> | |
*| | --------------------------- | |
*| | | |
*| | | |
*| | | |
*| HOST | | Firmware |
*| | | |
*| | | |
*| | -------------------------- | |
*| | <----| Global Response |<---- | |
*| | -------------------------- | |
*| | | |
*| | | |
*| | -------------------------- | |
*| | <----| Stream Response |<---- | |
*| | -------------------------- | |
*| | | |
*| | | |
*----------- ------------
*/
/*
* cmd_id is in the format of following type:
* type: indicate command type, global/stream commands.
* group: indicate the command group.
* id: A unique command identification in one type and group.
* |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->|
* | type | group | id |
*/
#define ISP4FW_CMD_TYPE_SHIFT 24
#define ISP4FW_CMD_GROUP_SHIFT 16
#define ISP4FW_CMD_TYPE_STREAM_CTRL (0x2U << ISP4FW_CMD_TYPE_SHIFT)
#define ISP4FW_CMD_GROUP_STREAM_CTRL (0x1U << ISP4FW_CMD_GROUP_SHIFT)
#define ISP4FW_CMD_GROUP_STREAM_BUFFER (0x4U << ISP4FW_CMD_GROUP_SHIFT)
/* Stream Command */
#define ISP4FW_CMD_ID_SET_STREAM_CONFIG (ISP4FW_CMD_TYPE_STREAM_CTRL\
| ISP4FW_CMD_GROUP_STREAM_CTRL | 0x1)
#define ISP4FW_CMD_ID_SET_OUT_CHAN_PROP (ISP4FW_CMD_TYPE_STREAM_CTRL\
| ISP4FW_CMD_GROUP_STREAM_CTRL | 0x3)
#define ISP4FW_CMD_ID_ENABLE_OUT_CHAN (ISP4FW_CMD_TYPE_STREAM_CTRL\
| ISP4FW_CMD_GROUP_STREAM_CTRL | 0x5)
#define ISP4FW_CMD_ID_START_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\
| ISP4FW_CMD_GROUP_STREAM_CTRL | 0x7)
#define ISP4FW_CMD_ID_STOP_STREAM (ISP4FW_CMD_TYPE_STREAM_CTRL\
| ISP4FW_CMD_GROUP_STREAM_CTRL | 0x8)
/* Stream Buffer Command */
#define ISP4FW_CMD_ID_SEND_BUFFER (ISP4FW_CMD_TYPE_STREAM_CTRL\
| ISP4FW_CMD_GROUP_STREAM_BUFFER | 0x1)
/*
* resp_id is in the format of following type:
* type: indicate command type, global/stream commands.
* group: indicate the command group.
* id: A unique command identification in one type and group.
* |<-Bit31 ~ Bit24->|<-Bit23 ~ Bit16->|<-Bit15 ~ Bit0->|
* | type | group | id |
*/
#define ISP4FW_RESP_GROUP_SHIFT 16
#define ISP4FW_RESP_GROUP_GENERAL (0x1 << ISP4FW_RESP_GROUP_SHIFT)
#define ISP4FW_RESP_GROUP_NOTIFICATION (0x3 << ISP4FW_RESP_GROUP_SHIFT)
/* General Response */
#define ISP4FW_RESP_ID_CMD_DONE (ISP4FW_RESP_GROUP_GENERAL | 0x1)
/* Notification */
#define ISP4FW_RESP_ID_NOTI_FRAME_DONE (ISP4FW_RESP_GROUP_NOTIFICATION | 0x1)
#define ISP4FW_CMD_STATUS_SUCCESS 0
#define ISP4FW_CMD_STATUS_FAIL 1
#define ISP4FW_CMD_STATUS_SKIPPED 2
#define ISP4FW_ADDR_SPACE_TYPE_GPU_VA 4
#define ISP4FW_MEMORY_POOL_SIZE (100 * 1024 * 1024)
/*
* standard ISP pipeline: mipicsi=>isp
*/
#define ISP4FW_MIPI0_ISP_PIPELINE_ID 0x5f91
enum isp4fw_sensor_id {
/* Sensor id for ISP input from MIPI port 0 */
ISP4FW_SENSOR_ID_ON_MIPI0 = 0,
};
enum isp4fw_stream_id {
ISP4FW_STREAM_ID_INVALID = -1,
ISP4FW_STREAM_ID_1 = 0,
ISP4FW_STREAM_ID_2 = 1,
ISP4FW_STREAM_ID_3 = 2,
ISP4FW_STREAM_ID_MAXIMUM
};
enum isp4fw_image_format {
/* 4:2:0,semi-planar, 8-bit */
ISP4FW_IMAGE_FORMAT_NV12 = 1,
/* interleave, 4:2:2, 8-bit */
ISP4FW_IMAGE_FORMAT_YUV422INTERLEAVED = 7,
};
enum isp4fw_pipe_out_ch {
ISP4FW_ISP_PIPE_OUT_CH_PREVIEW = 0,
};
enum isp4fw_yuv_range {
ISP4FW_ISP_YUV_RANGE_FULL = 0, /* YUV value range in 0~255 */
ISP4FW_ISP_YUV_RANGE_NARROW = 1, /* YUV value range in 16~235 */
ISP4FW_ISP_YUV_RANGE_MAX
};
enum isp4fw_buffer_type {
ISP4FW_BUFFER_TYPE_PREVIEW = 8,
ISP4FW_BUFFER_TYPE_META_INFO = 10,
ISP4FW_BUFFER_TYPE_MEM_POOL = 15,
};
enum isp4fw_buffer_status {
/* The buffer is INVALID */
ISP4FW_BUFFER_STATUS_INVALID,
/* The buffer is not filled with image data */
ISP4FW_BUFFER_STATUS_SKIPPED,
/* The buffer is available and awaiting to be filled */
ISP4FW_BUFFER_STATUS_EXIST,
/* The buffer is filled with image data */
ISP4FW_BUFFER_STATUS_DONE,
/* The buffer is unavailable */
ISP4FW_BUFFER_STATUS_LACK,
/* The buffer is dirty, probably caused by LMI leakage */
ISP4FW_BUFFER_STATUS_DIRTY,
ISP4FW_BUFFER_STATUS_MAX
};
enum isp4fw_buffer_source {
/* The buffer is from the stream buffer queue */
ISP4FW_BUFFER_SOURCE_STREAM,
};
struct isp4fw_error_code {
u32 code1;
u32 code2;
u32 code3;
u32 code4;
u32 code5;
};
/* Command Structure for FW */
struct isp4fw_cmd {
u32 cmd_seq_num;
u32 cmd_id;
u32 cmd_param[12];
u16 cmd_stream_id;
u8 cmd_silent_resp;
u8 reserved;
u32 cmd_check_sum;
};
struct isp4fw_resp_cmd_done {
/*
* The host2fw command seqNum.
* To indicate which command this response refers to.
*/
u32 cmd_seq_num;
/* The host2fw command id for host double check. */
u32 cmd_id;
/*
* Indicate the command process status.
* 0 means success. 1 means fail. 2 means skipped
*/
u16 cmd_status;
/*
* If cmd_status is 1, the command failed. The host can check
* isp4fw_error_code for details.
*/
u16 isp4fw_error_code;
/* The response payload type varies by cmd. */
u8 payload[36];
};
struct isp4fw_resp_param_package {
u32 package_addr_lo; /* The low 32 bit of the pkg address. */
u32 package_addr_hi; /* The high 32 bit of the pkg address. */
u32 package_size; /* The total pkg size in bytes. */
u32 package_check_sum; /* The byte sum of the pkg. */
};
struct isp4fw_resp {
u32 resp_seq_num;
u32 resp_id;
union {
struct isp4fw_resp_cmd_done cmd_done;
struct isp4fw_resp_param_package frame_done;
u32 resp_param[12];
} param;
u8 reserved[4];
u32 resp_check_sum;
};
struct isp4fw_mipi_pipe_path_cfg {
u32 b_enable;
enum isp4fw_sensor_id isp4fw_sensor_id;
};
struct isp4fw_isp_pipe_path_cfg {
u32 isp_pipe_id; /* pipe ids for pipeline construction */
};
struct isp4fw_isp_stream_cfg {
/* Isp mipi path */
struct isp4fw_mipi_pipe_path_cfg mipi_pipe_path_cfg;
/* Isp pipe path */
struct isp4fw_isp_pipe_path_cfg isp_pipe_path_cfg;
/* enable TNR */
u32 b_enable_tnr;
/*
* Number of frames for RTA processing.
* Set to 0 to use the firmware's default value.
*/
u32 rta_frames_per_proc;
};
struct isp4fw_image_prop {
enum isp4fw_image_format image_format;
u32 width;
u32 height;
u32 luma_pitch;
u32 chroma_pitch;
enum isp4fw_yuv_range yuv_range;
};
struct isp4fw_buffer {
/*
* A check num for debug usage, host can set the buf_tags
* to different numbers
*/
u32 buf_tags;
union {
u32 value;
struct {
u32 space : 16;
u32 vmid : 16;
} bit;
} vmid_space;
u32 buf_base_a_lo; /* Low address of buffer A */
u32 buf_base_a_hi; /* High address of buffer A */
u32 buf_size_a; /* Buffer size of buffer A */
u32 buf_base_b_lo; /* Low address of buffer B */
u32 buf_base_b_hi; /* High address of buffer B */
u32 buf_size_b; /* Buffer size of buffer B */
u32 buf_base_c_lo; /* Low address of buffer C */
u32 buf_base_c_hi; /* High address of buffer C */
u32 buf_size_c; /* Buffer size of buffer C */
};
struct isp4fw_buffer_meta_info {
u32 enabled; /* enabled flag */
enum isp4fw_buffer_status status; /* BufferStatus */
struct isp4fw_error_code err; /* err code */
enum isp4fw_buffer_source source; /* BufferSource */
struct isp4fw_image_prop image_prop; /* image_prop */
struct isp4fw_buffer buffer; /* buffer info */
};
struct isp4fw_meta_info {
u32 poc; /* frame id */
u32 fc_id; /* frame ctl id */
u32 time_stamp_lo; /* timestamp low 32 bits */
u32 time_stamp_hi; /* timestamp_high 32 bits */
struct isp4fw_buffer_meta_info preview; /* preview BufferMetaInfo */
};
struct isp4fw_cmd_send_buffer {
enum isp4fw_buffer_type buffer_type;
struct isp4fw_buffer buffer; /* buffer info */
};
struct isp4fw_cmd_set_out_ch_prop {
enum isp4fw_pipe_out_ch ch; /* ISP output channel */
struct isp4fw_image_prop image_prop; /* image property */
};
struct isp4fw_cmd_enable_out_ch {
enum isp4fw_pipe_out_ch ch; /* ISP output channel */
u32 is_enable; /* If channel is enabled or not */
};
struct isp4fw_cmd_set_stream_cfg {
struct isp4fw_isp_stream_cfg stream_cfg; /* stream path config */
};
#endif /* _ISP4_FW_CMD_RESP_H_ */
@@ -0,0 +1,124 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_HW_REG_H_
#define _ISP4_HW_REG_H_
#include <linux/io.h>
#define ISP_SOFT_RESET 0x62000
#define ISP_SYS_INT0_EN 0x62010
#define ISP_SYS_INT0_STATUS 0x62014
#define ISP_SYS_INT0_ACK 0x62018
#define ISP_CCPU_CNTL 0x62054
#define ISP_STATUS 0x62058
#define ISP_LOG_RB_BASE_LO0 0x62148
#define ISP_LOG_RB_BASE_HI0 0x6214c
#define ISP_LOG_RB_SIZE0 0x62150
#define ISP_LOG_RB_RPTR0 0x62154
#define ISP_LOG_RB_WPTR0 0x62158
#define ISP_RB_BASE_LO1 0x62170
#define ISP_RB_BASE_HI1 0x62174
#define ISP_RB_SIZE1 0x62178
#define ISP_RB_RPTR1 0x6217c
#define ISP_RB_WPTR1 0x62180
#define ISP_RB_BASE_LO2 0x62184
#define ISP_RB_BASE_HI2 0x62188
#define ISP_RB_SIZE2 0x6218c
#define ISP_RB_RPTR2 0x62190
#define ISP_RB_WPTR2 0x62194
#define ISP_RB_BASE_LO3 0x62198
#define ISP_RB_BASE_HI3 0x6219c
#define ISP_RB_SIZE3 0x621a0
#define ISP_RB_RPTR3 0x621a4
#define ISP_RB_WPTR3 0x621a8
#define ISP_RB_BASE_LO4 0x621ac
#define ISP_RB_BASE_HI4 0x621b0
#define ISP_RB_SIZE4 0x621b4
#define ISP_RB_RPTR4 0x621b8
#define ISP_RB_WPTR4 0x621bc
#define ISP_RB_BASE_LO5 0x621c0
#define ISP_RB_BASE_HI5 0x621c4
#define ISP_RB_SIZE5 0x621c8
#define ISP_RB_RPTR5 0x621cc
#define ISP_RB_WPTR5 0x621d0
#define ISP_RB_BASE_LO6 0x621d4
#define ISP_RB_BASE_HI6 0x621d8
#define ISP_RB_SIZE6 0x621dc
#define ISP_RB_RPTR6 0x621e0
#define ISP_RB_WPTR6 0x621e4
#define ISP_RB_BASE_LO7 0x621e8
#define ISP_RB_BASE_HI7 0x621ec
#define ISP_RB_SIZE7 0x621f0
#define ISP_RB_RPTR7 0x621f4
#define ISP_RB_WPTR7 0x621f8
#define ISP_RB_BASE_LO8 0x621fc
#define ISP_RB_BASE_HI8 0x62200
#define ISP_RB_SIZE8 0x62204
#define ISP_RB_RPTR8 0x62208
#define ISP_RB_WPTR8 0x6220c
#define ISP_RB_BASE_LO9 0x62210
#define ISP_RB_BASE_HI9 0x62214
#define ISP_RB_SIZE9 0x62218
#define ISP_RB_RPTR9 0x6221c
#define ISP_RB_WPTR9 0x62220
#define ISP_RB_BASE_LO10 0x62224
#define ISP_RB_BASE_HI10 0x62228
#define ISP_RB_SIZE10 0x6222c
#define ISP_RB_RPTR10 0x62230
#define ISP_RB_WPTR10 0x62234
#define ISP_RB_BASE_LO11 0x62238
#define ISP_RB_BASE_HI11 0x6223c
#define ISP_RB_SIZE11 0x62240
#define ISP_RB_RPTR11 0x62244
#define ISP_RB_WPTR11 0x62248
#define ISP_RB_BASE_LO12 0x6224c
#define ISP_RB_BASE_HI12 0x62250
#define ISP_RB_SIZE12 0x62254
#define ISP_RB_RPTR12 0x62258
#define ISP_RB_WPTR12 0x6225c
#define ISP_POWER_STATUS 0x60000
/* ISP_SOFT_RESET */
#define ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK 0x00000001UL
/* ISP_CCPU_CNTL */
#define ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK 0x00040000UL
/* ISP_STATUS */
#define ISP_STATUS__CCPU_REPORT_MASK 0x000000feUL
/* ISP_SYS_INT0_STATUS */
#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK 0x00010000UL
#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT10_INT_MASK 0x00040000UL
#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT11_INT_MASK 0x00100000UL
#define ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK 0x00400000UL
/* ISP_SYS_INT0_EN */
#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK 0x00010000UL
#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT10_EN_MASK 0x00040000UL
#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT11_EN_MASK 0x00100000UL
#define ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK 0x00400000UL
/* ISP_SYS_INT0_ACK */
#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK 0x00010000UL
#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT10_ACK_MASK 0x00040000UL
#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT11_ACK_MASK 0x00100000UL
#define ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK 0x00400000UL
/* Helper functions for reading isp registers */
static inline u32 isp4hw_rreg(void __iomem *base, u32 reg)
{
return readl(base + reg);
}
/* Helper functions for writing isp registers */
static inline void isp4hw_wreg(void __iomem *base, u32 reg, u32 val)
{
return writel(val, base + reg);
}
#endif /* _ISP4_HW_REG_H_ */
@@ -0,0 +1,832 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include <linux/iopoll.h>
#include "isp4_debug.h"
#include "isp4_fw_cmd_resp.h"
#include "isp4_hw_reg.h"
#include "isp4_interface.h"
#define ISP4IF_FW_RESP_RB_IRQ_EN_MASK \
(ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK\
| ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK)
#define ISP4IF_FW_CMD_TIMEOUT (HZ / 2)
struct isp4if_rb_config {
const char *name;
u32 index;
u32 reg_rptr;
u32 reg_wptr;
u32 reg_base_lo;
u32 reg_base_hi;
u32 reg_size;
u32 val_size;
u64 base_mc_addr;
void *base_sys_addr;
};
/* FW cmd ring buffer configuration */
static struct isp4if_rb_config isp4if_cmd_rb_config[ISP4IF_STREAM_ID_MAX] = {
{
.name = "CMD_RB_GBL0",
.index = 3,
.reg_rptr = ISP_RB_RPTR4,
.reg_wptr = ISP_RB_WPTR4,
.reg_base_lo = ISP_RB_BASE_LO4,
.reg_base_hi = ISP_RB_BASE_HI4,
.reg_size = ISP_RB_SIZE4,
},
{
.name = "CMD_RB_STR1",
.index = 0,
.reg_rptr = ISP_RB_RPTR1,
.reg_wptr = ISP_RB_WPTR1,
.reg_base_lo = ISP_RB_BASE_LO1,
.reg_base_hi = ISP_RB_BASE_HI1,
.reg_size = ISP_RB_SIZE1,
},
{
.name = "CMD_RB_STR2",
.index = 1,
.reg_rptr = ISP_RB_RPTR2,
.reg_wptr = ISP_RB_WPTR2,
.reg_base_lo = ISP_RB_BASE_LO2,
.reg_base_hi = ISP_RB_BASE_HI2,
.reg_size = ISP_RB_SIZE2,
},
{
.name = "CMD_RB_STR3",
.index = 2,
.reg_rptr = ISP_RB_RPTR3,
.reg_wptr = ISP_RB_WPTR3,
.reg_base_lo = ISP_RB_BASE_LO3,
.reg_base_hi = ISP_RB_BASE_HI3,
.reg_size = ISP_RB_SIZE3,
},
};
/* FW resp ring buffer configuration */
static struct isp4if_rb_config isp4if_resp_rb_config[ISP4IF_STREAM_ID_MAX] = {
{
.name = "RES_RB_GBL0",
.index = 3,
.reg_rptr = ISP_RB_RPTR12,
.reg_wptr = ISP_RB_WPTR12,
.reg_base_lo = ISP_RB_BASE_LO12,
.reg_base_hi = ISP_RB_BASE_HI12,
.reg_size = ISP_RB_SIZE12,
},
{
.name = "RES_RB_STR1",
.index = 0,
.reg_rptr = ISP_RB_RPTR9,
.reg_wptr = ISP_RB_WPTR9,
.reg_base_lo = ISP_RB_BASE_LO9,
.reg_base_hi = ISP_RB_BASE_HI9,
.reg_size = ISP_RB_SIZE9,
},
{
.name = "RES_RB_STR2",
.index = 1,
.reg_rptr = ISP_RB_RPTR10,
.reg_wptr = ISP_RB_WPTR10,
.reg_base_lo = ISP_RB_BASE_LO10,
.reg_base_hi = ISP_RB_BASE_HI10,
.reg_size = ISP_RB_SIZE10,
},
{
.name = "RES_RB_STR3",
.index = 2,
.reg_rptr = ISP_RB_RPTR11,
.reg_wptr = ISP_RB_WPTR11,
.reg_base_lo = ISP_RB_BASE_LO11,
.reg_base_hi = ISP_RB_BASE_HI11,
.reg_size = ISP_RB_SIZE11,
},
};
/* FW log ring buffer configuration */
static struct isp4if_rb_config isp4if_log_rb_config = {
.name = "LOG_RB",
.index = 0,
.reg_rptr = ISP_LOG_RB_RPTR0,
.reg_wptr = ISP_LOG_RB_WPTR0,
.reg_base_lo = ISP_LOG_RB_BASE_LO0,
.reg_base_hi = ISP_LOG_RB_BASE_HI0,
.reg_size = ISP_LOG_RB_SIZE0,
};
static struct isp4if_gpu_mem_info *
isp4if_gpu_mem_alloc(struct isp4_interface *ispif, u32 mem_size)
{
struct isp4if_gpu_mem_info *mem_info;
struct device *dev = ispif->dev;
int ret;
mem_info = kmalloc_obj(*mem_info, GFP_KERNEL);
if (!mem_info)
return NULL;
mem_info->mem_size = mem_size;
ret = isp_kernel_buffer_alloc(dev, mem_info->mem_size,
&mem_info->mem_handle,
&mem_info->gpu_mc_addr,
&mem_info->sys_addr);
if (ret) {
kfree(mem_info);
return NULL;
}
return mem_info;
}
static void isp4if_gpu_mem_free(struct isp4_interface *ispif,
struct isp4if_gpu_mem_info **mem_info_ptr)
{
struct isp4if_gpu_mem_info *mem_info = *mem_info_ptr;
struct device *dev = ispif->dev;
if (!mem_info) {
dev_err(dev, "invalid mem_info\n");
return;
}
*mem_info_ptr = NULL;
isp_kernel_buffer_free(&mem_info->mem_handle, &mem_info->gpu_mc_addr,
&mem_info->sys_addr);
kfree(mem_info);
}
static void isp4if_dealloc_fw_gpumem(struct isp4_interface *ispif)
{
isp4if_gpu_mem_free(ispif, &ispif->fw_mem_pool);
isp4if_gpu_mem_free(ispif, &ispif->fw_cmd_resp_buf);
isp4if_gpu_mem_free(ispif, &ispif->fw_log_buf);
for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++)
isp4if_gpu_mem_free(ispif, &ispif->meta_info_buf[i]);
}
static int isp4if_alloc_fw_gpumem(struct isp4_interface *ispif)
{
struct device *dev = ispif->dev;
ispif->fw_mem_pool = isp4if_gpu_mem_alloc(ispif,
ISP4FW_MEMORY_POOL_SIZE);
if (!ispif->fw_mem_pool)
goto error_no_memory;
ispif->fw_cmd_resp_buf =
isp4if_gpu_mem_alloc(ispif, ISP4IF_RB_PMBMAP_MEM_SIZE);
if (!ispif->fw_cmd_resp_buf)
goto error_no_memory;
ispif->fw_log_buf =
isp4if_gpu_mem_alloc(ispif, ISP4IF_FW_LOG_RINGBUF_SIZE);
if (!ispif->fw_log_buf)
goto error_no_memory;
for (unsigned int i = 0; i < ISP4IF_MAX_STREAM_BUF_COUNT; i++) {
ispif->meta_info_buf[i] =
isp4if_gpu_mem_alloc(ispif, ISP4IF_META_INFO_BUF_SIZE);
if (!ispif->meta_info_buf[i])
goto error_no_memory;
}
return 0;
error_no_memory:
dev_err(dev, "failed to allocate gpu memory\n");
return -ENOMEM;
}
static u32 isp4if_compute_check_sum(const void *buf, size_t buf_size)
{
const u8 *surplus_ptr;
const u32 *buffer;
u32 checksum = 0;
size_t i;
buffer = (const u32 *)buf;
for (i = 0; i < buf_size / sizeof(u32); i++)
checksum += buffer[i];
surplus_ptr = (const u8 *)&buffer[i];
/* add surplus data crc checksum */
for (i = 0; i < buf_size % sizeof(u32); i++)
checksum += surplus_ptr[i];
return checksum;
}
void isp4if_clear_cmdq(struct isp4_interface *ispif)
{
struct isp4if_cmd_element *buf_node, *tmp_node;
LIST_HEAD(free_list);
scoped_guard(spinlock, &ispif->cmdq_lock)
list_splice_init(&ispif->cmdq, &free_list);
list_for_each_entry_safe(buf_node, tmp_node, &free_list, list)
kfree(buf_node);
}
static bool isp4if_is_cmdq_rb_full(struct isp4_interface *ispif,
enum isp4if_stream_id stream)
{
struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream];
u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
u32 len = rb_config->val_size;
u32 rd_ptr, wr_ptr;
u32 bytes_free;
rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
/*
* Read and write pointers are equal, indicating the ring buffer
* is empty
*/
if (wr_ptr == rd_ptr)
return false;
if (wr_ptr > rd_ptr)
bytes_free = len - (wr_ptr - rd_ptr);
else
bytes_free = rd_ptr - wr_ptr;
/*
* Ignore one byte from the bytes free to prevent rd_ptr from equaling
* wr_ptr when the ring buffer is full, because rd_ptr == wr_ptr is
* supposed to indicate that the ring buffer is empty.
*/
return bytes_free <= sizeof(struct isp4fw_cmd);
}
struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif,
u32 seq_num, u32 cmd_id)
{
struct isp4if_cmd_element *ele;
guard(spinlock)(&ispif->cmdq_lock);
list_for_each_entry(ele, &ispif->cmdq, list) {
if (ele->seq_num == seq_num && ele->cmd_id == cmd_id) {
list_del(&ele->list);
return ele;
}
}
return NULL;
}
/* Must check that isp4if_is_cmdq_rb_full() == false before calling */
static int isp4if_insert_isp_fw_cmd(struct isp4_interface *ispif,
enum isp4if_stream_id stream,
const struct isp4fw_cmd *cmd)
{
struct isp4if_rb_config *rb_config = &isp4if_cmd_rb_config[stream];
u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
void *mem_sys = rb_config->base_sys_addr;
const u32 cmd_sz = sizeof(*cmd);
struct device *dev = ispif->dev;
u32 len = rb_config->val_size;
const void *src = cmd;
u32 rd_ptr, wr_ptr;
u32 bytes_to_end;
rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
if (rd_ptr >= len || wr_ptr >= len) {
dev_err(dev,
"rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, cmd_sz=%u\n",
stream, isp4dbg_get_if_stream_str(stream), rd_ptr,
wr_ptr, len, cmd_sz);
return -EINVAL;
}
bytes_to_end = len - wr_ptr;
if (bytes_to_end >= cmd_sz) {
/* FW cmd is just a straight copy to the write pointer */
memcpy(mem_sys + wr_ptr, src, cmd_sz);
isp4hw_wreg(ispif->mmio, wreg, (wr_ptr + cmd_sz) % len);
} else {
/*
* FW cmd is split because the ring buffer needs to wrap
* around
*/
memcpy(mem_sys + wr_ptr, src, bytes_to_end);
memcpy(mem_sys, src + bytes_to_end, cmd_sz - bytes_to_end);
isp4hw_wreg(ispif->mmio, wreg, cmd_sz - bytes_to_end);
}
return 0;
}
static inline enum isp4if_stream_id isp4if_get_fw_stream(u32 cmd_id)
{
return ISP4IF_STREAM_ID_1;
}
static int isp4if_send_fw_cmd(struct isp4_interface *ispif, u32 cmd_id,
const void *package,
u32 package_size, bool sync)
{
enum isp4if_stream_id stream = isp4if_get_fw_stream(cmd_id);
struct isp4if_cmd_element *ele = NULL;
struct device *dev = ispif->dev;
struct isp4fw_cmd cmd;
u32 seq_num;
int ret;
if (package_size > sizeof(cmd.cmd_param)) {
dev_err(dev, "fail pkgsize(%u) > %zu cmd:0x%x, stream %d\n",
package_size, sizeof(cmd.cmd_param), cmd_id, stream);
return -EINVAL;
}
/*
* The struct will be shared with ISP FW, use memset() to guarantee
* padding bits are zeroed, since this is not guaranteed on all
* compilers.
*/
memset(&cmd, 0, sizeof(cmd));
cmd.cmd_id = cmd_id;
switch (stream) {
case ISP4IF_STREAM_ID_GLOBAL:
cmd.cmd_stream_id = ISP4FW_STREAM_ID_INVALID;
break;
case ISP4IF_STREAM_ID_1:
cmd.cmd_stream_id = ISP4FW_STREAM_ID_1;
break;
default:
dev_err(dev, "fail bad stream id %d\n", stream);
return -EINVAL;
}
/* Allocate the sync command object early and outside of the lock */
if (sync) {
ele = kmalloc_obj(*ele, GFP_KERNEL);
if (!ele)
return -ENOMEM;
/* Get two references: one for the resp thread, one for us */
atomic_set(&ele->refcnt, 2);
init_completion(&ele->cmd_done);
}
if (package && package_size)
memcpy(cmd.cmd_param, package, package_size);
scoped_guard(mutex, &ispif->isp4if_mutex) {
ret = read_poll_timeout(isp4if_is_cmdq_rb_full, ret, !ret,
ISP4IF_RB_FULL_SLEEP_US,
ISP4IF_RB_FULL_TIMEOUT_US, false, ispif,
stream);
if (ret) {
struct isp4if_rb_config *rb_config =
&isp4if_resp_rb_config[stream];
u32 rd_ptr = isp4hw_rreg(ispif->mmio,
rb_config->reg_rptr);
u32 wr_ptr = isp4hw_rreg(ispif->mmio,
rb_config->reg_wptr);
dev_err(dev,
"failed to get free cmdq slot, stream %s(%d),rd %u, wr %u\n",
isp4dbg_get_if_stream_str(stream), stream,
rd_ptr, wr_ptr);
ret = -ETIMEDOUT;
goto free_ele;
}
seq_num = ispif->host2fw_seq_num++;
cmd.cmd_seq_num = seq_num;
cmd.cmd_check_sum = isp4if_compute_check_sum(&cmd, sizeof(cmd)
- sizeof(u32));
/*
* only append the fw cmd to queue when its response needs to
* be waited for, currently there are only two such commands,
* disable channel and stop stream which are only sent after
* close camera
*/
if (ele) {
ele->seq_num = seq_num;
ele->cmd_id = cmd_id;
scoped_guard(spinlock, &ispif->cmdq_lock)
list_add_tail(&ele->list, &ispif->cmdq);
}
ret = isp4if_insert_isp_fw_cmd(ispif, stream, &cmd);
if (ret) {
dev_err(dev,
"fail for insert_isp_fw_cmd cmd_id %s(0x%08x)\n",
isp4dbg_get_cmd_str(cmd_id), cmd_id);
goto err_dequeue_ele;
}
}
if (ele) {
ret = wait_for_completion_timeout(&ele->cmd_done,
ISP4IF_FW_CMD_TIMEOUT);
if (!ret) {
ret = -ETIMEDOUT;
goto err_dequeue_ele;
}
ret = 0;
goto put_ele_ref;
}
return 0;
err_dequeue_ele:
/*
* Try to remove the command from the queue. If that fails, then it
* means the response thread is currently using the object, and we need
* to use the refcount to avoid a use-after-free by either side.
*/
if (ele && isp4if_rm_cmd_from_cmdq(ispif, seq_num, cmd_id))
goto free_ele;
put_ele_ref:
/* Don't free the command if we didn't put the last reference */
if (ele && atomic_dec_return(&ele->refcnt))
ele = NULL;
free_ele:
kfree(ele);
return ret;
}
static int isp4if_send_buffer(struct isp4_interface *ispif,
struct isp4if_img_buf_info *buf_info)
{
struct isp4fw_cmd_send_buffer cmd;
/*
* The struct will be shared with ISP FW, use memset() to guarantee
* padding bits are zeroed, since this is not guaranteed on all
* compilers.
*/
memset(&cmd, 0, sizeof(cmd));
cmd.buffer_type = ISP4FW_BUFFER_TYPE_PREVIEW;
cmd.buffer.vmid_space.bit.space = ISP4FW_ADDR_SPACE_TYPE_GPU_VA;
isp4if_split_addr64(buf_info->planes[0].mc_addr,
&cmd.buffer.buf_base_a_lo,
&cmd.buffer.buf_base_a_hi);
cmd.buffer.buf_size_a = buf_info->planes[0].len;
isp4if_split_addr64(buf_info->planes[1].mc_addr,
&cmd.buffer.buf_base_b_lo,
&cmd.buffer.buf_base_b_hi);
cmd.buffer.buf_size_b = buf_info->planes[1].len;
isp4if_split_addr64(buf_info->planes[2].mc_addr,
&cmd.buffer.buf_base_c_lo,
&cmd.buffer.buf_base_c_hi);
cmd.buffer.buf_size_c = buf_info->planes[2].len;
return isp4if_send_fw_cmd(ispif, ISP4FW_CMD_ID_SEND_BUFFER, &cmd,
sizeof(cmd), false);
}
static void isp4if_init_rb_config(struct isp4_interface *ispif,
struct isp4if_rb_config *rb_config)
{
isp4hw_wreg(ispif->mmio, rb_config->reg_rptr, 0x0);
isp4hw_wreg(ispif->mmio, rb_config->reg_wptr, 0x0);
isp4hw_wreg(ispif->mmio, rb_config->reg_base_lo,
rb_config->base_mc_addr);
isp4hw_wreg(ispif->mmio, rb_config->reg_base_hi,
rb_config->base_mc_addr >> 32);
isp4hw_wreg(ispif->mmio, rb_config->reg_size, rb_config->val_size);
}
static int isp4if_fw_init(struct isp4_interface *ispif)
{
u32 aligned_rb_chunk_size = ISP4IF_RB_PMBMAP_MEM_CHUNK & 0xffffffc0;
struct isp4if_rb_config *rb_config;
u32 offset;
unsigned int i;
/* initialize CMD_RB streams */
for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) {
rb_config = (isp4if_cmd_rb_config + i);
offset = aligned_rb_chunk_size * rb_config->index;
rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE;
rb_config->base_sys_addr =
ispif->fw_cmd_resp_buf->sys_addr + offset;
rb_config->base_mc_addr =
ispif->fw_cmd_resp_buf->gpu_mc_addr + offset;
isp4if_init_rb_config(ispif, rb_config);
}
/* initialize RESP_RB streams */
for (i = 0; i < ISP4IF_STREAM_ID_MAX; i++) {
rb_config = (isp4if_resp_rb_config + i);
offset = aligned_rb_chunk_size *
(rb_config->index + ISP4IF_RESP_CHAN_TO_RB_OFFSET - 1);
rb_config->val_size = ISP4IF_FW_CMD_BUF_SIZE;
rb_config->base_sys_addr =
ispif->fw_cmd_resp_buf->sys_addr + offset;
rb_config->base_mc_addr =
ispif->fw_cmd_resp_buf->gpu_mc_addr + offset;
isp4if_init_rb_config(ispif, rb_config);
}
/* initialize LOG_RB stream */
rb_config = &isp4if_log_rb_config;
rb_config->val_size = ISP4IF_FW_LOG_RINGBUF_SIZE;
rb_config->base_mc_addr = ispif->fw_log_buf->gpu_mc_addr;
rb_config->base_sys_addr = ispif->fw_log_buf->sys_addr;
isp4if_init_rb_config(ispif, rb_config);
return 0;
}
static int isp4if_wait_fw_ready(struct isp4_interface *ispif,
u32 isp_status_addr)
{
struct device *dev = ispif->dev;
u32 timeout_ms = 100;
u32 interval_ms = 1;
u32 reg_val;
/* wait for FW initialize done! */
if (!read_poll_timeout(isp4hw_rreg, reg_val, reg_val
& ISP_STATUS__CCPU_REPORT_MASK,
interval_ms * 1000, timeout_ms * 1000, false,
ispif->mmio, isp_status_addr))
return 0;
dev_err(dev, "ISP CCPU FW boot failed\n");
return -ETIME;
}
static void isp4if_enable_ccpu(struct isp4_interface *ispif)
{
u32 reg_val;
reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET);
reg_val &= (~ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK);
isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val);
usleep_range(100, 150);
reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL);
reg_val &= (~ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK);
isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val);
}
static void isp4if_disable_ccpu(struct isp4_interface *ispif)
{
u32 reg_val;
reg_val = isp4hw_rreg(ispif->mmio, ISP_CCPU_CNTL);
reg_val |= ISP_CCPU_CNTL__CCPU_HOST_SOFT_RST_MASK;
isp4hw_wreg(ispif->mmio, ISP_CCPU_CNTL, reg_val);
usleep_range(100, 150);
reg_val = isp4hw_rreg(ispif->mmio, ISP_SOFT_RESET);
reg_val |= ISP_SOFT_RESET__CCPU_SOFT_RESET_MASK;
isp4hw_wreg(ispif->mmio, ISP_SOFT_RESET, reg_val);
}
static int isp4if_fw_boot(struct isp4_interface *ispif)
{
struct device *dev = ispif->dev;
if (ispif->status != ISP4IF_STATUS_PWR_ON) {
dev_err(dev, "invalid isp power status %d\n", ispif->status);
return -EINVAL;
}
isp4if_disable_ccpu(ispif);
isp4if_fw_init(ispif);
/* clear ccpu status */
isp4hw_wreg(ispif->mmio, ISP_STATUS, 0x0);
isp4if_enable_ccpu(ispif);
if (isp4if_wait_fw_ready(ispif, ISP_STATUS)) {
isp4if_disable_ccpu(ispif);
return -EINVAL;
}
/* enable interrupts */
isp4hw_wreg(ispif->mmio, ISP_SYS_INT0_EN,
ISP4IF_FW_RESP_RB_IRQ_EN_MASK);
ispif->status = ISP4IF_STATUS_FW_RUNNING;
dev_dbg(dev, "ISP CCPU FW boot success\n");
return 0;
}
int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream,
struct isp4fw_resp *resp)
{
struct isp4if_rb_config *rb_config = &isp4if_resp_rb_config[stream];
u32 rreg = rb_config->reg_rptr, wreg = rb_config->reg_wptr;
void *mem_sys = rb_config->base_sys_addr;
const u32 resp_sz = sizeof(*resp);
struct device *dev = ispif->dev;
u32 len = rb_config->val_size;
u32 rd_ptr, wr_ptr;
u32 bytes_to_end;
void *dst = resp;
u32 checksum;
rd_ptr = isp4hw_rreg(ispif->mmio, rreg);
wr_ptr = isp4hw_rreg(ispif->mmio, wreg);
if (rd_ptr >= len || wr_ptr >= len)
goto err_rb_invalid;
/*
* Read and write pointers are equal, indicating the ring buffer is
* empty
*/
if (rd_ptr == wr_ptr)
return -ENODATA;
bytes_to_end = len - rd_ptr;
if (bytes_to_end >= resp_sz) {
/* FW response is just a straight copy from the read pointer */
if (wr_ptr > rd_ptr && wr_ptr - rd_ptr < resp_sz)
goto err_rb_invalid;
memcpy(dst, mem_sys + rd_ptr, resp_sz);
isp4hw_wreg(ispif->mmio, rreg, (rd_ptr + resp_sz) % len);
} else {
/*
* FW response is split because the ring buffer wrapped
* around
*/
if (wr_ptr > rd_ptr || wr_ptr < resp_sz - bytes_to_end)
goto err_rb_invalid;
memcpy(dst, mem_sys + rd_ptr, bytes_to_end);
memcpy(dst + bytes_to_end, mem_sys, resp_sz - bytes_to_end);
isp4hw_wreg(ispif->mmio, rreg, resp_sz - bytes_to_end);
}
checksum = isp4if_compute_check_sum(resp, resp_sz - sizeof(u32));
if (checksum != resp->resp_check_sum) {
dev_err(dev, "resp checksum 0x%x,should 0x%x,rptr %u,wptr %u\n",
checksum, resp->resp_check_sum, rd_ptr, wr_ptr);
dev_err(dev, "%s(%u), seqNo %u, resp_id %s(0x%x)\n",
isp4dbg_get_if_stream_str(stream), stream,
resp->resp_seq_num, isp4dbg_get_resp_str(resp->resp_id),
resp->resp_id);
return -EINVAL;
}
return 0;
err_rb_invalid:
dev_err(dev,
"rb invalid: stream=%u(%s), rd=%u, wr=%u, len=%u, resp_sz=%u\n",
stream, isp4dbg_get_if_stream_str(stream), rd_ptr, wr_ptr, len,
resp_sz);
return -EINVAL;
}
int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id,
const void *package, u32 package_size)
{
return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, false);
}
int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id,
const void *package, u32 package_size)
{
return isp4if_send_fw_cmd(ispif, cmd_id, package, package_size, true);
}
void isp4if_clear_bufq(struct isp4_interface *ispif)
{
struct isp4if_img_buf_node *buf_node, *tmp_node;
LIST_HEAD(free_list);
scoped_guard(spinlock, &ispif->bufq_lock)
list_splice_init(&ispif->bufq, &free_list);
list_for_each_entry_safe(buf_node, tmp_node, &free_list, node)
kfree(buf_node);
}
void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node)
{
kfree(buf_node);
}
struct isp4if_img_buf_node *
isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info)
{
struct isp4if_img_buf_node *node;
node = kmalloc_obj(*node, GFP_KERNEL);
if (node)
node->buf_info = *buf_info;
return node;
}
struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif)
{
struct isp4if_img_buf_node *buf_node;
guard(spinlock)(&ispif->bufq_lock);
buf_node = list_first_entry_or_null(&ispif->bufq, typeof(*buf_node),
node);
if (buf_node)
list_del(&buf_node->node);
return buf_node;
}
int isp4if_queue_buffer(struct isp4_interface *ispif,
struct isp4if_img_buf_node *buf_node)
{
int ret;
ret = isp4if_send_buffer(ispif, &buf_node->buf_info);
if (ret)
return ret;
scoped_guard(spinlock, &ispif->bufq_lock)
list_add_tail(&buf_node->node, &ispif->bufq);
return 0;
}
int isp4if_stop(struct isp4_interface *ispif)
{
isp4if_disable_ccpu(ispif);
isp4if_dealloc_fw_gpumem(ispif);
return 0;
}
int isp4if_start(struct isp4_interface *ispif)
{
int ret;
ret = isp4if_alloc_fw_gpumem(ispif);
if (ret)
return ret;
ret = isp4if_fw_boot(ispif);
if (ret)
goto failed_fw_boot;
return 0;
failed_fw_boot:
isp4if_dealloc_fw_gpumem(ispif);
return ret;
}
int isp4if_deinit(struct isp4_interface *ispif)
{
isp4if_clear_cmdq(ispif);
isp4if_clear_bufq(ispif);
mutex_destroy(&ispif->isp4if_mutex);
return 0;
}
int isp4if_init(struct isp4_interface *ispif, struct device *dev,
void __iomem *isp_mmio)
{
ispif->dev = dev;
ispif->mmio = isp_mmio;
spin_lock_init(&ispif->cmdq_lock); /* used for cmdq access */
spin_lock_init(&ispif->bufq_lock); /* used for bufq access */
mutex_init(&ispif->isp4if_mutex); /* used for commands sent to ispfw */
INIT_LIST_HEAD(&ispif->cmdq);
INIT_LIST_HEAD(&ispif->bufq);
return 0;
}
@@ -0,0 +1,144 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_INTERFACE_H_
#define _ISP4_INTERFACE_H_
#include <drm/amd/isp.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
struct isp4fw_resp;
#define ISP4IF_RB_MAX 25
#define ISP4IF_RESP_CHAN_TO_RB_OFFSET 9
#define ISP4IF_RB_PMBMAP_MEM_SIZE (SZ_16M - 1)
#define ISP4IF_RB_PMBMAP_MEM_CHUNK \
(ISP4IF_RB_PMBMAP_MEM_SIZE / (ISP4IF_RB_MAX - 1))
#define ISP4IF_HOST2FW_COMMAND_SIZE sizeof(struct isp4fw_cmd)
#define ISP4IF_MAX_NUM_HOST2FW_COMMAND 40
#define ISP4IF_FW_CMD_BUF_SIZE \
(ISP4IF_MAX_NUM_HOST2FW_COMMAND * ISP4IF_HOST2FW_COMMAND_SIZE)
#define ISP4IF_RB_FULL_SLEEP_US (33 * USEC_PER_MSEC)
#define ISP4IF_RB_FULL_TIMEOUT_US (10 * ISP4IF_RB_FULL_SLEEP_US)
#define ISP4IF_META_INFO_BUF_SIZE ALIGN(sizeof(struct isp4fw_meta_info), 0x8000)
#define ISP4IF_MAX_STREAM_BUF_COUNT 8
#define ISP4IF_FW_LOG_RINGBUF_SIZE SZ_2M
enum isp4if_stream_id {
ISP4IF_STREAM_ID_GLOBAL = 0,
ISP4IF_STREAM_ID_1 = 1,
ISP4IF_STREAM_ID_MAX = 4
};
enum isp4if_status {
ISP4IF_STATUS_PWR_OFF,
ISP4IF_STATUS_PWR_ON,
ISP4IF_STATUS_FW_RUNNING,
ISP4IF_FSM_STATUS_MAX
};
struct isp4if_gpu_mem_info {
u64 mem_size;
u64 gpu_mc_addr;
void *sys_addr;
void *mem_handle;
};
struct isp4if_img_buf_info {
struct {
void *sys_addr;
u64 mc_addr;
u32 len;
} planes[3];
};
struct isp4if_img_buf_node {
struct list_head node;
struct isp4if_img_buf_info buf_info;
};
struct isp4if_cmd_element {
struct list_head list;
u32 seq_num;
u32 cmd_id;
struct completion cmd_done;
atomic_t refcnt;
};
struct isp4_interface {
struct device *dev;
void __iomem *mmio;
spinlock_t cmdq_lock; /* used for cmdq access */
spinlock_t bufq_lock; /* used for bufq access */
struct mutex isp4if_mutex; /* used to send fw cmd and read fw log */
struct list_head cmdq; /* commands sent to fw */
struct list_head bufq; /* buffers sent to fw */
enum isp4if_status status;
u32 host2fw_seq_num;
/* ISP fw buffers */
struct isp4if_gpu_mem_info *fw_log_buf;
struct isp4if_gpu_mem_info *fw_cmd_resp_buf;
struct isp4if_gpu_mem_info *fw_mem_pool;
struct isp4if_gpu_mem_info *meta_info_buf[ISP4IF_MAX_STREAM_BUF_COUNT];
};
static inline void isp4if_split_addr64(u64 addr, u32 *lo, u32 *hi)
{
if (lo)
*lo = addr & 0xffffffff;
if (hi)
*hi = addr >> 32;
}
static inline u64 isp4if_join_addr64(u32 lo, u32 hi)
{
return (((u64)hi) << 32) | (u64)lo;
}
int isp4if_f2h_resp(struct isp4_interface *ispif, enum isp4if_stream_id stream,
struct isp4fw_resp *resp);
int isp4if_send_command(struct isp4_interface *ispif, u32 cmd_id,
const void *package, u32 package_size);
int isp4if_send_command_sync(struct isp4_interface *ispif, u32 cmd_id,
const void *package, u32 package_size);
struct isp4if_cmd_element *isp4if_rm_cmd_from_cmdq(struct isp4_interface *ispif,
u32 seq_num, u32 cmd_id);
void isp4if_clear_cmdq(struct isp4_interface *ispif);
void isp4if_clear_bufq(struct isp4_interface *ispif);
void isp4if_dealloc_buffer_node(struct isp4if_img_buf_node *buf_node);
struct isp4if_img_buf_node *
isp4if_alloc_buffer_node(struct isp4if_img_buf_info *buf_info);
struct isp4if_img_buf_node *isp4if_dequeue_buffer(struct isp4_interface *ispif);
int isp4if_queue_buffer(struct isp4_interface *ispif,
struct isp4if_img_buf_node *buf_node);
int isp4if_stop(struct isp4_interface *ispif);
int isp4if_start(struct isp4_interface *ispif);
int isp4if_deinit(struct isp4_interface *ispif);
int isp4if_init(struct isp4_interface *ispif, struct device *dev,
void __iomem *isp_mmio);
#endif /* _ISP4_INTERFACE_H_ */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_SUBDEV_H_
#define _ISP4_SUBDEV_H_
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <media/v4l2-device.h>
#include "isp4_fw_cmd_resp.h"
#include "isp4_hw_reg.h"
#include "isp4_interface.h"
#include "isp4_video.h"
/*
* One is for none sensor specific response which is not used now.
* Another is for sensor specific response
*/
#define ISP4SD_MAX_FW_RESP_STREAM_NUM 2
/* Indicates the ISP status */
enum isp4sd_status {
ISP4SD_STATUS_PWR_OFF,
ISP4SD_STATUS_PWR_ON,
ISP4SD_STATUS_FW_RUNNING,
ISP4SD_STATUS_MAX
};
/* Indicates sensor and output stream status */
enum isp4sd_start_status {
ISP4SD_START_STATUS_OFF,
ISP4SD_START_STATUS_STARTED,
ISP4SD_START_STATUS_START_FAIL,
};
struct isp4sd_img_buf_node {
struct list_head node;
struct isp4if_img_buf_info buf_info;
};
/* This is ISP output after processing Bayer raw sensor input */
struct isp4sd_output_info {
enum isp4sd_start_status start_status;
u32 image_size;
};
/*
* Struct for sensor info used as ISP input or source.
* status: sensor status.
* output_info: ISP output after processing the sensor input.
* start_stream_cmd_sent: indicates if ISP4FW_CMD_ID_START_STREAM was sent
* to firmware.
* buf_sent_cnt: number of buffers sent to receive images.
*/
struct isp4sd_sensor_info {
struct isp4sd_output_info output_info;
enum isp4sd_start_status status;
bool start_stream_cmd_sent;
u32 buf_sent_cnt;
};
/*
* The thread is created by the driver to handle firmware responses which will
* be waken up when a firmware-to-driver response interrupt occurs.
*/
struct isp4sd_thread_handler {
struct task_struct *thread;
wait_queue_head_t waitq;
bool resp_ready;
};
struct isp4_subdev_thread_param {
u32 idx;
struct isp4_subdev *isp_subdev;
};
struct isp4_subdev {
struct v4l2_subdev sdev;
struct isp4_interface ispif;
struct isp4vid_dev isp_vdev;
struct media_pad sdev_pad;
enum isp4sd_status isp_status;
/* mutex used to synchronize the operation with firmware */
struct mutex ops_mutex;
struct isp4sd_thread_handler
fw_resp_thread[ISP4SD_MAX_FW_RESP_STREAM_NUM];
u32 host2fw_seq_num;
struct isp4sd_sensor_info sensor_info;
/* gpio descriptor */
struct gpio_desc *enable_gpio;
struct device *dev;
void __iomem *mmio;
struct isp4_subdev_thread_param
isp_resp_para[ISP4SD_MAX_FW_RESP_STREAM_NUM];
int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM];
bool irq_enabled;
/* spin lock to access ISP_SYS_INT0_EN exclusively */
spinlock_t irq_lock;
#ifdef CONFIG_DEBUG_FS
bool enable_fw_log;
struct dentry *debugfs_dir;
char *fw_log_output;
#endif
};
int isp4sd_init(struct isp4_subdev *isp_subdev, struct v4l2_device *v4l2_dev,
int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]);
void isp4sd_deinit(struct isp4_subdev *isp_subdev);
int isp4sd_ioc_send_img_buf(struct v4l2_subdev *sd,
struct isp4if_img_buf_info *buf_info);
int isp4sd_pwron_and_init(struct v4l2_subdev *sd);
int isp4sd_pwroff_and_deinit(struct v4l2_subdev *sd);
#endif /* _ISP4_SUBDEV_H_ */
@@ -0,0 +1,797 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mc.h>
#include <media/videobuf2-vmalloc.h>
#include "isp4_interface.h"
#include "isp4_subdev.h"
#include "isp4_video.h"
#define ISP4VID_ISP_DRV_NAME "amd_isp_capture"
#define ISP4VID_MAX_PREVIEW_FPS 30
#define ISP4VID_DEFAULT_FMT V4L2_PIX_FMT_NV12
#define ISP4VID_PAD_VIDEO_OUTPUT 0
/* time perframe default */
#define ISP4VID_ISP_TPF_DEFAULT isp4vid_tpfs[0]
static const char *const isp4vid_video_dev_name = "Preview";
/* Sizes must be in increasing order */
static const struct v4l2_frmsize_discrete isp4vid_frmsize[] = {
{640, 360},
{640, 480},
{1280, 720},
{1280, 960},
{1920, 1080},
{1920, 1440},
{2560, 1440},
{2880, 1620},
{2880, 1624},
{2888, 1808},
};
static const u32 isp4vid_formats[] = {
V4L2_PIX_FMT_NV12,
V4L2_PIX_FMT_YUYV
};
/* time perframe list */
static const struct v4l2_fract isp4vid_tpfs[] = {
{ 1, ISP4VID_MAX_PREVIEW_FPS }
};
void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev,
const struct isp4if_img_buf_info *img_buf)
{
struct isp4vid_capture_buffer *isp4vid_buf;
void *vbuf;
scoped_guard(mutex, &isp_vdev->buf_list_lock) {
isp4vid_buf = list_first_entry_or_null(&isp_vdev->buf_list,
typeof(*isp4vid_buf),
list);
if (!isp4vid_buf)
return;
vbuf = vb2_plane_vaddr(&isp4vid_buf->vb2.vb2_buf, 0);
if (vbuf != img_buf->planes[0].sys_addr) {
dev_err(isp_vdev->dev, "Invalid vbuf\n");
return;
}
list_del(&isp4vid_buf->list);
}
/* Fill the buffer */
isp4vid_buf->vb2.vb2_buf.timestamp = ktime_get_ns();
isp4vid_buf->vb2.sequence = isp_vdev->sequence++;
isp4vid_buf->vb2.field = V4L2_FIELD_ANY;
vb2_set_plane_payload(&isp4vid_buf->vb2.vb2_buf,
0, isp_vdev->format.sizeimage);
vb2_buffer_done(&isp4vid_buf->vb2.vb2_buf, VB2_BUF_STATE_DONE);
dev_dbg(isp_vdev->dev, "call vb2_buffer_done(size=%u)\n",
isp_vdev->format.sizeimage);
}
static const struct v4l2_pix_format isp4vid_fmt_default = {
.width = 1920,
.height = 1080,
.pixelformat = ISP4VID_DEFAULT_FMT,
.field = V4L2_FIELD_NONE,
.colorspace = V4L2_COLORSPACE_SRGB,
};
static void isp4vid_capture_return_all_buffers(struct isp4vid_dev *isp_vdev,
enum vb2_buffer_state state)
{
struct isp4vid_capture_buffer *vbuf, *node;
scoped_guard(mutex, &isp_vdev->buf_list_lock) {
list_for_each_entry_safe(vbuf, node, &isp_vdev->buf_list, list)
vb2_buffer_done(&vbuf->vb2.vb2_buf, state);
INIT_LIST_HEAD(&isp_vdev->buf_list);
}
dev_dbg(isp_vdev->dev, "call vb2_buffer_done(%d)\n", state);
}
static int isp4vid_vdev_link_validate(struct media_link *link)
{
return 0;
}
static const struct media_entity_operations isp4vid_vdev_ent_ops = {
.link_validate = isp4vid_vdev_link_validate,
};
static const struct v4l2_file_operations isp4vid_vdev_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open,
.release = vb2_fop_release,
.read = vb2_fop_read,
.poll = vb2_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = vb2_fop_mmap,
};
static int isp4vid_ioctl_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
strscpy(cap->driver, ISP4VID_ISP_DRV_NAME, sizeof(cap->driver));
snprintf(cap->card, sizeof(cap->card), "%s", ISP4VID_ISP_DRV_NAME);
cap->capabilities |= V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE;
dev_dbg(isp_vdev->dev, "%s|capabilities=0x%X\n", isp_vdev->vdev.name,
cap->capabilities);
return 0;
}
static int isp4vid_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
f->fmt.pix = isp_vdev->format;
return 0;
}
static int isp4vid_fill_buffer_size(struct v4l2_pix_format *fmt)
{
int ret = 0;
switch (fmt->pixelformat) {
case V4L2_PIX_FMT_NV12:
fmt->bytesperline = fmt->width;
fmt->sizeimage = fmt->bytesperline * fmt->height * 3 / 2;
break;
case V4L2_PIX_FMT_YUYV:
fmt->bytesperline = fmt->width * 2;
fmt->sizeimage = fmt->bytesperline * fmt->height;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
static int isp4vid_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
struct v4l2_pix_format *format = &f->fmt.pix;
const struct v4l2_frmsize_discrete *fsz;
size_t i;
/*
* Check if the hardware supports the requested format, use the default
* format otherwise.
*/
for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++)
if (isp4vid_formats[i] == format->pixelformat)
break;
if (i == ARRAY_SIZE(isp4vid_formats))
format->pixelformat = ISP4VID_DEFAULT_FMT;
switch (format->pixelformat) {
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_YUYV:
fsz = v4l2_find_nearest_size(isp4vid_frmsize,
ARRAY_SIZE(isp4vid_frmsize),
width, height, format->width,
format->height);
format->width = fsz->width;
format->height = fsz->height;
break;
default:
dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n",
isp_vdev->vdev.name,
format->pixelformat);
return -EINVAL;
}
/*
* There is no need to check the return value, as failure will never
* happen here
*/
isp4vid_fill_buffer_size(format);
if (format->field == V4L2_FIELD_ANY)
format->field = isp4vid_fmt_default.field;
if (format->colorspace == V4L2_COLORSPACE_DEFAULT)
format->colorspace = isp4vid_fmt_default.colorspace;
return 0;
}
static int isp4vid_set_fmt_2_isp(struct v4l2_subdev *sdev,
struct v4l2_pix_format *pix_fmt)
{
struct v4l2_subdev_format fmt = {};
switch (pix_fmt->pixelformat) {
case V4L2_PIX_FMT_NV12:
fmt.format.code = MEDIA_BUS_FMT_YUYV8_1_5X8;
break;
case V4L2_PIX_FMT_YUYV:
fmt.format.code = MEDIA_BUS_FMT_YUYV8_1X16;
break;
default:
return -EINVAL;
}
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt.pad = ISP4VID_PAD_VIDEO_OUTPUT;
fmt.format.width = pix_fmt->width;
fmt.format.height = pix_fmt->height;
return v4l2_subdev_call(sdev, pad, set_fmt, NULL, &fmt);
}
static int isp4vid_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
int ret;
/* Do not change the format while stream is on */
if (vb2_is_busy(&isp_vdev->vbq))
return -EBUSY;
ret = isp4vid_try_fmt_vid_cap(file, priv, f);
if (ret)
return ret;
dev_dbg(isp_vdev->dev, "%s|width height:%ux%u->%ux%u\n",
isp_vdev->vdev.name,
isp_vdev->format.width, isp_vdev->format.height,
f->fmt.pix.width, f->fmt.pix.height);
dev_dbg(isp_vdev->dev, "%s|pixelformat:0x%x-0x%x\n",
isp_vdev->vdev.name, isp_vdev->format.pixelformat,
f->fmt.pix.pixelformat);
dev_dbg(isp_vdev->dev, "%s|bytesperline:%u->%u\n",
isp_vdev->vdev.name, isp_vdev->format.bytesperline,
f->fmt.pix.bytesperline);
dev_dbg(isp_vdev->dev, "%s|sizeimage:%u->%u\n",
isp_vdev->vdev.name, isp_vdev->format.sizeimage,
f->fmt.pix.sizeimage);
isp_vdev->format = f->fmt.pix;
ret = isp4vid_set_fmt_2_isp(isp_vdev->isp_sdev, &isp_vdev->format);
return ret;
}
static int isp4vid_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
switch (f->index) {
case 0:
f->pixelformat = V4L2_PIX_FMT_NV12;
break;
case 1:
f->pixelformat = V4L2_PIX_FMT_YUYV;
break;
default:
return -EINVAL;
}
dev_dbg(isp_vdev->dev, "%s|index=%d, pixelformat=0x%X\n",
isp_vdev->vdev.name, f->index, f->pixelformat);
return 0;
}
static int isp4vid_enum_framesizes(struct file *file, void *fh,
struct v4l2_frmsizeenum *fsize)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
unsigned int i;
for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++) {
if (isp4vid_formats[i] == fsize->pixel_format)
break;
}
if (i == ARRAY_SIZE(isp4vid_formats))
return -EINVAL;
if (fsize->index < ARRAY_SIZE(isp4vid_frmsize)) {
fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
fsize->discrete = isp4vid_frmsize[fsize->index];
dev_dbg(isp_vdev->dev, "%s|size[%d]=%dx%d\n",
isp_vdev->vdev.name, fsize->index,
fsize->discrete.width, fsize->discrete.height);
} else {
return -EINVAL;
}
return 0;
}
static int isp4vid_ioctl_enum_frameintervals(struct file *file, void *priv,
struct v4l2_frmivalenum *fival)
{
struct isp4vid_dev *isp_vdev = video_drvdata(file);
size_t i;
if (fival->index >= ARRAY_SIZE(isp4vid_tpfs))
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(isp4vid_formats); i++)
if (isp4vid_formats[i] == fival->pixel_format)
break;
if (i == ARRAY_SIZE(isp4vid_formats))
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(isp4vid_frmsize); i++)
if (isp4vid_frmsize[i].width == fival->width &&
isp4vid_frmsize[i].height == fival->height)
break;
if (i == ARRAY_SIZE(isp4vid_frmsize))
return -EINVAL;
fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
fival->discrete = isp4vid_tpfs[fival->index];
v4l2_simplify_fraction(&fival->discrete.numerator,
&fival->discrete.denominator, 8, 333);
dev_dbg(isp_vdev->dev, "%s|interval[%d]=%d/%d\n",
isp_vdev->vdev.name, fival->index,
fival->discrete.numerator,
fival->discrete.denominator);
return 0;
}
static int isp4vid_ioctl_g_param(struct file *file, void *priv,
struct v4l2_streamparm *param)
{
struct v4l2_captureparm *capture = &param->parm.capture;
struct isp4vid_dev *isp_vdev = video_drvdata(file);
if (param->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
capture->capability = V4L2_CAP_TIMEPERFRAME;
capture->timeperframe = isp_vdev->timeperframe;
capture->readbuffers = 0;
dev_dbg(isp_vdev->dev, "%s|timeperframe=%d/%d\n", isp_vdev->vdev.name,
capture->timeperframe.numerator,
capture->timeperframe.denominator);
return 0;
}
static const struct v4l2_ioctl_ops isp4vid_vdev_ioctl_ops = {
.vidioc_querycap = isp4vid_ioctl_querycap,
.vidioc_enum_fmt_vid_cap = isp4vid_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = isp4vid_g_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = isp4vid_s_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = isp4vid_try_fmt_vid_cap,
.vidioc_reqbufs = vb2_ioctl_reqbufs,
.vidioc_querybuf = vb2_ioctl_querybuf,
.vidioc_qbuf = vb2_ioctl_qbuf,
.vidioc_expbuf = vb2_ioctl_expbuf,
.vidioc_dqbuf = vb2_ioctl_dqbuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
.vidioc_streamon = vb2_ioctl_streamon,
.vidioc_streamoff = vb2_ioctl_streamoff,
.vidioc_g_parm = isp4vid_ioctl_g_param,
.vidioc_s_parm = isp4vid_ioctl_g_param,
.vidioc_enum_framesizes = isp4vid_enum_framesizes,
.vidioc_enum_frameintervals = isp4vid_ioctl_enum_frameintervals,
};
static unsigned int isp4vid_get_image_size(struct v4l2_pix_format *fmt)
{
switch (fmt->pixelformat) {
case V4L2_PIX_FMT_NV12:
return fmt->width * fmt->height * 3 / 2;
case V4L2_PIX_FMT_YUYV:
return fmt->width * fmt->height * 2;
default:
return 0;
}
}
static int isp4vid_qops_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers,
unsigned int *nplanes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq);
unsigned int q_num_bufs = vb2_get_num_buffers(vq);
if (*nplanes > 1) {
dev_err(isp_vdev->dev,
"fail to setup queue, no mplane supported %u\n",
*nplanes);
return -EINVAL;
}
if (*nplanes == 1) {
unsigned int size;
size = isp4vid_get_image_size(&isp_vdev->format);
if (sizes[0] < size) {
dev_err(isp_vdev->dev,
"fail for small plane size %u, %u expected\n",
sizes[0], size);
return -EINVAL;
}
}
if (q_num_bufs + *nbuffers < ISP4IF_MAX_STREAM_BUF_COUNT)
*nbuffers = ISP4IF_MAX_STREAM_BUF_COUNT - q_num_bufs;
switch (isp_vdev->format.pixelformat) {
case V4L2_PIX_FMT_NV12:
case V4L2_PIX_FMT_YUYV: {
*nplanes = 1;
sizes[0] = max(sizes[0], isp_vdev->format.sizeimage);
isp_vdev->format.sizeimage = sizes[0];
}
break;
default:
dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n",
isp_vdev->vdev.name, isp_vdev->format.pixelformat);
return -EINVAL;
}
dev_dbg(isp_vdev->dev, "%s|*nbuffers=%u *nplanes=%u sizes[0]=%u\n",
isp_vdev->vdev.name,
*nbuffers, *nplanes, sizes[0]);
return 0;
}
static void isp4vid_qops_buffer_queue(struct vb2_buffer *vb)
{
struct isp4vid_capture_buffer *buf =
container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf);
struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue);
struct isp4if_img_buf_info *img_buf = &buf->img_buf;
void *vaddr = vb2_plane_vaddr(vb, 0);
dev_dbg(isp_vdev->dev, "queue buf, vaddr %p, gpuva 0x%llx, size %u\n",
vaddr, buf->gpu_addr, vb->planes[0].length);
switch (isp_vdev->format.pixelformat) {
case V4L2_PIX_FMT_NV12: {
u32 y_size = isp_vdev->format.sizeimage / 3 * 2;
u32 uv_size = isp_vdev->format.sizeimage / 3;
img_buf->planes[0].len = y_size;
img_buf->planes[0].sys_addr = vaddr;
img_buf->planes[0].mc_addr = buf->gpu_addr;
dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n",
img_buf->planes[0].mc_addr,
img_buf->planes[0].len);
img_buf->planes[1].len = uv_size;
img_buf->planes[1].sys_addr = vaddr + y_size;
img_buf->planes[1].mc_addr = buf->gpu_addr + y_size;
dev_dbg(isp_vdev->dev, "img_buf[1]: mc=0x%llx size=%u\n",
img_buf->planes[1].mc_addr,
img_buf->planes[1].len);
img_buf->planes[2].len = 0;
}
break;
case V4L2_PIX_FMT_YUYV: {
img_buf->planes[0].len = isp_vdev->format.sizeimage;
img_buf->planes[0].sys_addr = vaddr;
img_buf->planes[0].mc_addr = buf->gpu_addr;
dev_dbg(isp_vdev->dev, "img_buf[0]: mc=0x%llx size=%u\n",
img_buf->planes[0].mc_addr,
img_buf->planes[0].len);
img_buf->planes[1].len = 0;
img_buf->planes[2].len = 0;
}
break;
default:
dev_err(isp_vdev->dev, "%s|unsupported fmt=%u\n",
isp_vdev->vdev.name, isp_vdev->format.pixelformat);
return;
}
if (isp_vdev->stream_started)
isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev, img_buf);
scoped_guard(mutex, &isp_vdev->buf_list_lock)
list_add_tail(&buf->list, &isp_vdev->buf_list);
}
static int isp4vid_qops_start_streaming(struct vb2_queue *vq,
unsigned int count)
{
struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq);
struct isp4vid_capture_buffer *isp4vid_buf;
struct media_entity *entity;
struct v4l2_subdev *subdev;
struct media_pad *pad;
int ret = 0;
isp_vdev->sequence = 0;
ret = isp4sd_pwron_and_init(isp_vdev->isp_sdev);
if (ret) {
dev_err(isp_vdev->dev, "power up isp fail %d\n", ret);
goto release_buffers;
}
entity = &isp_vdev->vdev.entity;
while (1) {
pad = &entity->pads[0];
if (!(pad->flags & MEDIA_PAD_FL_SINK))
break;
pad = media_pad_remote_pad_first(pad);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
break;
entity = pad->entity;
subdev = media_entity_to_v4l2_subdev(entity);
ret = v4l2_subdev_call(subdev, video, s_stream, 1);
if (ret < 0 && ret != -ENOIOCTLCMD) {
dev_dbg(isp_vdev->dev, "fail start streaming: %s %d\n",
subdev->name, ret);
goto release_buffers;
}
}
list_for_each_entry(isp4vid_buf, &isp_vdev->buf_list, list)
isp4sd_ioc_send_img_buf(isp_vdev->isp_sdev,
&isp4vid_buf->img_buf);
isp_vdev->stream_started = true;
return 0;
release_buffers:
isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_QUEUED);
return ret;
}
static void isp4vid_qops_stop_streaming(struct vb2_queue *vq)
{
struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vq);
struct media_entity *entity;
struct v4l2_subdev *subdev;
struct media_pad *pad;
int ret;
entity = &isp_vdev->vdev.entity;
while (1) {
pad = &entity->pads[0];
if (!(pad->flags & MEDIA_PAD_FL_SINK))
break;
pad = media_pad_remote_pad_first(pad);
if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
break;
entity = pad->entity;
subdev = media_entity_to_v4l2_subdev(entity);
ret = v4l2_subdev_call(subdev, video, s_stream, 0);
if (ret < 0 && ret != -ENOIOCTLCMD)
dev_dbg(isp_vdev->dev, "fail stop streaming: %s %d\n",
subdev->name, ret);
}
isp_vdev->stream_started = false;
isp4sd_pwroff_and_deinit(isp_vdev->isp_sdev);
/* Release all active buffers */
isp4vid_capture_return_all_buffers(isp_vdev, VB2_BUF_STATE_ERROR);
}
static int isp4vid_qops_buf_init(struct vb2_buffer *vb)
{
struct isp4vid_capture_buffer *buf =
container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf);
struct isp4vid_dev *isp_vdev = vb2_get_drv_priv(vb->vb2_queue);
void *mem_priv = vb->planes[0].mem_priv;
struct device *dev = isp_vdev->dev;
u64 gpu_addr;
void *bo;
int ret;
if (vb->planes[0].dbuf) {
buf->dbuf = vb->planes[0].dbuf;
} else {
/*
* HAS_DMA is a Kconfig dependency so CONFIG_HAS_DMA is always
* defined when this driver is compiled. The #else branch is
* kept as a safeguard in case the dependency is ever removed.
*/
#ifdef CONFIG_HAS_DMA
buf->dbuf = vb2_vmalloc_memops.get_dmabuf(vb, mem_priv, 0);
if (IS_ERR_OR_NULL(buf->dbuf)) {
dev_err(dev, "fail to get dma buf\n");
return -EINVAL;
}
#else
dev_err(dev, "get dmabuf fail -- CONFIG_HAS_DMA not defined\n");
buf->dbuf = NULL;
return -EINVAL;
#endif
}
/* create isp user BO and obtain gpu_addr */
ret = isp_user_buffer_alloc(dev, buf->dbuf, &bo, &gpu_addr);
if (ret) {
dev_err(dev, "fail to create isp user BO\n");
if (!vb->planes[0].dbuf) {
dma_buf_put(buf->dbuf);
buf->dbuf = NULL;
}
return ret;
}
buf->bo = bo;
buf->gpu_addr = gpu_addr;
return 0;
}
static void isp4vid_qops_buf_cleanup(struct vb2_buffer *vb)
{
struct isp4vid_capture_buffer *buf =
container_of(vb, struct isp4vid_capture_buffer, vb2.vb2_buf);
if (buf->bo) {
isp_user_buffer_free(buf->bo);
buf->bo = NULL;
}
/*
* Only put dmabufs we obtained ourselves via get_dmabuf, not ones
* provided by the framework for DMABUF import
*/
if (buf->dbuf && buf->dbuf != vb->planes[0].dbuf)
dma_buf_put(buf->dbuf);
buf->dbuf = NULL;
}
static const struct vb2_ops isp4vid_qops = {
.queue_setup = isp4vid_qops_queue_setup,
.buf_init = isp4vid_qops_buf_init,
.buf_cleanup = isp4vid_qops_buf_cleanup,
.start_streaming = isp4vid_qops_start_streaming,
.stop_streaming = isp4vid_qops_stop_streaming,
.buf_queue = isp4vid_qops_buffer_queue,
};
int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd)
{
const char *vdev_name = isp4vid_video_dev_name;
struct v4l2_device *v4l2_dev;
struct video_device *vdev;
struct vb2_queue *q;
int ret;
if (!isp_vdev || !isp_sd || !isp_sd->v4l2_dev)
return -EINVAL;
v4l2_dev = isp_sd->v4l2_dev;
vdev = &isp_vdev->vdev;
isp_vdev->isp_sdev = isp_sd;
isp_vdev->dev = v4l2_dev->dev;
/* Initialize the vb2_queue struct */
mutex_init(&isp_vdev->vbq_lock);
q = &isp_vdev->vbq;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
q->io_modes = VB2_MMAP | VB2_DMABUF;
q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->buf_struct_size = sizeof(struct isp4vid_capture_buffer);
q->min_queued_buffers = 2;
q->ops = &isp4vid_qops;
q->drv_priv = isp_vdev;
q->mem_ops = &vb2_vmalloc_memops;
q->lock = &isp_vdev->vbq_lock;
q->dev = v4l2_dev->dev;
ret = vb2_queue_init(q);
if (ret) {
dev_err(v4l2_dev->dev, "vb2_queue_init error:%d\n", ret);
return ret;
}
/* Initialize buffer list and its lock */
mutex_init(&isp_vdev->buf_list_lock);
INIT_LIST_HEAD(&isp_vdev->buf_list);
/* Set default frame format */
isp_vdev->format = isp4vid_fmt_default;
isp_vdev->timeperframe = ISP4VID_ISP_TPF_DEFAULT;
v4l2_simplify_fraction(&isp_vdev->timeperframe.numerator,
&isp_vdev->timeperframe.denominator, 8, 333);
ret = isp4vid_fill_buffer_size(&isp_vdev->format);
if (ret) {
dev_err(v4l2_dev->dev, "fail to fill buffer size: %d\n", ret);
goto err_release_vb2_queue;
}
ret = isp4vid_set_fmt_2_isp(isp_sd, &isp_vdev->format);
if (ret) {
dev_err(v4l2_dev->dev, "fail init format :%d\n", ret);
goto err_release_vb2_queue;
}
/* Initialize the video_device struct */
isp_vdev->vdev.entity.name = vdev_name;
isp_vdev->vdev.entity.function = MEDIA_ENT_F_IO_V4L;
isp_vdev->vdev_pad.flags = MEDIA_PAD_FL_SINK;
ret = media_entity_pads_init(&isp_vdev->vdev.entity, 1,
&isp_vdev->vdev_pad);
if (ret) {
dev_err(v4l2_dev->dev, "init media entity pad fail:%d\n", ret);
goto err_release_vb2_queue;
}
vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE |
V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
vdev->entity.ops = &isp4vid_vdev_ent_ops;
vdev->release = video_device_release_empty;
vdev->fops = &isp4vid_vdev_fops;
vdev->ioctl_ops = &isp4vid_vdev_ioctl_ops;
vdev->lock = NULL;
vdev->queue = q;
vdev->v4l2_dev = v4l2_dev;
vdev->vfl_dir = VFL_DIR_RX;
strscpy(vdev->name, vdev_name, sizeof(vdev->name));
video_set_drvdata(vdev, isp_vdev);
ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (ret) {
dev_err(v4l2_dev->dev, "register video device fail:%d\n", ret);
goto err_entity_cleanup;
}
return 0;
err_entity_cleanup:
media_entity_cleanup(&isp_vdev->vdev.entity);
err_release_vb2_queue:
vb2_queue_release(q);
return ret;
}
void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev)
{
vb2_video_unregister_device(&isp_vdev->vdev);
}
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* Copyright (C) 2025 Advanced Micro Devices, Inc.
*/
#ifndef _ISP4_VIDEO_H_
#define _ISP4_VIDEO_H_
#include <media/v4l2-dev.h>
#include <media/videobuf2-memops.h>
#include "isp4_interface.h"
struct isp4vid_capture_buffer {
/*
* struct vb2_v4l2_buffer must be the first element
* the videobuf2 framework will allocate this struct based on
* buf_struct_size and use the first sizeof(struct vb2_buffer) bytes of
* memory as a vb2_buffer
*/
struct vb2_v4l2_buffer vb2;
struct isp4if_img_buf_info img_buf;
struct list_head list;
struct dma_buf *dbuf;
void *bo;
u64 gpu_addr;
};
struct isp4vid_dev {
struct video_device vdev;
struct media_pad vdev_pad;
struct v4l2_pix_format format;
/* mutex that protects vbq */
struct mutex vbq_lock;
struct vb2_queue vbq;
/* mutex that protects buf_list */
struct mutex buf_list_lock;
struct list_head buf_list;
u32 sequence;
bool stream_started;
struct device *dev;
struct v4l2_subdev *isp_sdev;
struct v4l2_fract timeperframe;
};
int isp4vid_dev_init(struct isp4vid_dev *isp_vdev, struct v4l2_subdev *isp_sd);
void isp4vid_dev_deinit(struct isp4vid_dev *isp_vdev);
void isp4vid_handle_frame_done(struct isp4vid_dev *isp_vdev,
const struct isp4if_img_buf_info *img_buf);
#endif /* _ISP4_VIDEO_H_ */