【RK3399】【Android7.1】camsys 驱动加载流程分析

驱动的加载过程

(1) camsys driver入口函数

​根据 ./kernel/drivers/media/video/rk_camsys 目录下的Makefile可以了解到,该目录源码最终会生成camsys_drv.o模块,设备端加载camsys_drv.o这个模块时,会调用这个模块中module_init注册的入口函数camsys_platform_init,具体代码如下:

module_init(camsys_platform_init);

static int __init camsys_platform_init(void)
{
	printk("CamSys driver version: v%d.%d.%d, "
		"CamSys head file version: v%d.%d.%d\n",
		(CAMSYS_DRIVER_VERSION&0xff0000) >> 16,
		(CAMSYS_DRIVER_VERSION&0xff00) >> 8,
		CAMSYS_DRIVER_VERSION&0xff,
		(CAMSYS_HEAD_VERSION&0xff0000) >> 16,
		(CAMSYS_HEAD_VERSION&0xff00) >> 8,
		CAMSYS_HEAD_VERSION&0xff);

	spin_lock_init(&camsys_devs.lock);
	INIT_LIST_HEAD(&camsys_devs.devs);
	platform_driver_register(&camsys_platform_driver);

	return 0;
}

​由上述代码我们可以知道在camsys_platform_init中实现了自旋锁的初始化,链表头的初始化,以及平台设备驱动的注册等。

(2) platform_driver结构体定义

camsys_platform_driver结构体的定义如下:

static struct platform_driver camsys_platform_driver = {
	.driver = {
		.name = CAMSYS_PLATFORM_DRV_NAME,
		.of_match_table = of_match_ptr(cif_of_match),
	},
	.probe = camsys_platform_probe,
	.remove = (camsys_platform_remove),
};

有上述代码可知,camsys_drv.o模块加载的时候,主要是注册了一个platform_driver结构体。在Linux内核中,platform总线在加载设备或者驱动的时候,都会有一个探测过程,探测是否有匹配的驱动或者设备,匹配成功则会执行其中的 .probe 函数,其中会优先通过 .of_match_table 匹配,如果匹配不成功,再使用 .name 进行匹配。

需要的 .name 定义如下:

#define CAMSYS_PLATFORM_DRV_NAME                "RockChip-CamSys"

需要的 .of_match_table 定义如下:

static const struct of_device_id cif_of_match[] = {
	{ .compatible = "rockchip,isp" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, cif_of_match);

​ 有上述定义可知,驱动中的 .of_match_table 需要和DTS里面的**.compatible = “rockchip,isp” 进行匹配,匹配成功了会调用platform_driver中的probe**函数。

(3) DTS设置

相应的dts文件存放路径:

./kernel/arch/arm64/boot/dts/rockchip/rk3399-android.dtsi

dts文件里的设置如下:

isp0: isp@ff910000 {
    compatible = "rockchip,rk3399-isp", "rockchip,isp";
    ......
    status = "disabled";
};

isp0: isp@ff920000 {
    compatible = "rockchip,rk3399-isp", "rockchip,isp";
    ......
    status = "disabled";
};

如果需要 .of_match_table 能和DTS匹配上,需要将status的状态从disabled改成okay。即:

isp0: isp@ff910000 {
    compatible = "rockchip,rk3399-isp", "rockchip,isp";
    ......
    status = "okay";
};

isp0: isp@ff920000 {
    compatible = "rockchip,rk3399-isp", "rockchip,isp";
    ......
    status = "okay";
};

(4) 执行probe函数

​ platform设备与驱动一旦匹配成功,将会执行驱动的probe函数,camsys驱动的probe函数定义如下,省略掉了部分非核心的代码:

static int camsys_platform_probe(struct platform_device *pdev)
{
    ......
    
    /* 获取dts文件中的compatible的节点并打印出来 */
	err = of_property_read_string(dev->of_node, "compatible", &compatible);
	if (err < 0) {
		camsys_err("get compatible failed!");
	} else {
         /* compatible = “rockchip,rk3399-isp” */
		camsys_trace(1, "compatible is %s\n", compatible);
	}
    
    /* 通过节点中包含的IC类型来判断具体是哪款IC */
	if (strstr(compatible, "rk3368"))
		CHIP_TYPE = 3368;
	else if (strstr(compatible, "rk3288"))
		CHIP_TYPE = 3288;
	else if (strstr(compatible, "rk3366"))
		CHIP_TYPE = 3366;
	else if (strstr(compatible, "rk3399"))
		CHIP_TYPE = 3399;

    /* 配置选择的IC,即填充camsys_soc_priv_s结构体的.soc_cfg函数 */
	camsys_soc_init(CHIP_TYPE);

    /*********************************************************
     * 根据设备节点dev的reg属性值,填充资源结构体r。第二个参数指明了使用
     * reg属性中第几个属性值,一般设置为0,表示第一个。 
     *********************************************************/
	err = of_address_to_resource(dev->of_node, 0, &register_res);

	/* map irqs,从设备节点dev中读取第0个irq号 */
	irq_id = irq_of_parse_and_map(dev->of_node, 0);

    /**********************************************************
     * 为camsys_dev结构体分配内存并初始化,使用devm_kzalloc分配的内存在
     * 注销这个驱动时会自动释放为camsys_dev结构体分配的内存。
     **********************************************************/
	camsys_dev = (camsys_dev_t *)devm_kzalloc(&pdev->dev,
		sizeof(camsys_dev_t),
		GFP_KERNEL);

	/* spin_lock_init(&camsys_dev->lock); */
    /* 初始化互斥锁 */
	mutex_init(&camsys_dev->extdevs.mut);

	INIT_LIST_HEAD(&camsys_dev->extdevs.list);
	INIT_LIST_HEAD(&camsys_dev->extdevs.active);
	INIT_LIST_HEAD(&camsys_dev->list);

	/* IRQ init,将上面获取的irq_id保存在结构体irq的成员变量irq_id中 */
	camsys_dev->irq.irq_id = irq_id;
    
    /* 初始化自旋锁用于中断 */
	spin_lock_init(&camsys_dev->irq.lock);
    
    /* 初始化链表头 */
	INIT_LIST_HEAD(&camsys_dev->irq.irq_pool);
	INIT_LIST_HEAD(&camsys_dev->devmems.memslist);

	/* get soc operation */
    /***********************************************************
     * camsys_soc_get中的操作比较简单,就是如果判断全局变量camsys_soc_p
     * 不为空则返回全局变量camsys_soc_p。这个全局变量camsys_soc_p在上面
     * 的函数camsys_soc_init()中分配并初始化。
     ***********************************************************/
	camsys_dev->soc = (void *)camsys_soc_get();

	/* Register mem init */
    /* 为camsys_meminfo_t结构体分配内存并初始化,该结构体主要用于设备内存地址映射 */
	meminfo = kzalloc(sizeof(camsys_meminfo_t), GFP_KERNEL);

    /* 通过平台资源register_res映射出内存虚拟地址 */
	meminfo->vir_base =
		(unsigned long)devm_ioremap_resource(dev, &register_res);
	/* 将上面获取的vir_base保存在结构体camsys_dev的成员变量rk_isp_base中 */
	camsys_dev->rk_isp_base = meminfo->vir_base;

    /* CAMSYS_REGISTER_MEM_NAME是宏,对应字符串"CamSys_RegMem" */
	strlcpy(meminfo->name, CAMSYS_REGISTER_MEM_NAME, sizeof(meminfo->name));
    
    /* 获取meminfo的物理地址以及长度 */
	meminfo->phy_base = register_res.start;
	meminfo->size = register_res.end - register_res.start + 1;
    
    /* 将链表meminfo->list插入camsys_dev->devmems.memslist链表头之后 */
	list_add_tail(&meminfo->list, &camsys_dev->devmems.memslist);

	/* I2c mem init */
    /******************************************************************
     * __get_free_page()分配连续的物理地址,用于整页分配。它返回值是一个虚拟地址。
     * 它们与真实的物理地址只有一个固定的偏移,因此存在较简单的转换关系。
     ******************************************************************/
	i2cmem = __get_free_page(GFP_KERNEL);

    /****************************************************************
     * 随着linux的长时间运行,空闲页面会越来越少,为了防止linux内核进入请求页面
     * 的僵局中,Linux内核采用页面回收算法(PFRA)从用户进程和内核高速缓存中回收
     * 内存页框,并根据需要把要回收页框的内容交换到磁盘上的交换区。
     * 调用SetPageReserved函数可以使页面不被交换。
     ****************************************************************/
	SetPageReserved(virt_to_page(i2cmem));

    /* 再次为camsys_meminfo_t结构体分配内存并初始化,该结构体主要用于I2C内存地址映射 */
	meminfo = kzalloc(sizeof(camsys_meminfo_t), GFP_KERNEL);

   /* CAMSYS_I2C_MEM_NAME是宏,对应字符串"CamSys_I2cMem" */
	strlcpy(meminfo->name, CAMSYS_I2C_MEM_NAME, sizeof(meminfo->name));
	meminfo->vir_base = i2cmem;
    /**************************************************************
     * __get_free_page()分配的物理地址是以 PAGE_OFFSET(3G) 为起点进行计算,
     * 所以__get_free_page() 所分配的物理页面被映射到了 PAGE_OFFSET 开始的
     * 虚拟地址中,所以其返回值实际是虚拟地址,需要通过virt_to_phys()转换为
     * 物理地址,通过virt_to_phys函数让虚拟地址减去3G内存即得到物理地址。 
     **************************************************************/
	meminfo->phy_base = virt_to_phys((void *)i2cmem);
    /* 因为分配的内存大小为一页,所以这里meminfo->size的长度为一页 */
	meminfo->size = PAGE_SIZE;
    
    /* 将新的链表meminfo->list插入camsys_dev->devmems.memslist链表头之后 */
	list_add_tail(&meminfo->list, &camsys_dev->devmems.memslist);

    /* 不了解这段代码块有什么意义 */ 
	{
		unsigned int *tmpp;

		tmpp = (unsigned int *)meminfo->vir_base;
		*tmpp = 0xfa561243;
	}

	/* Special init */
	{
		if (camsys_mipiphy_probe_cb(pdev, camsys_dev) < 0) {
			camsys_err("Mipi phy probe failed!");
		}
	}

    /* 因为使用的是mipi接口,这段条件编译并没有起到作用 */
	#if (defined(CONFIG_CAMSYS_MRV))
	camsys_mrv_probe_cb(pdev, camsys_dev);
	#elif (defined(CONFIG_CAMSYS_CIF))
	camsys_cif_probe_cb(pdev, camsys_dev);
	#else
	camsys_err("camsys driver haven't been complie!!!");
	#endif

	camsys_trace(1,"%s memory:", dev_name(&pdev->dev));
    
    /* 遍历camsys_dev->devmems.memslist,对meminfo类型的数据进行处理 */
	list_for_each_entry(meminfo, &camsys_dev->devmems.memslist, list) {
		if (strcmp(meminfo->name, CAMSYS_I2C_MEM_NAME) == 0) {
			camsys_dev->devmems.i2cmem = meminfo;
			camsys_trace(1,
				"	I2c memory (phy: 0x%lx vir: 0x%lx size: 0x%x)",
				meminfo->phy_base,
				meminfo->vir_base,
				meminfo->size);
		}
		if (strcmp(meminfo->name, CAMSYS_REGISTER_MEM_NAME) == 0) {
			camsys_dev->devmems.registermem = meminfo;
			camsys_trace(1,
				"	Register memory (phy: 0x%lx vir: 0x%lx size: 0x%x)",
				meminfo->phy_base,
				meminfo->vir_base,
				meminfo->size);
		}
	}

	camsys_dev->phy_cb = camsys_phy_ops;
	camsys_dev->pdev	=   pdev;

    /******************************************************
     * 把camsys_dev赋值给pdev->driver_data,pdev是平台总线设备,
     * 对于整个驱动是可见的,所以可以通过platform_get_drvdata来
     * 获取data。 
     *******************************************************/
	platform_set_drvdata(pdev, (void *)camsys_dev);
    
	/* Camsys_devs list add */
	spin_lock(&camsys_devs.lock);
	list_add_tail(&camsys_dev->list, &camsys_devs.devs);
	spin_unlock(&camsys_devs.lock);

    /* 该函数实际上只是初始化了g_ext_fsh_devs.dev_list链表头 */
	camsys_init_ext_fsh_module();
	camsys_trace(1, "Probe %s device success ", dev_name(&pdev->dev));
	return 0;

}

​在这个probe函数中通过DTS获取了一些平台资源,比如确定IC类型(RK3399),地址以及中断irq;分配了一个camsys_dev结构体,其对应着一个具体的摄像头设备,对其进行设置;初始化需要用到的自旋锁和互斥锁。分配了两个meminfo内存并获取它们的物理地址与虚拟地址以及长度,最后将设置好的camsys_dev保存在pdev使其全局可见,因为之后我们需要操作到这个结构体。

(5) probe函数中重要的函数解析

​ 在probe函数中有一个比较重要的函数camsys_mipiphy_probe_cb,其定义如下:

int camsys_mipiphy_probe_cb(
struct platform_device *pdev, camsys_dev_t *camsys_dev)
{
	......

    /* 从dts文件中读取32位的属性值,并保存在mipiphy_cnt变量中 */
	err = of_property_read_u32(dev->of_node,
		"rockchip,isp,mipiphy", &mipiphy_cnt);
	if (err < 0) {
		camsys_err("get property(rockchip,isp,mipiphy) failed!");
		goto fail;
	} else {
		camsys_trace(2, "%s have %d mipi phy\n",
			dev_name(&pdev->dev), mipiphy_cnt);
	}

    /* 给mipiphy分配内存并初始化 */
	mipiphy = kzalloc(sizeof(camsys_phyinfo_t)*mipiphy_cnt, GFP_KERNEL);

	camsys_dev->mipiphy = mipiphy;

    /* str是一个具有31个元素的数组,在这里将每个元素置0 */
	memset(str, 0x00, sizeof(str));
    
	for (i = 0; i < mipiphy_cnt; i++) {
		meminfo = NULL;
        
        /* 将"rockchip,isp,mipiphy%d,reg"保存到str变量中 */
		sprintf(str, "rockchip,isp,mipiphy%d,reg", i);
        
        /***********************************************
         * 从设备结点dev->of_node中读取2个属性名为str,
         * 类型为32位整型数组的属性值,即物理地址并放入phyreg。
         ***********************************************/
		if (of_property_read_u32_array(
				dev->of_node, str, phyreg, 2) == 0) {
            /* 给meminfo分配内存并初始化 */
			meminfo = kzalloc(sizeof(camsys_meminfo_t), GFP_KERNEL);
			if (meminfo != NULL) {
                /****************************************************************** 
                 * ioremap函数返回映射后的内核虚拟地址(3G-4G)并保存在meminfo->vir_base中,
                 * 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源。
                 ******************************************************************/
				meminfo->vir_base = (unsigned long)ioremap(phyreg[0], phyreg[1]);
                
				if (meminfo->vir_base) {
                    /* 将分配的meminfo命名为"CamSys_RegMem_MipiPhy" */
					strlcpy(meminfo->name,
                            CAMSYS_MIPIPHY_MEM_NAME, sizeof(meminfo->name));
                    /* 保存物理基地址 */
					meminfo->phy_base = phyreg[0];
                    /* 保存地址长度 */
					meminfo->size = phyreg[1];
				}
                /* 将分配的meminfo保存在camsys_dev的成员变量中 */
				camsys_dev->mipiphy[i].reg = meminfo;
			}
		}

		sprintf(str, "hclk_mipiphy%d", i);

        /* 通过mipiphy的时钟 */
		clk = devm_clk_get(&pdev->dev, str);
		if (!IS_ERR_OR_NULL(clk)) {
			phyclk = kzalloc(sizeof(camsys_mipiphy_clk_t), GFP_KERNEL);
			if (phyclk == NULL) {
				camsys_err("malloc camsys_mipiphy_clk_t for %s failed!",str);
			} else {
				phyclk->hclk = clk;
			}

			camsys_dev->mipiphy[i].clk = (void *)phyclk;
		}

        /* 对mipiphy的一些设置 */
		camsys_dev->mipiphy[i].phycnt = mipiphy_cnt;
		camsys_dev->mipiphy[i].clkin_cb = camsys_mipiphy_clkin_cb;
		camsys_dev->mipiphy[i].ops = camsys_mipiphy_ops;
		camsys_dev->mipiphy[i].remove = camsys_mipiphy_remove_cb;
	}

	if (CHIP_TYPE == 3368 || CHIP_TYPE == 3366 ||
	    CHIP_TYPE == 3399 || CHIP_TYPE == 3288) {

		if (CHIP_TYPE == 3399) {
            /* 给camsys_dev->dsiphy_reg分配内存并初始化 */
			camsys_dev->dsiphy_reg =
				kzalloc(sizeof(camsys_meminfo_t), GFP_KERNEL);
            
			if (of_property_read_u32_array(
					dev->of_node,"rockchip,isp,dsiphy,reg", phyreg, 2) == 0) {
                
                /****************************************************************** 
                 * ioremap函数返回映射后的内核虚拟地址(3G-4G)并保存在meminfo->vir_base中,
                 * 接着便可以通过读写该返回的内核虚拟地址去访问之这段I/O内存资源。
                 ******************************************************************/
				camsys_dev->dsiphy_reg->vir_base =
					(unsigned long)ioremap(phyreg[0], phyreg[1]);

                /* 将分配的camsys_dev->dsiphy_reg命名为"Dsi-DPHY" */
				strlcpy(camsys_dev->dsiphy_reg->name,
					"Dsi-DPHY", sizeof(camsys_dev->dsiphy_reg->name));
                /* 在名为"Dsi-DPHY"的内存中保存dsiphy的物理地址 */
				camsys_dev->dsiphy_reg->phy_base = phyreg[0];
                /* 在名为"Dsi-DPHY"的内存中保存dsiphy的长度 */
				camsys_dev->dsiphy_reg->size = phyreg[1];
			}
		} else { /* CHIP_TYPE != 3399 才会执行这部分代码 */
			camsys_dev->csiphy_reg = kzalloc(sizeof(camsys_meminfo_t), GFP_KERNEL);


			if (of_property_read_u32_array(
					dev->of_node,"rockchip,isp,csiphy,reg", phyreg, 2) == 0) {
				camsys_dev->csiphy_reg->vir_base =
                    (unsigned long)ioremap(phyreg[0], phyreg[1]);

				strlcpy(camsys_dev->csiphy_reg->name,
					"Csi-DPHY",sizeof(camsys_dev->csiphy_reg->name));
                
				camsys_dev->csiphy_reg->phy_base = phyreg[0];
				camsys_dev->csiphy_reg->size = phyreg[1];
			}
		}

    	/* 通过dts文件获取cru和grf的物理地址,再通过of_iomap进行映射 */
		/* get cru base */
		node = of_parse_phandle(dev->of_node, "rockchip,cru", 0);
		camsys_dev->rk_cru_base = (unsigned long)of_iomap(node, 0);
		camsys_trace(2, "rk_cru_base=0x%lx", camsys_dev->rk_cru_base);
		/* get grf base */
		node = of_parse_phandle(dev->of_node, "rockchip,grf", 0);
		camsys_dev->rk_grf_base = (unsigned long)of_iomap(node, 0);
		camsys_trace(2, "rk_grf_base=0x%lx", camsys_dev->rk_grf_base);
	}

	return 0;
}

​ 从上面的代码可以看出camsys_mipiphy_probe_cb这个函数的作用主要是分配设置mipiphy这个结构体,包括设置其时钟,物理地址,虚拟地址,长度等。

发布了23 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/karaskass/article/details/102700333