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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

分析平台全志 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_xfersmbus 子协议传输接口实现如果未实现核心层将会通过 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;				// 配置等待设备响应的超时时间
    			
    // 指明该适配器支持哪些类型的从设备具体看下文适配器类型说明
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	i2c->bus_freq     = pdata->frequency;	// 指定总线工作频率
	i2c->irq          = irq;				// 记录中断号用于显示信息和卸载时释放用
	i2c->bus_num      = pdata->bus_num;		// 记录总线号用于后续配置该总线对应的 GPIO 
	i2c->status       = I2C_XFER_IDLE;		// 默认设置为空闲状态
	i2c->suspended = 0;						// 保留成员代码并未使用
    
    // 构造适配器的名称SUNXI_TWI_DEV_NAME=twi --> twi0
	snprintf(i2c->adap.name, sizeof(i2c->adap.name), SUNXI_TWI_DEV_NAME"%u", i2c->adap.nr);
	pdev->dev.init_name = i2c->adap.name;
    
	// 初始化自旋锁用于竞态并发下保护临界区如 I2C 寄存器、struct i2c_msg 	
	spin_lock_init(&i2c->lock);
     // 用于阻塞进程当传输完成后产生的中断会唤醒进程也有等待超时时也会被唤醒i2c->adap.timeout
	init_waitqueue_head(&i2c->wait);
	
	i2c->mclk = of_clk_get(np, 0);		// 从 dts 获取时钟源 clocks = <&clk_twi0>;
	if (IS_ERR_OR_NULL(i2c->mclk)) {
		I2C_ERR("[i2c%d] request TWI clock failed\n", i2c->bus_num);
		ret = -EIO;
		goto eremap;
	}
    // 重点: 指定该 I2C 适配器的数据传输接口集
	i2c->adap.algo = &sunxi_i2c_algorithm;	
	// 申请注册 I2C 中断通信核心
	ret = request_irq(irq, sunxi_i2c_handler, int_flag, i2c->adap.name, i2c);
	if (ret) {
		I2C_ERR("[i2c%d] requeset irq failed!\n", i2c->bus_num);
		goto ereqirq;
	}

    // 记录以备后续通信时引用
	i2c->adap.algo_data  = i2c;
	i2c->adap.dev.parent = &pdev->dev;
	i2c->adap.dev.of_node = pdev->dev.of_node;
    // 标记着该路 I2C 已经被使用用于操作对应 GPIO 之前判断只有使用了才能操作对应的 GPIO
	twi_used_mask |= SUNXI_TWI_CHAN_MASK(pdev->id); 

    // 初始化 I2C 硬件控制器主要是启用供电并配置引脚再初始化时钟
	if (sunxi_i2c_hw_init(i2c, pdata)) {
		ret = -EIO;
		goto ehwinit;
	}
    // 启用动态电源管理实现使用时唤醒不使用时休眠可有效降低功耗
	pm_runtime_enable(i2c->dev);
	pm_runtime_set_autosuspend_delay(i2c->dev, AUTOSUSPEND_TIMEOUT);
	pm_runtime_use_autosuspend(i2c->dev);
	pm_runtime_set_active(i2c->dev);
    
    // 将适配器加入到系统中系统就会多出一个 I2C 控制器可用
	ret = i2c_add_numbered_adapter(&i2c->adap);
	if (ret < 0) {
		I2C_ERR("[i2c%d] failed to add adapter\n", i2c->bus_num);
		pm_runtime_set_suspended(i2c->dev);
		pm_runtime_disable(i2c->dev);
		goto eadapt;
	}
	// 将 I2C 控制器对象作为平台私有数据以备后续引用
	platform_set_drvdata(pdev, i2c);	
    // I2C 控制器相关信息:
    // /sys/devices/platform/soc/twi0/info
    // /sys/devices/platform/soc/twi0/status
	sunxi_i2c_sysfs(pdev);				
	return 0;
	......
}

适配器类型说明

lichee\linux-4.9\drivers\i2c\i2c-core.c

该类型主要作用是表明当前适配器是否支持 detect 检测方式

  • 通过直接赋值 I2C_CLASS_DEPRECATED 明确表明不支持 detect 检测方式。
  • 通过增加赋值 I2C_CLASS_DEPRECATED 表示目前支持但后续会不再支持会输出警告提示。
  • 这里仅提 adapter->class 的具体作用属于核心层的知识点后续有机会再出一篇文章写整个框架层的
/* i2c adapter classes (bitmask) */
#define I2C_CLASS_HWMON			(1<<0)		// 硬件监控类如 lm_sensors 等
#define I2C_CLASS_DDC			(1<<3)		// DDC是数字显示通道Digital Display Channel的意思 通常用于显示设备信息的获取
#define I2C_CLASS_SPD			(1<<7)		// 存储类的模组 
#define I2C_CLASS_DEPRECATED	(1<<8)		// 不支持 detect 自动检测的方式

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);

    // 如果设备驱动都同时提供了 detect、address_list才会继续走下去
	address_list = driver->address_list;
	if (!driver->detect || !address_list)
		return 0;

	// 如果适配器的类型直接指定 I2C_CLASS_DEPRECATED表示完全不支持 detect 方式检测
	if (adapter->class == I2C_CLASS_DEPRECATED) {
		dev_dbg(&adapter->dev,
			"This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
			"If you need it, check 'Documentation/i2c/instantiating-devices' for alternatives.\n",
			driver->driver.name);
		return 0;
	}

	// 如果设备驱动类型不在适配器支持则直接返回
	if (!(adapter->class & driver->class))
		return 0;

	// 创建一个临时的 clinet用于给对于的设备驱动去检测芯片 ID 之类的操作
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

    // 开始扫描设备驱动地址列表内的地址
	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev,
			"found normal entry for adapter %d, addr 0x%02x\n",
			adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		err = i2c_detect_address(temp_client, driver);
		if (unlikely(err))
			break;
	}

	kfree(temp_client);
	return err;
}

static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
	......

	/* Finally call the custom detection function */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);
	if (err) {
		/* -ENODEV is returned if the detection fails. We catch it
		   here as this isn't an error. */
		return err == -ENODEV ? 0 : err;
	}

	/* Consistency check */
	if (info.type[0] == '\0') {
		dev_err(&adapter->dev, "%s detection function provided no name for 0x%x\n", driver->driver.name, addr);
	} else {
		struct i2c_client *client;
		// 如果适配器增加 I2C_CLASS_DEPRECATED 标志意味着这个适配器将会放弃这种 detect 检测方式就给出警告输出提醒。
		if (adapter->class & I2C_CLASS_DEPRECATED)
			dev_warn(&adapter->dev,
				"This adapter will soon drop class based instantiation of devices. "
				"Please make sure client 0x%02x gets instantiated by other means. "
				"Check 'Documentation/i2c/instantiating-devices' for details.\n",
				info.addr);

 		dev_dbg(&adapter->dev, "Creating %s at 0x%02x\n", info.type, info.addr);
		client = i2c_new_device(adapter, &info);
		if (client)
			list_add_tail(&client->detected, &driver->clients);
		else
			dev_err(&adapter->dev, "Failed creating %s at 0x%02x\n", info.type, info.addr);
	}
	return 0;
}

三、通信实现部分

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

数据传输

  • 设备驱动调用 i2c_master_*()/i2c_transfer()/i2c_smbus_xfer() 等接口触发。
  • master_xfer 接口不会出现并发的情况因为 i2c-core.c 会上锁避免并发。
static const struct i2c_algorithm sunxi_i2c_algorithm = {
	.master_xfer	  = sunxi_i2c_xfer,					// 负责真正实现数据传输的接口
	.functionality	  = sunxi_i2c_functionality,		// 用于设备驱动查询该适配器支持哪些功能
};

static unsigned int sunxi_i2c_functionality(struct i2c_adapter *adap)
{
    // 对应设备驱动调用的  i2c_check_functionality(adapter, I2C_FUNC_XXX)
    // 用于设备驱动查询当前适配器是否支持指定的功能这里表明支持 I2C、10位地址、SMBUS 全功能
	return I2C_FUNC_I2C|I2C_FUNC_10BIT_ADDR|I2C_FUNC_SMBUS_EMUL;
}

static int sunxi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	struct sunxi_i2c *i2c = (struct sunxi_i2c *)adap->algo_data;
	int ret = SUNXI_I2C_FAIL;
	int i   = 0;

	ret = pm_runtime_get_sync(i2c->dev);
	if (ret < 0)
		goto out;

    // 这个循环多少次表明通信异常时重试多少次
	for (i = 1; i <= adap->retries; i++) {
		ret = sunxi_i2c_do_xfer(i2c, msgs, num);
		if (ret != SUNXI_I2C_RETRY)
			goto out;
		udelay(100);
	}
	ret = -EREMOTEIO;
out:
	pm_runtime_mark_last_busy(i2c->dev);
	pm_runtime_put_autosuspend(i2c->dev);
	return ret;
}

static int sunxi_i2c_do_xfer(struct sunxi_i2c *i2c, struct i2c_msg *msgs, int num)
{
	unsigned long timeout = 0;
	int ret = SUNXI_I2C_FAIL;
	unsigned long flags = 0;

    // 复位总线内部写了 TWI_SRST_REG(0x18) 寄存器的 TWI_SRST_SRST(0x1<<0) 寄存器位。
	twi_soft_reset(i2c->base_addr);
	udelay(100);

	// 查询中断状态寄存器 TWI_STAT_REG(0x10)如果非空闲且不是总线错误以及总线仲裁丢失状态
	while (twi_query_irq_status(i2c->base_addr) != TWI_STAT_IDLE &&
		twi_query_irq_status(i2c->base_addr) != TWI_STAT_BUS_ERR &&
		twi_query_irq_status(i2c->base_addr) != TWI_STAT_ARBLOST_SLAR_ACK) {
		I2C_DBG("[i2c%d] bus is busy, status = %x\n", i2c->bus_num, twi_query_irq_status(i2c->base_addr));
        // 通过连续发最多 9 个时钟脉冲来持续检测 scl、sda 电平状态是否都为高电平用于释放总线。
		if (twi_send_clk_9pulse(i2c->base_addr, i2c->bus_num) != SUNXI_I2C_OK) {
			ret = SUNXI_I2C_RETRY; // 连续发了 9 个时钟脉冲都无法释放总线则将状态设置为重试状态
			goto out;
		} else
			break;
	}

	// 进入临界区主要是避免与中断出现竞态并发
	spin_lock_irqsave(&i2c->lock, flags);
	i2c->msg     = msgs;
	i2c->msg_num = num;
	i2c->msg_ptr = 0;
	i2c->msg_idx = 0;
	i2c->status  = I2C_XFER_START;
	twi_enable_irq(i2c->base_addr);  // 开启中断
	twi_disable_ack(i2c->base_addr); // 关闭 ACK 
	/* set the special function register,default:0. */
	twi_set_efr(i2c->base_addr, 0);
	spin_unlock_irqrestore(&i2c->lock, flags);

	// 设置寄存器产生启动信号
	ret = twi_start(i2c->base_addr, i2c->bus_num);
	if (ret == SUNXI_I2C_FAIL) {
		I2C_ERR("[I2C%d] twi_regulator: %s\n", i2c->bus_num, ((struct sunxi_i2c_platform_data *)(i2c->adap.dev.parent->platform_data))->regulator_id);
		twi_soft_reset(i2c->base_addr);		// 软件复位 I2C 控制器
		twi_disable_irq(i2c->base_addr);  	// 关闭 I2C 中断
		i2c->status  = I2C_XFER_IDLE;
		ret = SUNXI_I2C_RETRY;				// 设置重试标记返回继续重试
		goto out;
	}

    // 设置状态机运行状态
	i2c->status  = I2C_XFER_RUNNING;
	// 进程进入睡眠状态等待中断唤醒或超时唤醒由此可见主要通信逻辑是放在中断实现
	timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, i2c->adap.timeout);
	/* return code,if(msg_idx == num) succeed */
	ret = i2c->msg_idx; // 记录已传输的数据包数量
	if (timeout == 0) {	// 等待中断超时意味着数据传输超时可能是没有应答
		I2C_ERR("[i2c%d] xfer timeout (dev addr:0x%x)\n", i2c->bus_num, msgs->addr);
		spin_lock_irqsave(&i2c->lock, flags);
		i2c->msg = NULL;
		spin_unlock_irqrestore(&i2c->lock, flags);
		ret = -ETIME;
	} else if (ret != num) {
        // 数据传输不完整
		I2C_ERR("[i2c%d] incomplete xfer (status: 0x%x, dev addr: 0x%x)\n", i2c->bus_num, ret, msgs->addr);
		ret = -ECOMM;
	}
out:
	return ret;
}

中断处理

纯静态代码分析其中逻辑并未上机实践因此可能会存在一点理解偏差的情况。

  • 中断的逻辑处理与具体的芯片寄存器配置强关联。
  • 在 i2c-sunxi.c 里每次产生中断都只传输一个字节(地址或数据)通过不停的触发中断传输批量数据。
  • 每次传输完一个消息就会发起重新启动信号继续传输下一个消息直至所有消息传输完成才会唤醒进程。
static irqreturn_t sunxi_i2c_handler(int this_irq, void *dev_id)
{
	struct sunxi_i2c *i2c = (struct sunxi_i2c *)dev_id;

	if (!twi_query_irq_flag(i2c->base_addr)) {
		I2C_ERR("unknown interrupt!\n");
		return IRQ_NONE;
	}
	twi_disable_irq(i2c->base_addr);	// 关闭中断
	sunxi_i2c_core_process(i2c);		// 数据处理
	if (i2c->status != I2C_XFER_IDLE)	// 如果状态机非空闲状态说明还有数据要传输则开启中断
		twi_enable_irq(i2c->base_addr);	
	return IRQ_HANDLED;
}

static int sunxi_i2c_core_process(struct sunxi_i2c *i2c)
{
	void __iomem *base_addr = i2c->base_addr;
	int  ret        = SUNXI_I2C_OK;
	int  err_code   = 0;
	unsigned char  state = 0;
	unsigned char  tmp   = 0;
	unsigned long flags = 0;

    // 读取控制器中断状态
	state = twi_query_irq_status(base_addr);
	spin_lock_irqsave(&i2c->lock, flags);
	if (i2c->msg == NULL) {
		I2C_ERR("[i2c%d] i2c message is NULL, err_code = 0xfe\n", i2c->bus_num);
		err_code = 0xfe;
		goto msg_null;
	}
	// break会继续传输并且不会唤醒进程
    // goto会停止传输并且还会唤醒对应进程
	switch (state) {
	...
	case 0x08: // 控制器已发送了启动时序
	case 0x10: // 控制器已发送了重复启动时序
        // 发送从机器件地址7位地址则加读写位发送若是10位则会先发最高有效位的前两位加读写位
        //  7位地址: xxxx_xxx_rw
		// 10位地址: 1111_0xx_rw
		sunxi_i2c_addr_byte(i2c);  
		break;
	...
	case 0x18: // 发送从机器件地址后有收到从机的应答信号
        // 如果是 10bit 地址则继续发送剩余的 8 bit 地址
		if (i2c->msg[i2c->msg_idx].flags & I2C_M_TEN) {
			tmp = i2c->msg[i2c->msg_idx].addr & 0xff;
			twi_put_byte(base_addr, &tmp); /* case 0xd0: */
			break;
		}
		// 如果不是则继续下面的分支开始发送数据
	case 0xd0: // 第二个地址字节+发送的写位接收到应答信号应该对应 10bit 地址的器件应答
	case 0x28: // 以 master 模式传输数据字节接收到应答信号
		// 如果该消息还有数据就继续发送数据
		if (i2c->msg_ptr < i2c->msg[i2c->msg_idx].len) {
			twi_put_byte(base_addr, &(i2c->msg[i2c->msg_idx].buf[i2c->msg_ptr]));
			i2c->msg_ptr++; // 每次只发送一个字节
			break; 
		}
		i2c->msg_idx++; /* the other msg */
		i2c->msg_ptr = 0;
		if (i2c->msg_idx == i2c->msg_num) { // 完成所有消息传输则 goto 会唤醒进程。
			err_code = SUNXI_I2C_OK;/* Success,wakeup */
			goto ok_out;
		} else if (i2c->msg_idx < i2c->msg_num) { 
            // 能到这里就说明当前的消息要求读取的数据量已经传输完成
            // 发起重新启动信号继续下一个消息数据的传输
			ret = twi_restart(base_addr, i2c->bus_num);
			if (ret == SUNXI_I2C_FAIL) {
				I2C_ERR("[I2C%d] twi_regulator: %s\n", i2c->bus_num, ((struct sunxi_i2c_platform_data *)(i2c->adap.dev.parent->platform_data))->regulator_id);
				err_code = SUNXI_I2C_SFAIL;
				goto err_out;/* START can't sendout */
			}
		} else {
			err_code = SUNXI_I2C_FAIL;
			goto err_out;
		}
		break;
	...
	case 0x40: // 地址+读位有从机回应应答信号
		if (i2c->msg[i2c->msg_idx].len > 1) { 
			twi_enable_ack(base_addr); // 该消息的缓冲区长度大于1才使能自动回复应答信号
			twi_clear_irq_flag(base_addr);/* jump to case 0x50 */
		} else if (i2c->msg[i2c->msg_idx].len == 1) {
			twi_clear_irq_flag(base_addr);/* jump to case 0x58 */
		}
		break;
	case 0x48: // 地址+读位没有从机回应应答信号
		err_code = 0x48;	/*err,wakeup the thread*/
		goto err_out;
	case 0x50: // 控制器收到数据并主动给从机回应了应答信号
		if (i2c->msg_ptr < i2c->msg[i2c->msg_idx].len) {
			if ((i2c->msg_ptr + 2) == i2c->msg[i2c->msg_idx].len)
				twi_disable_ack(base_addr); // 接收的最后一个字节让控制器不再发送应答信号
			// 读取一个字节的数据并继续等待下一个字节到来
			twi_get_byte(base_addr, &i2c->msg[i2c->msg_idx].buf[i2c->msg_ptr]);
			i2c->msg_ptr++;
			break;
		}
		// 如果已经读够数据了还会收到数据并回应给从机应答信号说明出现了异常内存越界导致 msg_ptr 被修改
		err_code = SUNXI_I2C_FAIL;/* err, wakeup */
		goto err_out;
	case 0x58: // 控制器收到数据但主动给从机回应非应答信号
		if (i2c->msg_ptr == i2c->msg[i2c->msg_idx].len - 1) {
            // 读取最后一个字节的数据与 twi_get_byte() 不同的是该接口不会清除中断标志位
			twi_get_last_byte(base_addr, &i2c->msg[i2c->msg_idx].buf[i2c->msg_ptr]);
			i2c->msg_idx++;
			i2c->msg_ptr = 0;
			if (i2c->msg_idx == i2c->msg_num) {
				err_code = SUNXI_I2C_OK; // 所有消息要求读取的数量量都传输完成即可终止传输
				goto ok_out;
			} else if (i2c->msg_idx < i2c->msg_num) {
				// 能到这里就说明当前的消息要求读取的数据量已经传输完成
                // 发起重新启动信号继续下一个消息数据的传输
				ret = twi_restart(base_addr, i2c->bus_num);
				if (ret == SUNXI_I2C_FAIL) {/* START fail */
					I2C_ERR("[I2C%d] twi_regulator: %s\n", i2c->bus_num, ((struct sunxi_i2c_platform_data *)(i2c->adap.dev.parent->platform_data))->regulator_id);
					err_code = SUNXI_I2C_SFAIL;
					goto err_out;
				}
				break;
			}
		} else {
			err_code = 0x58;
			goto err_out;
		}
	...
	}
	i2c->debug_state = state;/* just for debug */
	spin_unlock_irqrestore(&i2c->lock, flags);
	return ret;

ok_out:
err_out:
    // 发送停止信号
	if (twi_stop(base_addr, i2c->bus_num) == SUNXI_I2C_TFAIL)
		I2C_ERR("[i2c%d] STOP failed!\n", i2c->bus_num);
msg_null:
	ret = sunxi_i2c_xfer_complete(i2c, err_code); // 唤醒进程
	i2c->debug_state = state;/* just for debug */
	spin_unlock_irqrestore(&i2c->lock, flags);
	return ret;
}

smbus 协议说明

lichee\linux-4.9\drivers\i2c\i2c-core.c

设备驱动层调用的 i2c_smbus_*() 相关接口时如果适配器并未实现 smbus_xfer 接口那么核心层就会通过 master_xfer 去模拟 smbus 协议。
在这里插入图片描述

s32 i2c_smbus_write_byte(const struct i2c_client *client, u8 value)
{
	return i2c_smbus_xfer(client->adapter, client->addr, client->flags, I2C_SMBUS_WRITE, value, I2C_SMBUS_BYTE, NULL);
}
EXPORT_SYMBOL(i2c_smbus_write_byte);

s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags, char read_write, u8 command, int protocol, union i2c_smbus_data *data)
{
	...
	if (adapter->algo->smbus_xfer) { // 如果 smbus_xfer 成员不为空就会调用该成员实现数据传输
		i2c_lock_bus(adapter, I2C_LOCK_SEGMENT);
		/* Retry automatically on arbitration loss */
		orig_jiffies = jiffies;
		for (res = 0, try = 0; try <= adapter->retries; try++) {
			res = adapter->algo->smbus_xfer(adapter, addr, flags, read_write, command, protocol, data);
			if (res != -EAGAIN)
				break;
			if (time_after(jiffies, orig_jiffies + adapter->timeout))
				break;
		}
		i2c_unlock_bus(adapter, I2C_LOCK_SEGMENT);
		if (res != -EOPNOTSUPP || !adapter->algo->master_xfer)
			goto trace;
		/*
		 * Fall back to i2c_smbus_xfer_emulated if the adapter doesn't
		 * implement native support for the SMBus operation.
		 */
	}
    // 如果 smbus_xfer 成员为空则通过 i2c_transfer() 模拟最后会调用 master_xfer 成员实现。
	res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write, command, protocol, data);
	...
	return res;
}
EXPORT_SYMBOL(i2c_smbus_xfer);

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data)
{
	...
	status = i2c_transfer(adapter, msg, num);
	if (status < 0)
		return status;
	...
	return 0;
}

简单实现适配器驱动

代码实例

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/platform_device.h>

struct my_adap {
	struct i2c_adapter	adap;
	struct device       *dev;
};

static int my_adap_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
	int i = 0, j = 0;
	struct my_adap *i2c = (struct my_adap *)adap->algo_data;
	struct device *dev = i2c->dev;
	
	dev_notice(dev, "my_adap_xfer() num:%d\n", num);
	for(i = 0; i < num; i++){
		dev_notice(dev, "msgs[%d]->len:%d\n", i, msgs[i].len);
		dev_notice(dev, "msgs[%d]->addr:0x%.2X\n", i, msgs[i].addr);
		dev_notice(dev, "msgs[%d]->flags:0x%.2X\n", i, msgs[i].flags);
		for(j = 0; j < msgs->len; j++){
			dev_notice(dev, "msgs[%d]->buf[%d]:0x%.2X ", i, j, msgs[i].buf[j]);
		}
	}
	return num;
}

static unsigned int my_adap_functionality(struct i2c_adapter *adap)
{
	struct my_adap *i2c = (struct my_adap *)adap->algo_data;
	struct device *dev = i2c->dev;
	
	dev_notice(dev, "my_adap_functionality() ...\n");
	return I2C_FUNC_I2C|I2C_FUNC_10BIT_ADDR|I2C_FUNC_SMBUS_EMUL;
}

static const struct i2c_algorithm my_adap_algorithm = {
	.master_xfer	  = my_adap_xfer,
	.functionality	  = my_adap_functionality,
};

static int my_adap_probe(struct platform_device *pdev)
{
	struct my_adap *i2c = NULL;
	struct device *dev = &pdev->dev;
	
	dev_notice(dev, "my_adap_probe() ...\n");
	if (!(i2c = kzalloc(sizeof(struct my_adap), GFP_KERNEL))){
		dev_err(dev, "kzalloc failed...\n");
		return -ENOMEM;
	}

	i2c->dev = &pdev->dev;
	i2c->adap.owner   = THIS_MODULE;
	i2c->adap.nr      = -1;	// 自动分配
	i2c->adap.retries = 3;
	i2c->adap.timeout = 5*HZ;
	i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
	
	i2c->adap.algo_data  = i2c;
	i2c->adap.dev.parent = &pdev->dev;
	i2c->adap.algo = &my_adap_algorithm;
	i2c->adap.dev.of_node = pdev->dev.of_node;
	snprintf(i2c->adap.name, sizeof(i2c->adap.name), "myadap");
	
	if (i2c_add_numbered_adapter(&i2c->adap) < 0) {
		dev_err(dev, "failed to add adapter\n");
		kfree(i2c);
		return -ENODEV;
	}
	platform_set_drvdata(pdev, i2c);
	
	return 0;
}

static int my_adap_remove(struct platform_device *pdev)
{
	struct my_adap *i2c = platform_get_drvdata(pdev);
	struct device *dev = &pdev->dev;
	
	dev_notice(dev, "my_adap_remove() ...\n");
	platform_set_drvdata(pdev, NULL);
	i2c_del_adapter(&i2c->adap);
	kfree(i2c);
	return 0;
}

static const struct of_device_id my_adap_match[] = {
	{ .compatible = "myadap", },
	{},
};
MODULE_DEVICE_TABLE(of, my_adap_match);

static struct platform_driver my_adap_driver = {
	.probe		= my_adap_probe,
	.remove		= my_adap_remove,
	.driver		= {
		.name	= "myadap",
		.owner	= THIS_MODULE,
		.of_match_table = my_adap_match,
	},
};

/*data relating*/
static struct platform_device my_adap_device = {
	.name	= "myadap",
};

static int __init my_adap_init(void)
{
	int err = 0;
	if ((err = platform_device_register(&my_adap_device)) < 0) {
		return err;
	}
	return platform_driver_register(&my_adap_driver);
}

static void __exit my_adap_exit(void)
{
	platform_driver_unregister(&my_adap_driver);
	platform_device_register(&my_adap_device);
}

fs_initcall(my_adap_init);
module_exit(my_adap_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Simple Bus Driver");

实现的效果

  • 加载驱动后配合 i2c-dev.c 通用驱动会自动生成 /dev/i2c-3 设备节点。
  • 可以使用 i2c-tools 进行数据读写由于驱动没有实际产生通信时序直接按原有的消息数量返回所以检测和读写都可以成功。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6