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. Add interrupt handling and notification for
isp firmware to isp-subdevice.

Co-developed-by: Sultan Alsawaf <sultan@kerneltoast.com>
Signed-off-by: Sultan Alsawaf <sultan@kerneltoast.com>
Co-developed-by: Svetoslav Stoilov <Svetoslav.Stoilov@amd.com>
Signed-off-by: Svetoslav Stoilov <Svetoslav.Stoilov@amd.com>
Signed-off-by: Bin Du <Bin.Du@amd.com>
Reviewed-by: Sultan Alsawaf <sultan@kerneltoast.com>
Tested-by: Alexey Zagorodnikov <xglooom@gmail.com>
Tested-by: Kate Hsuan <hpa@redhat.com>
Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
This commit is contained in:
Bin Du 2026-05-06 17:32:46 +08:00 committed by Sakari Ailus
parent 4c5feef6a6
commit 4e5e7a7ddb
6 changed files with 1259 additions and 6 deletions

View File

@ -1168,6 +1168,8 @@ F: drivers/media/platform/amd/isp4/isp4_fw_cmd_resp.h
F: drivers/media/platform/amd/isp4/isp4_hw_reg.h
F: drivers/media/platform/amd/isp4/isp4_interface.c
F: drivers/media/platform/amd/isp4/isp4_interface.h
F: drivers/media/platform/amd/isp4/isp4_subdev.c
F: drivers/media/platform/amd/isp4/isp4_subdev.h
AMD KFD
M: Felix Kuehling <Felix.Kuehling@amd.com>

View File

@ -4,4 +4,5 @@
obj-$(CONFIG_VIDEO_AMD_ISP4_CAPTURE) += amd_isp4_capture.o
amd_isp4_capture-objs := isp4.o \
isp4_interface.o
isp4_interface.o \
isp4_subdev.o

View File

@ -3,13 +3,18 @@
* 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_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;
@ -17,27 +22,103 @@ static const struct {
u32 en_mask;
u32 ack_mask;
u32 rb_int_num;
} isp4_irq[] = {
} 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;
int irq[ARRAY_SIZE(isp4_irq)];
struct isp4_subdev *isp_subdev;
struct isp4_device *isp_dev;
int ret;
@ -47,6 +128,12 @@ static int isp4_capture_probe(struct platform_device *pdev)
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)
@ -55,7 +142,8 @@ static int isp4_capture_probe(struct platform_device *pdev)
isp4_irq[i].rb_int_num);
ret = devm_request_irq(dev, irq[i], isp4_irq_handler,
IRQF_NO_AUTOEN, isp4_irq[i].name, dev);
IRQF_NO_AUTOEN, isp4_irq[i].name,
isp_subdev);
if (ret)
return dev_err_probe(dev, ret, "fail to req irq %d\n",
irq[i]);
@ -78,6 +166,13 @@ static int isp4_capture_probe(struct platform_device *pdev)
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_device_register(&isp_dev->mdev);
if (ret) {
dev_err_probe(dev, ret, "fail to register media device\n");
@ -89,6 +184,8 @@ static int isp4_capture_probe(struct platform_device *pdev)
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:
@ -103,6 +200,7 @@ static void isp4_capture_remove(struct platform_device *pdev)
struct device *dev = &pdev->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);

View File

@ -6,12 +6,15 @@
#ifndef _ISP4_H_
#define _ISP4_H_
#include <media/v4l2-device.h>
#include <media/videobuf2-memops.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_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,120 @@
/* 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"
/*
* 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 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;
};
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_ */