这个ipu_common.c函数提供ipu底层函数调用的一些关系和函数。
(一)分析这个文件从init函数入口,发现有这个subsys_initcall,说明ipu是作为一个子系统注册到内核中的:
int32_t __init ipu_gen_init(void)
{
int32_t ret;
ret = platform_driver_register(&mxcipu_driver);
return 0;
}
subsys_initcall(ipu_gen_init);
(二)这个mxcipu_driver结构体注册到平台以后,如果有匹配的设备的话,就会调用其中的probe函
static int ipu_probe(struct platform_device *pdev)
{
struct ipu_soc *ipu;
struct resource *res;
unsigned long ipu_base;
const struct of_device_id *of_id =
of_match_device(imx_ipuv3_dt_ids, &pdev->dev);
const struct ipu_platform_type *iputype = of_id->data;
const struct ipu_devtype *devtype = &iputype->devtype;
int ret = 0, id;
u32 bypass_reset, reg;
/*以imx6qp为例(后面本文件中如果有涉及到板子的一些资源,都以imx6qp为例),
*iputype= of_id->data,这个of_id返回的是imx_ipuv3_dt_ids[]数组中的某一项,
static const struct of_device_id imx_ipuv3_dt_ids[] = {
{ .compatible = "fsl,imx51-ipu", .data = &ipu_type_imx51, },
{ .compatible = "fsl,imx53-ipu", .data = &ipu_type_imx53, },
{ .compatible = "fsl,imx6q-ipu", .data = &ipu_type_imx6q, },
{ .compatible = "fsl,imx6qp-ipu", .data = &ipu_type_imx6qp, },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_ipuv3_dt_ids);
在本文件中就是{.compatible
= "fsl,imx6qp-ipu", .data = &ipu_type_imx6qp,}这一项。所以:
*iputype= &ipu_type_imx6qp,
*devtype=
下面标红的部分:
static struct ipu_platform_type ipu_type_imx6qp = {
.devtype = {
.name = "IPUv3H",
.cm_ofs = 0x00200000,
.idmac_ofs = 0x00208000,
.ic_ofs = 0x00220000,
.csi0_ofs = 0x00230000,
.csi1_ofs = 0x00238000,
.di0_ofs = 0x00240000,
.di1_ofs = 0x00248000,
.smfc_ofs = 0x00250000,
.dc_ofs = 0x00258000,
.dmfc_ofs = 0x00260000,
.vdi_ofs = 0x00268000,
.cpmem_ofs = 0x00300000,
.srm_ofs = 0x00340000,
.tpm_ofs = 0x00360000,
.dc_tmpl_ofs = 0x00380000,
.type = IPUv3H,
.idmac_used_bufs_present = true,
},
.ch0123_axi = 0,
.ch23_axi = 0,
.ch27_axi = 2,
.ch28_axi = 3,
.normal_axi = 1,
.idmac_used_bufs_en_r = true,
.idmac_used_bufs_en_w = true,
.idmac_used_bufs_max_r = 0x3,
.idmac_used_bufs_max_w = 0x3,
.smfc_idmac_12bit_3planar_bs_fixup = true,
};
*/
dev_dbg(&pdev->dev, "<%s>
", __func__);
ret = of_property_read_u32(pdev->dev.of_node,
"bypass_reset", &bypass_reset);
if (ret < 0) {
dev_dbg(&pdev->dev, "can not get bypass_reset
");
return ret;
}
/*从pdev->dev.of_node这个devicenode结构体里面读取“bypass_reset”这一项,将读出的结果存在&bypass_reset中。*/
id = of_alias_get_id(pdev->dev.of_node, "ipu");
if (id < 0) {
dev_dbg(&pdev->dev, "can not get alias id
");
return id;
}
/*这个of_alias_get_id函数的大致意思是根据名字“ipu”获取到它的id,将这个id返回。但是有两个ipu呢,这个怎么选择??如果根据名字的话,怎么区分这两个ipu?*/
ipu = &ipu_array[id];
memset(ipu, 0, sizeof(struct ipu_soc));
ipu->bypass_reset = (bool)bypass_reset;
ipu->dev = &pdev->dev;
ipu->id = id;
ipu->devtype = devtype->type;
ipu->ch0123_axi = iputype->ch0123_axi;
ipu->ch23_axi = iputype->ch23_axi;
ipu->ch27_axi = iputype->ch27_axi;
ipu->ch28_axi = iputype->ch28_axi;
ipu->normal_axi = iputype->normal_axi;
ipu->smfc_idmac_12bit_3planar_bs_fixup =
iputype->smfc_idmac_12bit_3planar_bs_fixup;
spin_lock_init(&ipu->int_reg_spin_lock);
spin_lock_init(&ipu->rdy_reg_spin_lock);
mutex_init(&ipu->mutex_lock);
/*这一些就是根据上面获取到的信息来填充这个structipu_soc
*ipu结构体。*/
dev_dbg(&pdev->dev, "revision is %s
", devtype->name);
ipu->irq_sync = platform_get_irq(pdev, 0);
ipu->irq_err = platform_get_irq(pdev, 1);
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res || ipu->irq_sync < 0 || ipu->irq_err < 0) {
dev_err(&pdev->dev, "can't get device resources
");
return -ENODEV;
}
/*获取irq资源和内存资源。*/
if (!devm_request_mem_region(&pdev->dev, res->start,
resource_size(res), pdev->name))
return -EBUSY;
/*申请I/O内存资源,申请后还需要通过ioremap等函数映射后才能够使用。*/
ret = devm_request_irq(&pdev->dev, ipu->irq_sync,
ipu_sync_irq_handler, 0, pdev->name, ipu);
if (ret) {
dev_err(ipu->dev, "request SYNC interrupt failed
");
return ret;
}
ret = devm_request_irq(&pdev->dev, ipu->irq_err,
ipu_err_irq_handler, 0, pdev->name, ipu);
if (ret) {
dev_err(ipu->dev, "request ERR interrupt failed
");
return ret;
}
/*为上面获取到的irq资源注册中断服务函数。*/
ipu_base = res->start; //ipu地址的初始值。
ipu->cm_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->cm_ofs, PAGE_SIZE);
ipu->ic_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->ic_ofs, PAGE_SIZE);
ipu->idmac_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->idmac_ofs, PAGE_SIZE);
/* DP Registers are accessed thru the SRM */
ipu->dp_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->srm_ofs, PAGE_SIZE);
ipu->dc_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->dc_ofs, PAGE_SIZE);
ipu->dmfc_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->dmfc_ofs, PAGE_SIZE);
ipu->di_reg[0] = devm_ioremap(&pdev->dev,
ipu_base + devtype->di0_ofs, PAGE_SIZE);
ipu->di_reg[1] = devm_ioremap(&pdev->dev,
ipu_base + devtype->di1_ofs, PAGE_SIZE);
ipu->smfc_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->smfc_ofs, PAGE_SIZE);
ipu->csi_reg[0] = devm_ioremap(&pdev->dev,
ipu_base + devtype->csi0_ofs, PAGE_SIZE);
ipu->csi_reg[1] = devm_ioremap(&pdev->dev,
ipu_base + devtype->csi1_ofs, PAGE_SIZE);
ipu->cpmem_base = devm_ioremap(&pdev->dev,
ipu_base + devtype->cpmem_ofs, SZ_128K);
ipu->tpmem_base = devm_ioremap(&pdev->dev,
ipu_base + devtype->tpm_ofs, SZ_64K);
ipu->dc_tmpl_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->dc_tmpl_ofs, SZ_128K);
ipu->vdi_reg = devm_ioremap(&pdev->dev,
ipu_base + devtype->vdi_ofs, PAGE_SIZE);
if (!ipu->cm_reg || !ipu->ic_reg || !ipu->idmac_reg ||
!ipu->dp_reg || !ipu->dc_reg || !ipu->dmfc_reg ||
!ipu->di_reg[0] || !ipu->di_reg[1] || !ipu->smfc_reg ||
!ipu->csi_reg[0] || !ipu->csi_reg[1] || !ipu->cpmem_base ||
!ipu->tpmem_base || !ipu->dc_tmpl_reg || !ipu->vdi_reg)
return -ENOMEM;
dev_dbg(ipu->dev, "IPU CM Regs = %p
", ipu->cm_reg);
dev_dbg(ipu->dev, "IPU IC Regs = %p
", ipu->ic_reg);
dev_dbg(ipu->dev, "IPU IDMAC Regs = %p
", ipu->idmac_reg);
dev_dbg(ipu->dev, "IPU DP Regs = %p
", ipu->dp_reg);
dev_dbg(ipu->dev, "IPU DC Regs = %p
", ipu->dc_reg);
dev_dbg(ipu->dev, "IPU DMFC Regs = %p
", ipu->dmfc_reg);
dev_dbg(ipu->dev, "IPU DI0 Regs = %p
", ipu->di_reg[0]);
dev_dbg(ipu->dev, "IPU DI1 Regs = %p
", ipu->di_reg[1]);
dev_dbg(ipu->dev, "IPU SMFC Regs = %p
", ipu->smfc_reg);
dev_dbg(ipu->dev, "IPU CSI0 Regs = %p
", ipu->csi_reg[0]);
dev_dbg(ipu->dev, "IPU CSI1 Regs = %p
", ipu->csi_reg[1]);
dev_dbg(ipu->dev, "IPU CPMem = %p
", ipu->cpmem_base);
dev_dbg(ipu->dev, "IPU TPMem = %p
", ipu->tpmem_base);
dev_dbg(ipu->dev, "IPU DC Template Mem = %p
", ipu->dc_tmpl_reg);
dev_dbg(ipu->dev, "IPU VDI Regs = %p
", ipu->vdi_reg);
/*根据获取到的资源来为每个寄存器映射内存空间。*/
ipu->ipu_clk = devm_clk_get(ipu->dev, "bus");
if (IS_ERR(ipu->ipu_clk)) {
dev_err(ipu->dev, "clk_get ipu failed");
return PTR_ERR(ipu->ipu_clk);
}
/*获取"bus"的时钟。*/
/* ipu_clk is always prepared */
ret = clk_prepare_enable(ipu->ipu_clk);
if (ret < 0) {
dev_err(ipu->dev, "ipu clk enable failed
");
return ret;
}
/*这个函数是clk_prepare和clk_enable两个函数的集合,其中clk_prepare函数是个预定义的函数,需要定义CONFIG_HAVE_CLK_PREPARE这个宏,然后就是调用clk_enable函数来使能时钟。*/
ipu->prg_clk = devm_clk_get(ipu->dev, "prg");
if (IS_ERR(ipu->prg_clk))
ipu->prg_clk = NULL;
/*获取"prg"的时钟。*/
ipu->online = true;
/*这个online是一个bool类型的变量,表示当前这个ipu是否正在使用中。*/
platform_set_drvdata(pdev, ipu);
/*设置私有数据。*/
/*下面这个bypass_reset参数在前面通过of_property_read_u32函数获得了,从dts文件中可以看到它等于0.同时在structipu_soc中关于这个bypass_reset有这样的注释:Bypassreset
to avoid display channel being stopped by probe since it maystarts to work inbootloader.这个值是从dts文件中获得的bypass_reset,因为在开发板启动过程中,需要使能屏幕来显示,所以显示通道可能已将在bootloader中开启了,在这里设置这个值,使得显示通道在probe函数中不会关闭,也就是这个参数的含义(旁路)。*/
if (!bypass_reset) {
ret = device_reset(&pdev->dev);
if (ret) {
dev_err(&pdev->dev, "failed to reset: %d
", ret);
return ret;
}
ipu_mem_reset(ipu);
ipu_disp_init(ipu);
/* Set MCU_T to divide MCU access window into 2 */
ipu_cm_write(ipu, 0x00400000L | (IPU_MCU_T_DEFAULT << 18),
IPU_DISP_GEN);
}
/*这个ipu_mem_reset函数同样在这个文件中,如下所示:
static int ipu_mem_reset(struct ipu_soc *ipu)
{
int timeout = 1000;
ipu_cm_write(ipu, 0x807FFFFF, IPU_MEM_RST);
while (ipu_cm_read(ipu, IPU_MEM_RST) & 0x80000000) {
if (!timeout--)
return -ETIME;
msleep(1);
}
return 0;
}
这个ipu_disp_init函数同样在ipu_common.c文件中,如下所示:
void ipu_disp_init(struct ipu_soc *ipu)
{
ipu->fg_csc_type = ipu->bg_csc_type = CSC_NONE;
ipu->color_key_4rgb = true;
_ipu_init_dc_mappings(ipu);
_ipu_dmfc_init(ipu, DMFC_NORMAL, 1);
}
最后通过ipu_cm_write函数来设置有关显示的一些寄存器。
*/
/* setup ipu clk tree after ipu reset */
ret = ipu_clk_setup_enable(ipu);
if (ret < 0) {
dev_err(ipu->dev, "ipu clk setup failed
");
ipu->online = false;
return ret;
}
/*然后调用ipu_clk_setup_enable函数设置时钟树,这个函数也在这个ipu_common.c文件中,在后面分析。*/
if (devtype->idmac_used_bufs_present) {
/* devtype->idmac_used_bufs_present = true。 */
reg = ipu_idmac_read(ipu, IDMAC_CONF);
if (iputype->idmac_used_bufs_en_r) //idmac_used_bufs_en_r = true
reg |= IDMAC_CONF_USED_BUFS_EN_R;
else
reg &= ~IDMAC_CONF_USED_BUFS_EN_R;
if (iputype->idmac_used_bufs_en_w) //idmac_used_bufs_en_w = true
reg |= IDMAC_CONF_USED_BUFS_EN_W;
else
reg &= ~IDMAC_CONF_USED_BUFS_EN_W;
reg &= ~IDMAC_CONF_USED_BUFS_MAX_R_MASK;
reg |= (iputype->idmac_used_bufs_max_r <<
IDMAC_CONF_USED_BUFS_MAX_R_OFFSET);
reg &= ~IDMAC_CONF_USED_BUFS_MAX_W_MASK;
reg |= (iputype->idmac_used_bufs_max_w <<
IDMAC_CONF_USED_BUFS_MAX_W_OFFSET);
/* idmac_used_bufs_max_r = 0x3,
idmac_used_bufs_max_w = 0x3。 */
ipu_idmac_write(ipu, reg, IDMAC_CONF);
}
/*上面这段代码先通过ipu_idmac_read函数来读取IDMAC_CONF寄存器的值,然后根据iputype的一些信息设置它们在寄存器中对应的值,最后将新值通过ipu_idmac_write函数重新写入寄存器中。这个函数用来配置IDMAC。*/
/* Set sync refresh channels and CSI->mem channel as high priority */
ipu_idmac_write(ipu, 0x18800003L, IDMAC_CHA_PRI(0));
/*通过设置IDMAC_CHA_PRI(0)寄存器,将syncrefresh
channels 和CSI->memchannel的设为高优先级*/
/* Enable error interrupts by default */
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(5));
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(6));
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(9));
ipu_cm_write(ipu, 0xFFFFFFFF, IPU_INT_CTRL(10));
/*将IPU_INT_CTRL(5),IPU_INT_CTRL(6),IPU_INT_CTRL(9),IPU_INT_CTRL(10)这几个寄存器设置为默认值0xFFFFFFFF,这几个寄存器应该是错误中断使能的寄存器。*/
if (!bypass_reset)
clk_disable(ipu->ipu_clk);
/*同样是这个bypass_reset参数,暂时不知道它是多少。*/
register_ipu_device(ipu, id);
/*注册ipu_device设备,这个函数在ipu_device.c文件中*/
pm_runtime_enable(&pdev->dev);
/*使能设备的电源管理*/
return ret;
}