mirror of
https://github.com/torvalds/linux.git
synced 2026-07-28 01:55:51 +02:00
Add debug fs for isp4 driver and add more detailed descriptive error info to some of the log message 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>
241 lines
6.7 KiB
C
241 lines
6.7 KiB
C
// 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");
|