// SPDX-License-Identifier: (GPL-2.0-only OR MIT) /* * Copyright (C) 2025 Amlogic, Inc. All rights reserved */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "c4-isp-common.h" #include "hw/c4-isp-hw.h" #include "hw/c4-isp-regs.h" #define C4_IPP_RDMA_INIT_SIZE (32 * SZ_1K) #define C4_IPP_RDMA_CFG_SIZE (4 * SZ_1K) #define C4_FEBE_RDMA_BASE_SIZE (512 * SZ_1K) #define C4_FEBE_RDMA_FDN_SIZE (256 * SZ_1K) #define C4_FEBE_RDMA_CFG_SIZE (192 * SZ_1K) #define C4_DISP_RDMA_BASE_SIZE (8 * SZ_1K) #define C4_DISP_RDMA_INIT_SIZE (8 * SZ_1K) #define C4_DISP_RDMA_REG_BASE 0x12000 #define C4_ISP_DISP_REG(reg, id) ((reg) + (id) * 0x100) enum c4_isp_debug_idx { ISP_IN_W = 0, ISP_IN_H, DISP0_W, DISP0_H, DISP1_W, DISP1_H, DISP2_W, DISP2_H, SIZE0_AF, SIZE0_AWB, SIZE1_AE, SIZE1_FLKR, SIZE2_POST, CSUM_0, CSUM_1, CSUM_2, CSUM_3, ISP_DEBUG_NUM }; static struct c4_isp_thread_info thread_info; static struct c4_isp_data_buffer rdma_febe_orig; static struct c4_isp_data_buffer rdma_disp_orig; static struct c4_ipp_rdma_info rdma_ipp_info; static bool rdma_alloc_en = true; module_param(rdma_alloc_en, bool, 0444); static u32 c4_isp_read_field(u32 val, u32 start, u32 len) { u32 mask = (len == 32) ? ~0U : ((1U << len) - 1); return (val >> start) & mask; } static u32 c4_isp_code_to_bpp(u32 code) { switch (code) { case MEDIA_BUS_FMT_SBGGR10_1X10: case MEDIA_BUS_FMT_SGBRG10_1X10: case MEDIA_BUS_FMT_SGRBG10_1X10: case MEDIA_BUS_FMT_SRGGB10_1X10: return 10; case MEDIA_BUS_FMT_SBGGR12_1X12: case MEDIA_BUS_FMT_SGBRG12_1X12: case MEDIA_BUS_FMT_SGRBG12_1X12: case MEDIA_BUS_FMT_SRGGB12_1X12: return 12; default: return 0; } } static u32 *c4_isp_proc_status(struct c4_isp_device *isp) { static u32 isp_debuginfo[ISP_DEBUG_NUM]; u32 val; int i; val = c4_isp_base_hw_read(isp, ISP_TOP_FRM_SIZE); isp_debuginfo[ISP_IN_W] = c4_isp_read_field(val, 16, 16); isp_debuginfo[ISP_IN_H] = c4_isp_read_field(val, 0, 16); for (i = 0; i < 3; i++) { static const u32 idwmif_cfg_regs[] = { IDWMIF_CFG_0, IDWMIF_CFG_1, IDWMIF_CFG_2 }; u32 stream_on; stream_on = c4_isp_read_field( c4_isp_base_hw_read(isp, idwmif_cfg_regs[i]), 0, 1); if (stream_on) { val = c4_isp_disp_hw_read(isp, C4_ISP_DISP_REG(DISP_OUT_SIZE, i)); isp_debuginfo[DISP0_W + 2 * i] = c4_isp_read_field(val, 16, 16); isp_debuginfo[DISP0_H + 2 * i] = c4_isp_read_field(val, 0, 16); } else { isp_debuginfo[DISP0_W + 2 * i] = 0; isp_debuginfo[DISP0_H + 2 * i] = 0; } } val = c4_isp_base_hw_read(isp, VIU_DMAWR_SIZE0); isp_debuginfo[SIZE0_AF] = c4_isp_read_field(val, 0, 16); isp_debuginfo[SIZE0_AWB] = c4_isp_read_field(val, 16, 16); val = c4_isp_base_hw_read(isp, VIU_DMAWR_SIZE1); isp_debuginfo[SIZE1_AE] = c4_isp_read_field(val, 0, 16); isp_debuginfo[SIZE1_FLKR] = c4_isp_read_field(val, 16, 16); val = c4_isp_base_hw_read(isp, VIU_DMAWR_SIZE2); isp_debuginfo[SIZE2_POST] = c4_isp_read_field(val, 0, 16); for (i = 0; i < 4; i++) isp_debuginfo[CSUM_0 + i] = c4_isp_base_hw_read(isp, ISP_CHECKSUM_RO_DAT_0 + i * 4); return isp_debuginfo; } static const char *c4_isp_work_mode_str(enum c4_isp_work_mode mode) { switch (mode) { case C4_ISP_RAW2DDR_MODE: return "RAW2DDR"; case C4_ISP_ALLSCAN_MODE: return "ALLSCAN"; case C4_ISP_SCANTILE_MODE: return "SCANTILE"; case C4_ISP_ALLTILE_MODE: return "ALLTILE"; case C4_ISP_OFFTILE_MODE: return "OFFTILE"; case C4_ISP_OFFPOST_MODE: return "OFFPOST"; case C4_ISP_OFFHUGE_MODE: return "OFFHUGE"; default: return "UNKNOWN"; } } static const char *c4_isp_image_mode_str(enum c4_isp_image_mode mode) { switch (mode) { case C4_ISP_IMAGE_SDR: return "SDR"; case C4_ISP_IMAGE_HDR: return "HDR"; default: return "UNKNOWN"; } } static int c4_isp_proc_show(struct seq_file *m, void *v) { struct c4_isp_device *isp = m->private; struct c4_isp_core *core = &isp->core; u32 *isp_info = c4_isp_proc_status(isp); struct v4l2_subdev_format fmt = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, .pad = C4_ISP_CORE_PAD_SINK_VIDEO, }; u32 width = 0, height = 0, bpp = 0; if (!v4l2_subdev_call_state_active(&core->sd, pad, get_fmt, &fmt)) { width = fmt.format.width; height = fmt.format.height; bpp = c4_isp_code_to_bpp(fmt.format.code); } seq_printf(m, "\n****************** ISP MODULE PARAM *******************\n"); seq_printf(m, " ------- Pubattr Info ------- \n"); seq_printf(m, "%8s%8s%8s%8s%8s \n", "WndX", "WndY", "snsW", "snsH", "bpp"); seq_printf(m, "%8d%8d%8d%8d%8d \n\n", 0, 0, width, height, bpp); seq_printf(m, "%12s%12s\n", "work_mode", "wdr_mode"); seq_printf(m, "%12s%12s\n\n", c4_isp_work_mode_str(core->cfg_info.work_mode), c4_isp_image_mode_str(core->cfg_info.img_mode)); seq_printf(m, " ------- Isp Solution ------- \n"); seq_printf(m, "%15s%15s \n", "input vsize", "input hsize"); seq_printf(m, "%15d%15d \n\n", isp_info[ISP_IN_H], isp_info[ISP_IN_W]); seq_printf(m, " ------- Disp Solution ------- \n"); seq_printf(m, "%15s%15s \n", "fmt hsize", "fmt vsize"); seq_printf(m, "%4s%11d%15d \n", "chn0", isp_info[DISP0_W], isp_info[DISP0_H]); seq_printf(m, "%4s%11d%15d \n", "chn1", isp_info[DISP1_W], isp_info[DISP1_H]); seq_printf(m, "%4s%11d%15d \n\n", "chn2", isp_info[DISP2_W], isp_info[DISP2_H]); seq_printf(m, " ------- Dma Configuration ------- \n"); seq_printf(m, "%10s%5s%15s%15s%15s%15s \n", "chx_size0-4", "af", "awb", "ae", "flkr", "post"); seq_printf(m, "%16d%15d%15d%15d%15d \n\n", isp_info[SIZE0_AF], isp_info[SIZE0_AWB], isp_info[SIZE1_AE], isp_info[SIZE1_FLKR], isp_info[SIZE2_POST]); seq_printf(m, " ------- Isp Checksum ------- \n"); seq_printf(m, " -- ro_checksum_dat_[0-3] -- \n"); seq_printf(m, "%15s%15s%15s%15s \n", "fe_din", "fe_dout", "me_dout", "be_dout"); seq_printf(m, "%15u%15u%15u%15u \n\n", isp_info[CSUM_0], isp_info[CSUM_1], isp_info[CSUM_2], isp_info[CSUM_3]); seq_printf(m, "******************* PROC END *******************\n\n"); return 0; } static int c4_isp_debug_open(struct inode *inode, struct file *file) { #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 6, 0) return single_open(file, c4_isp_proc_show, pde_data(inode)); #else return single_open(file, c4_isp_proc_show, PDE_DATA(inode)); #endif } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0) static const struct proc_ops c4_isp_proc_file_ops = { .proc_open = c4_isp_debug_open, .proc_read = seq_read, .proc_lseek = seq_lseek, .proc_release = single_release, }; #else static const struct file_operations c4_isp_proc_file_ops = { .open = c4_isp_debug_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; #endif static int c4_isp_proc_init(struct c4_isp_device *isp) { char name[16]; snprintf(name, sizeof(name), "isp%u", isp->info->id); isp->proc_entry = proc_create_data(name, 0644, NULL, &c4_isp_proc_file_ops, isp); if (!isp->proc_entry) { dev_err(isp->dev, "isp%u create proc failed\n", isp->info->id); return -ENOMEM; } return 0; } static void c4_isp_proc_remove(struct c4_isp_device *isp) { if (isp->proc_entry) { proc_remove(isp->proc_entry); isp->proc_entry = NULL; } } static int c4_isp_fill_rdma_buff(struct c4_isp_device *isp, bool febe, struct c4_isp_data_buffer *buff) { u32 count = febe ? C4_ISP_FEBE_RDMA_REG_MAX : C4_ISP_DISP_RDMA_REG_MAX; struct c4_isp_rdma_base_reg *rdma_orig = buff->vaddr; int ret; ret = pm_runtime_resume_and_get(isp->dev); if (ret) { dev_err(isp->dev, "Failed to resume and get\n"); return ret; } c4_isp_top_cfg_reset(isp); memset(buff->vaddr, 0xff, buff->size); for (unsigned int reg = 0; reg < count; reg++) { if (febe) { rdma_orig[reg].addr = reg << 2; rdma_orig[reg].value = c4_isp_base_hw_read(isp, reg << 2); } else { rdma_orig[reg].addr = (reg << 2) + C4_DISP_RDMA_REG_BASE; rdma_orig[reg].value = c4_isp_disp_hw_read(isp, reg << 2); } } pm_runtime_put(isp->dev); return 0; } static int c4_isp_rdma_alloc_buff(struct c4_isp_device *isp, struct c4_isp_data_buffer *buff, u32 size) { if (buff->vaddr) return 0; buff->size = size; buff->vaddr = dma_alloc_attrs(isp->dev, buff->size, &buff->dma_addr, GFP_KERNEL, 0); if (!buff->vaddr) return -ENOMEM; return 0; } static void c4_isp_rdma_free_buff(struct c4_isp_device *isp, struct c4_isp_data_buffer *buff) { if (!buff->vaddr) return; dma_free_attrs(isp->dev, buff->size, buff->vaddr, buff->dma_addr, 0); buff->vaddr = NULL; buff->dma_addr = 0; buff->size = 0; } static int c4_isp_rdma_alloc_ipp_buffs(struct c4_isp_device *isp) { struct c4_ipp_rdma_info *ipp_rdma_info = &rdma_ipp_info; int ret; if (ipp_rdma_info->rdma_init.vaddr) { isp->ipp_rdma_info = ipp_rdma_info; return 0; } ret = c4_isp_rdma_alloc_buff(isp, &ipp_rdma_info->rdma_init, C4_IPP_RDMA_INIT_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc ipp_rdma_init buffer\n"); return ret; } ret = c4_isp_rdma_alloc_buff(isp, &ipp_rdma_info->rdma_cfg, C4_IPP_RDMA_CFG_SIZE); if (ret) { c4_isp_rdma_free_buff(isp, &ipp_rdma_info->rdma_init); dev_err(isp->dev, "Failed to alloc ipp_rdma_cfg buffer\n"); return -ENOMEM; } isp->ipp_rdma_info = ipp_rdma_info; dev_info(isp->dev, "Ipp_rdma-(init/cfg): 0x%llx, 0x%llx\n", ipp_rdma_info->rdma_init.dma_addr, ipp_rdma_info->rdma_cfg.dma_addr); return 0; } static void c4_isp_rdma_free_ipp_buffs(struct c4_isp_device *isp) { c4_isp_rdma_free_buff(isp, &isp->ipp_rdma_info->rdma_init); c4_isp_rdma_free_buff(isp, &isp->ipp_rdma_info->rdma_cfg); } static int c4_isp_rdma_alloc_febe_buffs(struct c4_isp_device *isp) { struct c4_isp_data_buffer *febe_rdma_orig = &rdma_febe_orig; int ret; if (!febe_rdma_orig->vaddr) { ret = c4_isp_rdma_alloc_buff(isp, febe_rdma_orig, C4_FEBE_RDMA_BASE_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc febe_rdma_orig buffer\n"); return -ENOMEM; } c4_isp_fill_rdma_buff(isp, true, febe_rdma_orig); } ret = c4_isp_rdma_alloc_buff(isp, &isp->febe_rdma_base, C4_FEBE_RDMA_BASE_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc febe_rdma_base buffer\n"); goto err_febe_rdma_orig_free_buffer; } ret = c4_isp_rdma_alloc_buff(isp, &isp->febe_rdma_init, C4_FEBE_RDMA_CFG_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc febe_rdma_init buffer\n"); goto err_febe_rdma_base_free_buffer; } ret = c4_isp_rdma_alloc_buff(isp, &isp->febe_rdma_cfg, C4_FEBE_RDMA_CFG_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc febe_rdma_cfg buffer\n"); goto err_febe_rdma_init_free_buffer; } ret = c4_isp_rdma_alloc_buff(isp, &isp->febe_rdma_fdn, C4_FEBE_RDMA_FDN_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc febe_rdma_fdn buffer\n"); goto err_febe_rdma_cfg_free_buffer; } isp->febe_rdma_orig = febe_rdma_orig; dev_info(isp->dev, "Febe_rdma-(orig/base): 0x%llx, 0x%llx\n", isp->febe_rdma_orig->dma_addr, isp->febe_rdma_base.dma_addr); dev_info(isp->dev, "Febe_rdma-(fdn/init/cfg): 0x%llx, 0x%llx, 0x%llx\n", isp->febe_rdma_fdn.dma_addr, isp->febe_rdma_init.dma_addr, isp->febe_rdma_cfg.dma_addr); return 0; err_febe_rdma_cfg_free_buffer: c4_isp_rdma_free_buff(isp, &isp->febe_rdma_cfg); err_febe_rdma_init_free_buffer: c4_isp_rdma_free_buff(isp, &isp->febe_rdma_init); err_febe_rdma_base_free_buffer: c4_isp_rdma_free_buff(isp, &isp->febe_rdma_base); err_febe_rdma_orig_free_buffer: c4_isp_rdma_free_buff(isp, febe_rdma_orig); return ret; } static void c4_isp_rdma_free_febe_buffs(struct c4_isp_device *isp) { c4_isp_rdma_free_buff(isp, &isp->febe_rdma_fdn); c4_isp_rdma_free_buff(isp, &isp->febe_rdma_cfg); c4_isp_rdma_free_buff(isp, &isp->febe_rdma_init); c4_isp_rdma_free_buff(isp, &isp->febe_rdma_base); c4_isp_rdma_free_buff(isp, isp->febe_rdma_orig); } static int c4_isp_rdma_alloc_disp_buffs(struct c4_isp_device *isp) { struct c4_isp_data_buffer *disp_rdma_orig = &rdma_disp_orig; int ret; if (!disp_rdma_orig->vaddr) { ret = c4_isp_rdma_alloc_buff(isp, disp_rdma_orig, C4_DISP_RDMA_BASE_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc disp_rdma_orig buffer\n"); return -ENOMEM; } c4_isp_fill_rdma_buff(isp, false, disp_rdma_orig); } ret = c4_isp_rdma_alloc_buff(isp, &isp->disp_rdma_base, C4_DISP_RDMA_INIT_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc disp_rdma_base buffer\n"); goto err_disp_rdma_orig_free_buffer; } ret = c4_isp_rdma_alloc_buff(isp, &isp->disp_rdma_init, C4_DISP_RDMA_INIT_SIZE); if (ret) { dev_err(isp->dev, "Failed to alloc disp_rdma_init buffer\n"); goto err_disp_rdma_base_free_buffer; } isp->disp_rdma_orig = disp_rdma_orig; dev_info(isp->dev, "Disp_rdma-(orig/base/init): 0x%llx, 0x%llx, 0x%llx\n", isp->disp_rdma_orig->dma_addr, isp->disp_rdma_base.dma_addr, isp->disp_rdma_init.dma_addr); return 0; err_disp_rdma_base_free_buffer: c4_isp_rdma_free_buff(isp, &isp->disp_rdma_base); err_disp_rdma_orig_free_buffer: c4_isp_rdma_free_buff(isp, disp_rdma_orig); return ret; } static void c4_isp_rdma_free_disp_buffs(struct c4_isp_device *isp) { c4_isp_rdma_free_buff(isp, &isp->disp_rdma_init); c4_isp_rdma_free_buff(isp, &isp->disp_rdma_base); c4_isp_rdma_free_buff(isp, isp->disp_rdma_orig); } static int c4_isp_rdma_alloc_buffers(struct c4_isp_device *isp) { int ret; if (!isp->rdma_alloc_en) return 0; ret = c4_isp_rdma_alloc_ipp_buffs(isp); if (ret) return ret; ret = c4_isp_rdma_alloc_febe_buffs(isp); if (ret) goto err_rdma_free_ipp_buffs; ret = c4_isp_rdma_alloc_disp_buffs(isp); if (ret) goto err_rdma_free_febe_buffs; isp->rdma_inited_cnt = 0; return 0; err_rdma_free_febe_buffs: c4_isp_rdma_free_febe_buffs(isp); err_rdma_free_ipp_buffs: c4_isp_rdma_free_ipp_buffs(isp); return ret; } static void c4_isp_rdma_free_buffers(struct c4_isp_device *isp) { if (!isp->rdma_alloc_en) return; c4_isp_rdma_free_disp_buffs(isp); c4_isp_rdma_free_febe_buffs(isp); c4_isp_rdma_free_ipp_buffs(isp); } static int c4_isp_rdma_cfg_buffer(struct c4_isp_device *isp) { int ret; if (isp->core.cfg_info.work_mode == C4_ISP_OFFTILE_MODE || isp->core.cfg_info.work_mode == C4_ISP_OFFPOST_MODE || isp->core.cfg_info.work_mode == C4_ISP_OFFHUGE_MODE || !isp->rdma_en) return 0; ret = c4_isp_rdma_manual_start(isp, &isp->febe_rdma_cfg, isp->febe_rdma_cfg_reg_cnt); if (ret) { dev_err(isp->dev, "Failed to manual start isp_rdma_cfg\n"); return ret; } ret = c4_isp_rdma_manual_start(isp, &isp->disp_rdma_init, isp->disp_rdma_init_reg_cnt); if (ret) { dev_err(isp->dev, "Failed to manual start disp_rdma_init\n"); return ret; } return 0; } static void c4_isp_cfg_plat_setting(struct c4_isp_device *isp) { isp->fpga_env = false; isp->rdma_alloc_en = rdma_alloc_en; isp->rdma_en = rdma_alloc_en; isp->thrd_info = &thread_info; } /* PM runtime suspend */ static int c4_isp_runtime_suspend(struct device *dev) { struct c4_isp_device *isp = dev_get_drvdata(dev); pm_runtime_put_sync(isp->isp_pd); pm_runtime_put_sync(isp->opp_pd); clk_bulk_disable_unprepare(isp->info->clock_num, isp->clks); return 0; } /* PM runtime resume */ static int c4_isp_runtime_resume(struct device *dev) { struct c4_isp_device *isp = dev_get_drvdata(dev); pm_runtime_get_sync(isp->isp_pd); pm_runtime_get_sync(isp->opp_pd); return clk_bulk_prepare_enable(isp->info->clock_num, isp->clks); } static const struct dev_pm_ops c4_isp_pm_ops = { SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, pm_runtime_force_resume) RUNTIME_PM_OPS(c4_isp_runtime_suspend, c4_isp_runtime_resume, NULL) }; /* IRQ handler */ static irqreturn_t c4_isp_fe_rst_irq_handler(int irq, void *dev) { struct c4_isp_device *isp = dev; if (!isp->streaming || !isp->sending) return IRQ_HANDLED; dev_dbg(isp->dev, "FE rst intr\n"); c4_isp_core_queue_sof(isp); return IRQ_HANDLED; } static irqreturn_t c4_isp_fe_done_irq_handler(int irq, void *dev) { struct c4_isp_device *isp = dev; if (!isp->streaming || !isp->sending) return IRQ_NONE; dev_dbg(isp->dev, "FE done intr\n"); return IRQ_HANDLED; } static irqreturn_t c4_isp_be_done_irq_handler(int irq, void *dev) { struct c4_isp_device *isp = dev; if (!isp->streaming || !isp->sending) return IRQ_NONE; dev_dbg(isp->dev, "BE done intr\n"); c4_isp_captures_isr(isp, C4_ISP_CAP_DEV_0); c4_isp_captures_isr(isp, C4_ISP_CAP_DEV_1); c4_isp_captures_isr(isp, C4_ISP_CAP_DEV_2); c4_isp_captures_isr(isp, C4_ISP_CAP_DEV_3); c4_isp_core_nr_isr(isp); c4_isp_raw_isr(isp); c4_isp_stats_isr(isp); c4_isp_params_isr(isp); c4_isp_rdma_cfg_buffer(isp); isp->be_frm_seq++; c4_isp_offline_thread_isr(isp); return IRQ_HANDLED; } static irqreturn_t c4_isp_intf_irq_handler(int irq, void *dev) { struct c4_isp_device *isp = dev; if (!isp->streaming) return IRQ_NONE; dev_dbg(isp->dev, "INTF intf\n"); c4_ipp_multi_vc_isr(isp); return IRQ_HANDLED; } /* Subdev notifier register */ static int c4_isp_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_subdev *sd, struct v4l2_async_connection *asc) { struct c4_isp_device *isp = container_of(notifier, struct c4_isp_device, notifier); struct media_pad *sink = &isp->ipp.sd.entity.pads[C4_IPP_PAD_SINK_VIDEO]; return v4l2_create_fwnode_links_to_pad(sd, sink, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); } static int c4_isp_notify_complete(struct v4l2_async_notifier *notifier) { struct c4_isp_device *isp = container_of(notifier, struct c4_isp_device, notifier); return v4l2_device_register_subdev_nodes(&isp->v4l2_dev); } static const struct v4l2_async_notifier_operations c4_isp_notify_ops = { .bound = c4_isp_notify_bound, .complete = c4_isp_notify_complete, }; static int c4_isp_async_nf_register(struct c4_isp_device *isp) { struct v4l2_async_connection *asc; struct fwnode_handle *ep; int ret; v4l2_async_nf_init(&isp->notifier, &isp->v4l2_dev); ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), 0, 0, FWNODE_GRAPH_ENDPOINT_NEXT); if (!ep) return -ENOTCONN; asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep, struct v4l2_async_connection); fwnode_handle_put(ep); if (IS_ERR(asc)) return PTR_ERR(asc); isp->notifier.ops = &c4_isp_notify_ops; ret = v4l2_async_nf_register(&isp->notifier); if (ret) v4l2_async_nf_cleanup(&isp->notifier); return ret; } static void c4_isp_async_nf_unregister(struct c4_isp_device *isp) { v4l2_async_nf_unregister(&isp->notifier); v4l2_async_nf_cleanup(&isp->notifier); } static int c4_isp_media_register(struct c4_isp_device *isp) { struct media_device *media_dev = &isp->media_dev; struct v4l2_device *v4l2_dev = &isp->v4l2_dev; int ret; /* Initialize media device */ strscpy(media_dev->model, C4_ISP_DRIVER_NAME, sizeof(media_dev->model)); media_dev->dev = isp->dev; media_device_init(media_dev); /* Initialize v4l2 device */ v4l2_dev->mdev = media_dev; strscpy(v4l2_dev->name, C4_ISP_DRIVER_NAME, sizeof(v4l2_dev->name)); ret = v4l2_device_register(isp->dev, v4l2_dev); if (ret) goto err_media_dev_cleanup; ret = media_device_register(&isp->media_dev); if (ret) { dev_err(isp->dev, "Failed to register media device: %d\n", ret); goto err_unreg_v4l2_dev; } return 0; err_unreg_v4l2_dev: v4l2_device_unregister(&isp->v4l2_dev); err_media_dev_cleanup: media_device_cleanup(media_dev); return ret; } static void c4_isp_media_unregister(struct c4_isp_device *isp) { media_device_unregister(&isp->media_dev); v4l2_device_unregister(&isp->v4l2_dev); media_device_cleanup(&isp->media_dev); } static void c4_isp_remove_links(struct c4_isp_device *isp) { unsigned int i; media_entity_remove_links(&isp->core.sd.entity); for (i = C4_ISP_RSZ_0; i < C4_ISP_RSZ_MAX; i++) media_entity_remove_links(&isp->resizers[i].sd.entity); for (i = C4_ISP_RSZ_0; i < C4_ISP_NUM_CAP_DEVS; i++) media_entity_remove_links(&isp->caps[i].vdev.entity); } static int c4_isp_create_links(struct c4_isp_device *isp) { unsigned int i; int ret; for (i = C4_ISP_RSZ_0; i < C4_ISP_RSZ_MAX; i++) { ret = media_create_pad_link(&isp->resizers[i].sd.entity, C4_ISP_RSZ_PAD_SOURCE, &isp->caps[i].vdev.entity, 0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); if (ret) { dev_err(isp->dev, "Failed to link rsz %u and cap %u\n", i, i); goto err_remove_links; } ret = media_create_pad_link(&isp->core.sd.entity, C4_ISP_CORE_PAD_SOURCE_VIDEO_0 + i, &isp->resizers[i].sd.entity, C4_ISP_RSZ_PAD_SINK, MEDIA_LNK_FL_ENABLED); if (ret) { dev_err(isp->dev, "Failed to link core and rsz %u\n", i); goto err_remove_links; } } ret = media_create_pad_link(&isp->core.sd.entity, C4_ISP_CORE_PAD_SOURCE_RAW, &isp->raw.vdev.entity, 0, MEDIA_LNK_FL_ENABLED); if (ret) { dev_err(isp->dev, "Failed to link core and raw\n"); goto err_remove_links; } ret = media_create_pad_link(&isp->core.sd.entity, C4_ISP_CORE_PAD_SOURCE_STATS, &isp->stats.vdev.entity, 0, MEDIA_LNK_FL_ENABLED); if (ret) { dev_err(isp->dev, "Failed to link core and stats\n"); goto err_remove_links; } ret = media_create_pad_link(&isp->params.vdev.entity, 0, &isp->core.sd.entity, C4_ISP_CORE_PAD_SINK_PARAMS, MEDIA_LNK_FL_ENABLED); if (ret) { dev_err(isp->dev, "Failed to link params and core\n"); goto err_remove_links; } ret = media_create_pad_link(&isp->ipp.sd.entity, C4_IPP_PAD_SOURCE_VIDEO_ISP, &isp->core.sd.entity, C4_ISP_CORE_PAD_SINK_VIDEO, 0); if (ret) { dev_err(isp->dev, "Failed to link ipp and core\n"); goto err_remove_links; } ret = media_create_pad_link(&isp->imp.vdev.entity, 0, &isp->core.sd.entity, C4_ISP_CORE_PAD_SINK_VIDEO, 0); if (ret) { dev_err(isp->dev, "Failed to link imp and core\n"); goto err_remove_links; } for (i = C4_IPP_CAP_DEV_0; i < C4_IPP_NUM_CAP_DEVS; i++) { ret = media_create_pad_link(&isp->ipp.sd.entity, C4_IPP_PAD_SOURCE_VIDEO_0 + i, &isp->ipp_caps[i].vdev.entity, 0, MEDIA_LNK_FL_ENABLED); if (ret) { dev_err(isp->dev, "Failed to link ipp and ipp_cap: %u\n", i); goto err_remove_links; } } return 0; err_remove_links: c4_isp_remove_links(isp); return ret; } static int c4_isp_videos_register(struct c4_isp_device *isp) { int ret; ret = c4_isp_captures_register(isp); if (ret) return ret; ret = c4_isp_stats_register(isp); if (ret) goto err_captures_unregister; ret = c4_isp_params_register(isp); if (ret) goto err_stats_unregister; ret = c4_isp_raw_register(isp); if (ret) goto err_params_unregister; ret = c4_isp_imp_register(isp); if (ret) goto err_raw_unregister; ret = c4_ipp_caps_register(isp); if (ret) goto err_imp_unregister; ret = c4_isp_create_links(isp); if (ret) goto err_ipp_caps_unregister; return 0; err_ipp_caps_unregister: c4_ipp_caps_unregister(isp); err_imp_unregister: c4_isp_imp_unregister(isp); err_raw_unregister: c4_isp_raw_unregister(isp); err_params_unregister: c4_isp_params_unregister(isp); err_stats_unregister: c4_isp_stats_unregister(isp); err_captures_unregister: c4_isp_captures_unregister(isp); return ret; } static void c4_isp_videos_unregister(struct c4_isp_device *isp) { c4_isp_remove_links(isp); c4_ipp_caps_unregister(isp); c4_isp_imp_unregister(isp); c4_isp_raw_unregister(isp); c4_isp_params_unregister(isp); c4_isp_stats_unregister(isp); c4_isp_captures_unregister(isp); } static void __iomem *c4_isp_ioremap_byname(struct c4_isp_device *isp, char *name) { struct platform_device *pdev = to_platform_device(isp->hw_dev); struct resource *res; void __iomem *mem; res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name); if (!res) { dev_err(isp->dev, "Failed to get %s io resource\n", name); return NULL; } mem = devm_ioremap(isp->dev, res->start, resource_size(res)); if (!mem) { dev_err(isp->dev, "Failed to ioremap %s resource\n", name); return NULL; } return mem; } static int c4_isp_ioremap_resource(struct c4_isp_device *isp) { isp->base = c4_isp_ioremap_byname(isp, "base"); if (!isp->base) return -ENOMEM; isp->disp = c4_isp_ioremap_byname(isp, "disp"); if (!isp->disp) return -ENOMEM; return 0; } static int c4_isp_get_hw_dev(struct c4_isp_device *isp) { struct device_node *isp_node; struct platform_device *isp_pdev; isp_node = of_parse_phandle(isp->dev->of_node, "isp", 0); if (!isp_node) return -EINVAL; isp_pdev = of_find_device_by_node(isp_node); of_node_put(isp_node); if (!isp_pdev) { dev_err(isp->dev, "Failed to find isp pdev"); return -EINVAL; } isp->hw_dev = &isp_pdev->dev; return 0; } static int c4_isp_request_irq(struct c4_isp_device *isp) { struct platform_device *pdev = to_platform_device(isp->hw_dev); int irq; int ret; irq = platform_get_irq_byname(pdev, "fe_rst"); if (irq < 0) { dev_err(isp->dev, "Failed to get fe_rst irq"); return irq; } ret = devm_request_irq(isp->dev, irq, c4_isp_fe_rst_irq_handler, IRQF_SHARED, dev_driver_string(isp->dev), isp); if (ret) { dev_err(isp->dev, "Failed to request fe_rst irq\n"); return ret; } irq = platform_get_irq_byname(pdev, "fe_done"); if (irq < 0) { dev_err(isp->dev, "Failed to get fe_done irq"); return irq; } ret = devm_request_irq(isp->dev, irq, c4_isp_fe_done_irq_handler, IRQF_SHARED, dev_driver_string(isp->dev), isp); if (ret) { dev_err(isp->dev, "Failed to request fe_rst irq\n"); return ret; } irq = platform_get_irq_byname(pdev, "be_done"); if (irq < 0) { dev_err(isp->dev, "Failed to get be_done irq"); return irq; } ret = devm_request_irq(isp->dev, irq, c4_isp_be_done_irq_handler, IRQF_SHARED, dev_driver_string(isp->dev), isp); if (ret) { dev_err(isp->dev, "Failed to request be_done irq\n"); return ret; } irq = platform_get_irq_byname(pdev, "intf"); if (irq < 0) { dev_err(isp->dev, "Failed to get intf_top irq"); return irq; } ret = devm_request_irq(isp->dev, irq, c4_isp_intf_irq_handler, IRQF_SHARED, dev_driver_string(isp->dev), isp); if (ret) { dev_err(isp->dev, "Failed to request intf irq\n"); return ret; } return 0; } static int c4_isp_get_clocks(struct c4_isp_device *isp) { const struct c4_isp_match_info *info = isp->info; for (unsigned int i = 0; i < info->clock_num; i++) isp->clks[i].id = info->clocks[i]; return devm_clk_bulk_get(isp->dev, info->clock_num, isp->clks); } static int c4_isp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct c4_isp_device *isp; int ret; isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; isp->info = of_device_get_match_data(dev); isp->dev = dev; ret = c4_isp_get_hw_dev(isp); if (ret) return ret; isp->isp_pd = dev_pm_domain_attach_by_name(dev, "isp-pd"); if (IS_ERR_OR_NULL(isp->isp_pd)) { ret = IS_ERR(isp->isp_pd) ? PTR_ERR(isp->isp_pd) : -ENODEV; return dev_err_probe(dev, ret, "Failed to attach isp-pd\n"); } isp->opp_pd = dev_pm_domain_attach_by_name(dev, "opp-pd"); if (IS_ERR_OR_NULL(isp->opp_pd)) { ret = IS_ERR(isp->opp_pd) ? PTR_ERR(isp->opp_pd) : -ENODEV; return dev_err_probe(dev, ret, "Failed to attach opp-pd\n"); } ret = c4_isp_ioremap_resource(isp); if (ret) return ret; ret = c4_isp_request_irq(isp); if (ret) return ret; ret = c4_isp_get_clocks(isp); if (ret) return dev_err_probe(dev, ret, "Failed to get clocks\n"); platform_set_drvdata(pdev, isp); pm_runtime_enable(dev); ret = c4_isp_media_register(isp); if (ret) goto err_runtime_disable; ret = c4_ipp_register(isp); if (ret) goto err_media_unregister; ret = c4_isp_core_register(isp); if (ret) goto err_ipp_unregister; ret = c4_isp_resizers_register(isp); if (ret) goto err_core_unregister; ret = c4_isp_async_nf_register(isp); if (ret) goto err_resizers_unregister; ret = c4_isp_videos_register(isp); if (ret) goto err_nf_unregister; of_reserved_mem_device_init(isp->dev); c4_isp_cfg_plat_setting(isp); ret = c4_isp_rdma_alloc_buffers(isp); if (ret) goto err_videos_unregister; ret = c4_isp_proc_init(isp); if (ret) goto err_videos_unregister; dev_info(dev, "isp_probe okk rdma_alloc_en %d rdma_en %d\n", isp->rdma_alloc_en, isp->rdma_en); return 0; err_videos_unregister: c4_isp_videos_unregister(isp); err_nf_unregister: c4_isp_async_nf_unregister(isp); err_resizers_unregister: c4_isp_resizers_unregister(isp); err_core_unregister: c4_isp_core_unregister(isp); err_ipp_unregister: c4_ipp_unregister(isp); err_media_unregister: c4_isp_media_unregister(isp); err_runtime_disable: pm_runtime_disable(dev); return ret; }; static void c4_isp_remove(struct platform_device *pdev) { struct c4_isp_device *isp = platform_get_drvdata(pdev); c4_isp_proc_remove(isp); c4_isp_rdma_free_buffers(isp); of_reserved_mem_device_release(isp->dev); c4_isp_videos_unregister(isp); c4_isp_async_nf_unregister(isp); c4_isp_core_unregister(isp); c4_isp_resizers_unregister(isp); c4_ipp_unregister(isp); c4_isp_media_unregister(isp); pm_runtime_disable(isp->dev); dev_pm_domain_detach(isp->isp_pd, true); dev_pm_domain_detach(isp->opp_pd, true); }; static const struct c4_isp_match_info isp_info_0 = { .id = C4_ISP_VIRT_0, .clocks = {"dewarp", "isp"}, .clock_num = 2, }; static const struct c4_isp_match_info isp_info_1 = { .id = C4_ISP_VIRT_1, .clocks = {"dewarp", "isp"}, .clock_num = 2, }; static const struct c4_isp_match_info isp_info_2 = { .id = C4_ISP_VIRT_2, .clocks = {"dewarp", "isp"}, .clock_num = 2, }; static const struct c4_isp_match_info isp_info_3 = { .id = C4_ISP_VIRT_3, .clocks = {"dewarp", "isp"}, .clock_num = 2, }; static const struct of_device_id c4_isp_of_match[] = { { .compatible = "amlogic,isp-virt0", .data = &isp_info_0, }, { .compatible = "amlogic,isp-virt1", .data = &isp_info_1, }, { .compatible = "amlogic,isp-virt2", .data = &isp_info_2, }, { .compatible = "amlogic,isp-virt3", .data = &isp_info_3, }, { }, }; MODULE_DEVICE_TABLE(of, c4_isp_of_match); static struct platform_driver c4_isp_driver = { .probe = c4_isp_probe, .remove = c4_isp_remove, .driver = { .name = "c4-isp", .of_match_table = c4_isp_of_match, .pm = pm_ptr(&c4_isp_pm_ops), }, }; module_platform_driver(c4_isp_driver); MODULE_AUTHOR("Keke Li "); MODULE_DESCRIPTION("Amlogic C4 ISP5 pipeline"); MODULE_LICENSE("GPL");