【分析笔记】全志 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);

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";
};

在这里插入图片描述

二、初始化部分

关键结构: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
};
入口函数: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 = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>
	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;				// 配置等待设备响应的超时时间
    			
    
  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值