Files
BPI-CM9-Linux/aml-comp/hardware/amlogic/arm-isp-6.12/aml_isp5/driver/isp5/c4-isp-dev.c
T

1262 lines
30 KiB
C

// SPDX-License-Identifier: (GPL-2.0-only OR MIT)
/*
* Copyright (C) 2025 Amlogic, Inc. All rights reserved
*/
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/version.h>
#include <linux/pm_domain.h>
#include <linux/media-bus-format.h>
#include <media/v4l2-common.h>
#include <media/v4l2-device.h>
#include <media/v4l2-subdev.h>
#include <media/v4l2-fwnode.h>
#include <media/v4l2-mc.h>
#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 <keke.li@amlogic.com>");
MODULE_DESCRIPTION("Amlogic C4 ISP5 pipeline");
MODULE_LICENSE("GPL");