// SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2025 Advanced Micro Devices, Inc. */ #include #include #include #include #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 "); MODULE_AUTHOR("Pratap Nirujogi "); MODULE_LICENSE("GPL");