mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
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:
parent
4c5feef6a6
commit
4e5e7a7ddb
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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_ */
|
||||
|
|
|
|||
1029
drivers/media/platform/amd/isp4/isp4_subdev.c
Normal file
1029
drivers/media/platform/amd/isp4/isp4_subdev.c
Normal file
File diff suppressed because it is too large
Load Diff
120
drivers/media/platform/amd/isp4/isp4_subdev.h
Normal file
120
drivers/media/platform/amd/isp4/isp4_subdev.h
Normal 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_ */
|
||||
Loading…
Reference in New Issue
Block a user