• 【分析笔记】全志 i2c-sunxi.c 控制器驱动分析


    分析平台:全志 A64
    内核版本:Linux 4.9
    数据手册Allwinner_A64_User_Manual_V1.1.pdf (whycan.com)

    驱动框架

    在这里插入图片描述

    I2C 设备驱动

    作为方案应用来说,我们是最经常要动的地方,这一层主要与具体的芯片功能强关联,不同的芯片具有不同的使用方法,如触摸屏设备驱动。

    核心框架层

    Linux 提供的硬件抽象层,起到承上启下的作用,对上提供注册设备驱动的统一接口,对下提供硬件控制器接入统一接口,负责维护众多的设备驱动和适配器驱动。

    适配器层

    由 Soc 芯片原厂提供,通常 Soc 支持多少路 I2C 总线,就会有多少个硬件控制器,这些硬件控制器才是真正实现与外设芯片通信的地方。我们也可以通过 GPIO 模拟 I2C 时序来实现一个硬件适配器,对于设备驱动来说,它不需要关心 Soc 是通过何种方式产生通信时序来跟外设芯片通信的。

    本文主要分析位于适配器层的全志 i2c-sunxi.c 硬件控制器驱动程序,目的在于了解 I2C 适配器驱动的使用方法。

    代码分析

    一、平台设备驱动

    I2C 控制器驱动是通过平台驱动的方式注册到系统中:

    lichee\linux-4.9\drivers\i2c\busses\i2c-sunxi.c

    // 匹配条件:只要与 compatible 所指向的字符串完全相同即可
    static const struct of_device_id sunxi_i2c_match[] = {
       
    	{
        .compatible = "allwinner,sun8i-twi", },
    	{
        .compatible = "allwinner,sun50i-twi", },
    	{
       },
    };
    MODULE_DEVICE_TABLE(of, sunxi_i2c_match);
    
    static struct platform_driver sunxi_i2c_driver = {
       
    	.probe		= sunxi_i2c_probe,			// 匹配成功后会被调用
    	.remove		= sunxi_i2c_remove,			// 驱动移除时会被调用
    	.driver		= {
       
    		.name	= SUNXI_TWI_DEV_NAME,
    		.owner	= THIS_MODULE,
    		.pm		= SUNXI_I2C_DEV_PM_OPS,
    		.of_match_table = sunxi_i2c_match,	// 指定平台设备资源匹配调节(dts)
    	},
    };
    
    static int __init sunxi_i2c_adap_init(void)
    {
       
        // 注册平台驱动
    	return platform_driver_register(&sunxi_i2c_driver);
    }
    
    static void __exit sunxi_i2c_adap_exit(void)
    {
       
        // 卸载平台驱动
    	platform_driver_unregister(&sunxi_i2c_driver);
    }
    
    fs_initcall(sunxi_i2c_adap_init);
    module_exit(sunxi_i2c_adap_exit);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    A64 有四路 I2C 控制器,取其中一路 dts 内容如下:

    lichee\linux-4.9\arch\arm64\boot\dts\sunxi\sun50iw1p1.dtsi

    设备树(dts)里面的配置信息,都会在内核被解析为平台设备(platform_device)注册到系统里面,其本质还是平台设备(platform_device)。

    twi0: twi@0x01c2ac00{
    	#address-cells = <1>;
    	#size-cells = <0>;
    	compatible = "allwinner,sun50i-twi";
    	device_type = "twi0";
    	reg = <0x0 0x01c2ac00 0x0 0x400>;
    	interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
    	clocks = <&clk_twi0>;
    	clock-frequency = <400000>;
    	pinctrl-names = "default", "sleep";
    	pinctrl-0 = <&twi0_pins_a>;
    	pinctrl-1 = <&twi0_pins_b>;
    	status = "disabled";
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    二、初始化部分

    关键结构:struct i2c_algorithm

    lichee\linux-4.9\include\linux\i2c.h

    • I2C 的实际数据传输均依赖于该数据结构定义的回调接口。
    • master_xfer:通用的 I2C 传输接口实现,是适配器驱动必须实现的一个功能接口。
    • smbus_xfer:smbus 子协议传输接口实现,如果未实现,核心层将会通过 master_xfer 模拟实现。
    • functionality:被用于查询该适配器驱动所支持的功能。
    • 通过 i2c_add_numbered_adapter() 将该数据结构注册到 I2C 核心层中。
    struct i2c_algorithm {
       
    	/* If an adapter algorithm can't do I2C-level access, set master_xfer
    	   to NULL. If an adapter algorithm can do SMBus access, set
    	   smbus_xfer. If set to NULL, the SMBus protocol is simulated
    	   using common I2C messages */
    	/* master_xfer should return the number of messages successfully
    	   processed, or a negative value on error */
    	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
    	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);
    
    	/* To determine what the adapter supports */
    	u32 (*functionality) (struct i2c_adapter *);
    
    #if IS_ENABLED(CONFIG_I2C_SLAVE)
    	int (*reg_slave)(struct i2c_client *client);
    	int (*unreg_slave)(struct i2c_client *client);
    #endif
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    入口函数:sunxi_i2c_probe

    lichee\linux-4.9\drivers\i2c\busses\i2c-sunxi.c

    static int sunxi_i2c_probe(struct platform_device *pdev)
    {
       
    	struct device_node *np = pdev->dev.of_node;
    	struct sunxi_i2c *i2c = NULL;
    	struct resource *mem_res = NULL;
    	struct sunxi_i2c_platform_data *pdata = NULL;
    	int ret, irq;
    	unsigned long int_flag = 0;
    	const char *str_vcc_twi;
    
        // 创建一个 I2C 控制器对象
    	i2c = kzalloc(sizeof(struct sunxi_i2c), GFP_KERNEL);
    	if (!i2c)
    		return -ENOMEM;
    
         // 开辟一个用于存储平台相关的数据内存
    	pdata = kzalloc(sizeof(struct sunxi_i2c_platform_data), GFP_KERNEL);
    	if (pdata == NULL) {
       
    		kfree(i2c);
    		return -ENOMEM;
    	}
    	i2c->dev = &pdev->dev;
    	pdev->dev.platform_data = pdata;
    	pdev->dev.driver_data = i2c;
    
        // 通过 dts 里面的 aliases 来确定总线编号
        //aliases {
       
        //   twi0 = &twi0;
        //    ...};
    	pdev->id = of_alias_get_id(np, "twi");
    	if (pdev->id < 0) {
       
    		I2C_ERR("I2C failed to get alias id\n");
    		ret = -EINVAL;
    		goto emem;
    	}
    	pdata->bus_num  = pdev->id;
    
        // 从 dts 获取寄存器地址段资源,对应 dts: reg = <0x0 0x01c2ac00 0x0 0x400>
    	mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    	if (mem_res == NULL) {
       
    		I2C_ERR("[I2C%d] failed to get MEM res\n", pdev->id);
    		ret = -ENXIO;
    		goto emem;
    	}
    	// 申请占用该段寄存器内存
    	if (!request_mem_region(mem_res->start, resource_size(mem_res),
    				mem_res->name)) {
       
    		I2C_ERR("[I2C%d] failed to request mem region\n", pdev->id);
    		ret = -EINVAL;
    		goto emem;
    	}
    	// 将寄存器地址段映射出来,方便后续进行操作
    	i2c->base_addr = ioremap(mem_res->start, resource_size(mem_res));
    	if (!i2c->base_addr) {
       
    		ret = -EIO;
    		goto eiomap;
    	}
    	// 从 dts 获取中断信息,对应 dts: interrupts = 
    	irq = platform_get_irq(pdev, 0);
    	if (irq < 0) {
       
    		I2C_ERR("[I2C%d] failed to get irq\n", pdev->id);
    		ret = -EINVAL;
    		goto eiomap;
    	}
    	// 从 dts 获取时钟频率,如需要 I2C 总线工作在 100KHz 就可以修改该参数
    	ret = of_property_read_u32(np, "clock-frequency", &pdata->frequency);
    	if (ret) {
       
    		I2C_ERR("[I2C%d] failed to get clock frequency\n", pdev->id);
    		ret = -EINVAL;
    		goto eiomap;
    	}
        // 从 dts 获取电源相关的配置,一般是对应该 I2C 的 SCL\SDA GPIO 的供电
    	ret = of_property_read_string(np, "twi_regulator", &str_vcc_twi);
    	if (ret)
    		I2C_ERR("[I2C%d] failed to get regulator id\n", pdev->id);
    	else {
       
    		pr_info("[I2C%d] twi_regulator: %s\n", pdev->id, str_vcc_twi);
    		strcpy(pdata->regulator_id, str_vcc_twi);
    	}
    
         // 初始化适配器相关的接口
    	pdev->dev.release = sunxi_i2c_release;	// 关闭时用于清理的接口
    	i2c->adap.owner   = THIS_MODULE;
    	i2c->adap.nr      = pdata->bus_num;		// 指定适配器编号
    	i2c->adap.retries = 3;					// 指定通信失败时重试的次数
    	i2c->adap.timeout = 5*HZ;				// 配置等待设备响应的超时时间
        			
        
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
  • 相关阅读:
    Android 数据恢复的顶级软件分享
    Linux - 进程概念
    免杀对抗-反沙盒+反调试
    BGP选路的十一条原则(第八课)
    高中生自学Python,这里给大家一些建议
    Qt中的单例模式
    (十三)强缓存和协商缓存的区别
    Docker安全及日志管理
    Java项目:JSP实现的图书管理系统
    php switch case 多条件使用同一个case
  • 原文地址:https://blog.csdn.net/lovemengx/article/details/128086698