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

news/2024/10/21 13:38:02/

分析平台:全志 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_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, 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 --> twi0snprintf(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.timeoutinit_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 之前判断,只有使用了才能操作对应的 GPIOtwi_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/statussunxi_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 ithere 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);elsedev_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;} elsebreak;}// 进入临界区,主要是避免与中断出现竞态并发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_rwsunxi_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 进行数据读写,由于驱动没有实际产生通信时序,直接按原有的消息数量返回,所以检测和读写都可以成功。

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


http://www.ppmy.cn/news/345161.html

相关文章

23 H5的spi控制器驱动

spi工作原理参考:http://blog.csdn.net/jklinux/article/details/74287735 在内核里的配置选项: make menuconfig ARCHarm64 CROSS_COMPILEaarch64-linux-gnu-Device Drivers --->[*] SPI support ---><*> Allwinner A31 SPI controller //H5所用的spi控制器…

学习VI编辑器

【PART 1】 简单编辑 &#xff03;移动光标 h j k l &#xff0b; 移动到下一行的行首&#xff0c;直接按回车也可以达到同样效果 &#xff0d; 移动到上一行的行首 一次移动几个位置&#xff0c;例&#xff1a;4l &#xff03;设置页面右边距 不是特别…

OrangePi3 LTS 驱动开发-uart通信

OrangePi3 LTS 驱动开发-uart通信 kernel版本&#xff1a;orange-pi-5.10-media 5.1x版本&#xff0c;uart3默认是关闭的&#xff0c;需要在orangepiEnv.txt中添加overlaysuart3字样开启uart3。如果不添加&#xff0c;默认设置为开&#xff0c;需要更高dts中的配置。 1、修改…

学习vi和vim编辑器(3):简单的文本编辑(2)

接着上一篇文章&#xff0c;继续讲解vi编辑器中一些简单的文本编辑命令。这一篇文章主要是文本的删除&#xff0c;复制&#xff0c;移动等命令。 删除文本&#xff1a; 正如上一篇文章中讲过的&#xff0c;对于删除命令&#xff08;"d"&#xff09;也具有“(comman…

对于摄像机而言同样清晰度下25p和50i画质会有什么差别?

作者&#xff1a;worldream 链接&#xff1a;https://www.zhihu.com/question/26796732/answer/135024474 来源&#xff1a;知乎 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 50i其实就是50fps——从摄像机的摄像管原始信号出发 模拟电视…

对视频50p,50i 还有25p,25i区别的粗劣解释

对视频50p,50i 还有25p,25i区别的粗劣解释 首先跟你解释p和i的概念&#xff0c;p是逐行扫描模式&#xff0c;i是隔行扫描模式。 帧速率有以下几种&#xff1a; 60帧/秒&#xff08;实际为59.94帧/秒&#xff0c;为方便起见&#xff0c;记为60帧/秒&#xff09; 50帧/秒、 3…

QT中信号和槽的概念

信号就是发生什么事件&#xff0c;槽就是触发什么函数&#xff0c;所以信号与槽就是发生什么事件(比如点击某个按钮)然后就会触发某个函数 connect(信号的发送者&#xff0c;发送的具体信号&#xff0c;信号的接受者&#xff0c;信号的处理&#xff09; connect(myButton , &a…

【服务器数据恢复】热备盘激活失败导致raid5瘫痪的数据恢复案例

服务器数据恢复环境&#xff1a; 一台EMC存储中数块磁盘组建了一组raid5磁盘阵列&#xff0c;阵列中有2块热备盘&#xff1b;上层采用ZFS文件系统&#xff0c;划分了一个lun&#xff0c;供sun小机使用。 服务器故障&检测&#xff1a; 存储在正常运行中突然崩溃无法使用&…