19a54c285SBin Du // SPDX-License-Identifier: GPL-2.0+ 29a54c285SBin Du /* 39a54c285SBin Du * Copyright (C) 2025 Advanced Micro Devices, Inc. 49a54c285SBin Du */ 59a54c285SBin Du 64e5e7a7dSBin Du #include <linux/irq.h> 79a54c285SBin Du #include <linux/pm_runtime.h> 89a54c285SBin Du #include <linux/vmalloc.h> 99a54c285SBin Du #include <media/v4l2-ioctl.h> 109a54c285SBin Du 119a54c285SBin Du #include "isp4.h" 12*ec4bec22SBin Du #include "isp4_debug.h" 134e5e7a7dSBin Du #include "isp4_hw_reg.h" 149a54c285SBin Du 159a54c285SBin Du #define ISP4_DRV_NAME "amd_isp_capture" 164e5e7a7dSBin Du #define ISP4_FW_RESP_RB_IRQ_STATUS_MASK \ 174e5e7a7dSBin Du (ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK | \ 184e5e7a7dSBin Du ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK) 199a54c285SBin Du 209a54c285SBin Du static const struct { 219a54c285SBin Du const char *name; 229a54c285SBin Du u32 status_mask; 239a54c285SBin Du u32 en_mask; 249a54c285SBin Du u32 ack_mask; 259a54c285SBin Du u32 rb_int_num; 264e5e7a7dSBin Du } isp4_irq[ISP4SD_MAX_FW_RESP_STREAM_NUM] = { 279a54c285SBin Du /* The IRQ order is aligned with the isp4_subdev.fw_resp_thread order */ 289a54c285SBin Du { 299a54c285SBin Du .name = "isp_irq_global", 304e5e7a7dSBin Du .status_mask = 314e5e7a7dSBin Du ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT12_INT_MASK, 324e5e7a7dSBin Du .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT12_EN_MASK, 334e5e7a7dSBin Du .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT12_ACK_MASK, 349a54c285SBin Du .rb_int_num = 4, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT12 */ 359a54c285SBin Du }, 369a54c285SBin Du { 379a54c285SBin Du .name = "isp_irq_stream1", 384e5e7a7dSBin Du .status_mask = 394e5e7a7dSBin Du ISP_SYS_INT0_STATUS__SYS_INT_RINGBUFFER_WPT9_INT_MASK, 404e5e7a7dSBin Du .en_mask = ISP_SYS_INT0_EN__SYS_INT_RINGBUFFER_WPT9_EN_MASK, 414e5e7a7dSBin Du .ack_mask = ISP_SYS_INT0_ACK__SYS_INT_RINGBUFFER_WPT9_ACK_MASK, 429a54c285SBin Du .rb_int_num = 0, /* ISP_4_1__SRCID__ISP_RINGBUFFER_WPT9 */ 439a54c285SBin Du }, 449a54c285SBin Du }; 459a54c285SBin Du 464e5e7a7dSBin Du void isp4_intr_enable(struct isp4_subdev *isp_subdev, u32 index, bool enable) 474e5e7a7dSBin Du { 484e5e7a7dSBin Du u32 intr_en; 494e5e7a7dSBin Du 504e5e7a7dSBin Du /* Synchronize ISP_SYS_INT0_EN writes with the IRQ handler's writes */ 514e5e7a7dSBin Du spin_lock_irq(&isp_subdev->irq_lock); 524e5e7a7dSBin Du intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN); 534e5e7a7dSBin Du if (enable) 544e5e7a7dSBin Du intr_en |= isp4_irq[index].en_mask; 554e5e7a7dSBin Du else 564e5e7a7dSBin Du intr_en &= ~isp4_irq[index].en_mask; 574e5e7a7dSBin Du 584e5e7a7dSBin Du isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); 594e5e7a7dSBin Du spin_unlock_irq(&isp_subdev->irq_lock); 604e5e7a7dSBin Du } 614e5e7a7dSBin Du 624e5e7a7dSBin Du static void isp4_wake_up_resp_thread(struct isp4_subdev *isp_subdev, u32 index) 634e5e7a7dSBin Du { 644e5e7a7dSBin Du struct isp4sd_thread_handler *thread_ctx = 654e5e7a7dSBin Du &isp_subdev->fw_resp_thread[index]; 664e5e7a7dSBin Du 674e5e7a7dSBin Du thread_ctx->resp_ready = true; 684e5e7a7dSBin Du wake_up_interruptible(&thread_ctx->waitq); 694e5e7a7dSBin Du } 704e5e7a7dSBin Du 719a54c285SBin Du static irqreturn_t isp4_irq_handler(int irq, void *arg) 729a54c285SBin Du { 734e5e7a7dSBin Du struct isp4_subdev *isp_subdev = arg; 744e5e7a7dSBin Du u32 intr_ack = 0, intr_en = 0, intr_status; 754e5e7a7dSBin Du int seen = 0; 764e5e7a7dSBin Du 774e5e7a7dSBin Du /* Get the ISP_SYS interrupt status */ 784e5e7a7dSBin Du intr_status = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_STATUS); 794e5e7a7dSBin Du intr_status &= ISP4_FW_RESP_RB_IRQ_STATUS_MASK; 804e5e7a7dSBin Du 814e5e7a7dSBin Du /* Find which ISP_SYS interrupts fired */ 824e5e7a7dSBin Du for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) { 834e5e7a7dSBin Du if (intr_status & isp4_irq[i].status_mask) { 844e5e7a7dSBin Du intr_ack |= isp4_irq[i].ack_mask; 854e5e7a7dSBin Du intr_en |= isp4_irq[i].en_mask; 864e5e7a7dSBin Du seen |= BIT(i); 874e5e7a7dSBin Du } 884e5e7a7dSBin Du } 894e5e7a7dSBin Du 904e5e7a7dSBin Du /* 914e5e7a7dSBin Du * Disable the ISP_SYS interrupts that fired. Must be done before waking 924e5e7a7dSBin Du * the response threads, since they re-enable interrupts when finished. 934e5e7a7dSBin Du * The lock synchronizes RMW of INT0_EN with isp4_enable_interrupt(). 944e5e7a7dSBin Du */ 954e5e7a7dSBin Du spin_lock(&isp_subdev->irq_lock); 964e5e7a7dSBin Du intr_en = isp4hw_rreg(isp_subdev->mmio, ISP_SYS_INT0_EN) & ~intr_en; 974e5e7a7dSBin Du isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_EN, intr_en); 984e5e7a7dSBin Du spin_unlock(&isp_subdev->irq_lock); 994e5e7a7dSBin Du 1004e5e7a7dSBin Du /* 1014e5e7a7dSBin Du * Clear the ISP_SYS interrupts. This must be done after the interrupts 1024e5e7a7dSBin Du * are disabled, so that ISP FW won't flag any new interrupts on these 1034e5e7a7dSBin Du * streams, and thus we don't need to clear interrupts again before 1044e5e7a7dSBin Du * re-enabling them in the response thread. 1054e5e7a7dSBin Du */ 1064e5e7a7dSBin Du isp4hw_wreg(isp_subdev->mmio, ISP_SYS_INT0_ACK, intr_ack); 1074e5e7a7dSBin Du 1084e5e7a7dSBin Du /* 1094e5e7a7dSBin Du * The operation `(seen >> i) << i` is logically equivalent to 1104e5e7a7dSBin Du * `seen &= ~BIT(i)`, with fewer instructions after compilation. 1114e5e7a7dSBin Du */ 1124e5e7a7dSBin Du for (int i; (i = ffs(seen)); seen = (seen >> i) << i) 1134e5e7a7dSBin Du isp4_wake_up_resp_thread(isp_subdev, i - 1); 1144e5e7a7dSBin Du 1159a54c285SBin Du return IRQ_HANDLED; 1169a54c285SBin Du } 1179a54c285SBin Du 1189a54c285SBin Du static int isp4_capture_probe(struct platform_device *pdev) 1199a54c285SBin Du { 1204e5e7a7dSBin Du int irq[ISP4SD_MAX_FW_RESP_STREAM_NUM]; 1219a54c285SBin Du struct device *dev = &pdev->dev; 1224e5e7a7dSBin Du struct isp4_subdev *isp_subdev; 1239a54c285SBin Du struct isp4_device *isp_dev; 1249a54c285SBin Du int ret; 1259a54c285SBin Du 1269a54c285SBin Du isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL); 1279a54c285SBin Du if (!isp_dev) 1289a54c285SBin Du return -ENOMEM; 1299a54c285SBin Du 1309a54c285SBin Du dev->init_name = ISP4_DRV_NAME; 1319a54c285SBin Du 1324e5e7a7dSBin Du isp_subdev = &isp_dev->isp_subdev; 1334e5e7a7dSBin Du isp_subdev->mmio = devm_platform_ioremap_resource(pdev, 0); 1344e5e7a7dSBin Du if (IS_ERR(isp_subdev->mmio)) 1354e5e7a7dSBin Du return dev_err_probe(dev, PTR_ERR(isp_subdev->mmio), 1364e5e7a7dSBin Du "isp ioremap fail\n"); 1374e5e7a7dSBin Du 1389a54c285SBin Du for (size_t i = 0; i < ARRAY_SIZE(isp4_irq); i++) { 1399a54c285SBin Du irq[i] = platform_get_irq(pdev, isp4_irq[i].rb_int_num); 1409a54c285SBin Du if (irq[i] < 0) 1419a54c285SBin Du return dev_err_probe(dev, irq[i], 1429a54c285SBin Du "fail to get irq %d\n", 1439a54c285SBin Du isp4_irq[i].rb_int_num); 1449a54c285SBin Du 1459a54c285SBin Du ret = devm_request_irq(dev, irq[i], isp4_irq_handler, 1464e5e7a7dSBin Du IRQF_NO_AUTOEN, isp4_irq[i].name, 1474e5e7a7dSBin Du isp_subdev); 1489a54c285SBin Du if (ret) 1499a54c285SBin Du return dev_err_probe(dev, ret, "fail to req irq %d\n", 1509a54c285SBin Du irq[i]); 1519a54c285SBin Du } 1529a54c285SBin Du 1539a54c285SBin Du isp_dev->v4l2_dev.mdev = &isp_dev->mdev; 1549a54c285SBin Du 1559a54c285SBin Du strscpy(isp_dev->mdev.model, "amd_isp41_mdev", 1569a54c285SBin Du sizeof(isp_dev->mdev.model)); 1579a54c285SBin Du isp_dev->mdev.dev = dev; 1589a54c285SBin Du media_device_init(&isp_dev->mdev); 1599a54c285SBin Du 1609a54c285SBin Du snprintf(isp_dev->v4l2_dev.name, sizeof(isp_dev->v4l2_dev.name), 1619a54c285SBin Du "AMD-V4L2-ROOT"); 1629a54c285SBin Du ret = v4l2_device_register(dev, &isp_dev->v4l2_dev); 1639a54c285SBin Du if (ret) { 1649a54c285SBin Du dev_err_probe(dev, ret, "fail register v4l2 device\n"); 1659a54c285SBin Du goto err_clean_media; 1669a54c285SBin Du } 1679a54c285SBin Du 1689a54c285SBin Du pm_runtime_set_suspended(dev); 1699a54c285SBin Du pm_runtime_enable(dev); 1704e5e7a7dSBin Du spin_lock_init(&isp_subdev->irq_lock); 1714e5e7a7dSBin Du ret = isp4sd_init(&isp_dev->isp_subdev, &isp_dev->v4l2_dev, irq); 1724e5e7a7dSBin Du if (ret) { 1734e5e7a7dSBin Du dev_err_probe(dev, ret, "fail init isp4 sub dev\n"); 1744e5e7a7dSBin Du goto err_pm_disable; 1754e5e7a7dSBin Du } 1764e5e7a7dSBin Du 1772ccf48afSBin Du ret = media_create_pad_link(&isp_dev->isp_subdev.sdev.entity, 1782ccf48afSBin Du 0, 1792ccf48afSBin Du &isp_dev->isp_subdev.isp_vdev.vdev.entity, 1802ccf48afSBin Du 0, 1812ccf48afSBin Du MEDIA_LNK_FL_ENABLED | 1822ccf48afSBin Du MEDIA_LNK_FL_IMMUTABLE); 1832ccf48afSBin Du if (ret) { 1842ccf48afSBin Du dev_err_probe(dev, ret, "fail to create pad link\n"); 1852ccf48afSBin Du goto err_isp4_deinit; 1862ccf48afSBin Du } 1872ccf48afSBin Du 1889a54c285SBin Du ret = media_device_register(&isp_dev->mdev); 1899a54c285SBin Du if (ret) { 1909a54c285SBin Du dev_err_probe(dev, ret, "fail to register media device\n"); 1919a54c285SBin Du goto err_isp4_deinit; 1929a54c285SBin Du } 1939a54c285SBin Du 1949a54c285SBin Du platform_set_drvdata(pdev, isp_dev); 195*ec4bec22SBin Du isp_debugfs_create(isp_dev); 1969a54c285SBin Du 1979a54c285SBin Du return 0; 1989a54c285SBin Du 1999a54c285SBin Du err_isp4_deinit: 2004e5e7a7dSBin Du isp4sd_deinit(&isp_dev->isp_subdev); 2014e5e7a7dSBin Du err_pm_disable: 2029a54c285SBin Du pm_runtime_disable(dev); 2039a54c285SBin Du v4l2_device_unregister(&isp_dev->v4l2_dev); 2049a54c285SBin Du err_clean_media: 2059a54c285SBin Du media_device_cleanup(&isp_dev->mdev); 2069a54c285SBin Du 2079a54c285SBin Du return ret; 2089a54c285SBin Du } 2099a54c285SBin Du 2109a54c285SBin Du static void isp4_capture_remove(struct platform_device *pdev) 2119a54c285SBin Du { 2129a54c285SBin Du struct isp4_device *isp_dev = platform_get_drvdata(pdev); 2139a54c285SBin Du struct device *dev = &pdev->dev; 2149a54c285SBin Du 215*ec4bec22SBin Du isp_debugfs_remove(isp_dev); 216*ec4bec22SBin Du 2179a54c285SBin Du media_device_unregister(&isp_dev->mdev); 2184e5e7a7dSBin Du isp4sd_deinit(&isp_dev->isp_subdev); 2199a54c285SBin Du pm_runtime_disable(dev); 2209a54c285SBin Du v4l2_device_unregister(&isp_dev->v4l2_dev); 2219a54c285SBin Du media_device_cleanup(&isp_dev->mdev); 2229a54c285SBin Du } 2239a54c285SBin Du 2249a54c285SBin Du static struct platform_driver isp4_capture_drv = { 2259a54c285SBin Du .probe = isp4_capture_probe, 2269a54c285SBin Du .remove = isp4_capture_remove, 2279a54c285SBin Du .driver = { 2289a54c285SBin Du .name = ISP4_DRV_NAME, 2299a54c285SBin Du } 2309a54c285SBin Du }; 2319a54c285SBin Du 2329a54c285SBin Du module_platform_driver(isp4_capture_drv); 2339a54c285SBin Du 2349a54c285SBin Du MODULE_ALIAS("platform:" ISP4_DRV_NAME); 2359a54c285SBin Du MODULE_IMPORT_NS("DMA_BUF"); 2369a54c285SBin Du 2379a54c285SBin Du MODULE_DESCRIPTION("AMD ISP4 Driver"); 2389a54c285SBin Du MODULE_AUTHOR("Bin Du <bin.du@amd.com>"); 2399a54c285SBin Du MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>"); 2409a54c285SBin Du MODULE_LICENSE("GPL"); 241