分析平台:全志 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 进行数据读写,由于驱动没有实际产生通信时序,直接按原有的消息数量返回,所以检测和读写都可以成功。




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

  1. linux pl320 mbox控制器驱动分析-(3) pl320驱动代码分析

    linux pl320 mbox控制器驱动分析-(3)pl320驱动代码分析 1 pl320 mbox控制器宏定义 2 初始化接口 3 ipc_handler mbox中断处理函数 4 数据的收发 4 ...

  2. linux pl320 mbox控制器驱动分析 - (1) pl320手册分析

    linux pl320 mbox控制器驱动分析 1 pl320简介 1.1 pl320用途 1.2 pl320 IPCM 由以下部分组成: 1.3 pl320 IPCM可配置的参数 1.4 功能操作 ...

  3. 【分析笔记】Linux 4.9 单总线驱动框架分析

    文章介绍 本文主要是基于 T507 Android 10 Linux 4.9 的源代码,对 Linux W1 总线框架的分析记录,便于了解整个框架的基本实现机制. 驱动框架 这张图基本上将内部的各个源 ...

  4. 嵌入式Linux驱动笔记(十一)------i2c设备之mpu6050驱动

    ###你好!这里是风筝的博客, ###欢迎和我一起交流. 上一节讲了i2c框架: 嵌入式Linux驱动笔记(十)------通俗易懂式了解i2c框架 这次就来写一写真正的i2c设备驱动: mpu605 ...

  5. 【分析笔记】Linux 4.9 backlight 子系统分析

    相关信息 内核版本:Linux version 4.9.56 驱动文件:lichee\linux-4.9\drivers\video\backlight\backlight.c 驱动作用 对上,面对应 ...

  6. NLP情感分析笔记(五):多类型情感分析

    在本次学习中,我们将对具有 6 个类的数据集执行分类.请注意,该数据集实际上并不是情感分析数据集,而是问题数据集,任务是对问题所属的类别进行分类.但是,本次学习中涵盖的所有内容都适用于任何包含属于 

  7. NLP情感分析笔记(四):卷积情感分析

    卷积情感分析 CNN: 能够从局部输入图像块中提取特征,并能将表示模块化,同时可以高效第利用数据 可以用于处理时序数据,时间可以被看作一个空间维度,就像二维图像的高度和宽度 那么为什么要在文本上使用卷 ...

  8. NLP情感分析笔记(三):Faster 情感分析

    Faster 情感分析 一.数据预处理: FastText分类模型: 其他文本分类模型最大的不同之处在于其计算了输入句子的n-gram n-gram: 将文本里面的内容按照字节进行大小为n的滑动窗口操 ...

  9. NLP情感分析笔记(六):Transformer情感分析

    Transformer学习: 图解Transformer(完整版)_龙心尘-CSDN博客_transformer NLP实操手册: 基于Transformer的深度学习架构的应用指南(综述)_王博(K ...

最新文章

  1. BZOJ 1050: [HAOI2006]旅行comf(枚举+并查集)
  2. 看一遍那些网络上晦涩的名词都弄明白了!
  3. 用Java语言,写一个植物大战僵尸简易版!
  4. String path = request.getContextPath()和String basePath = request.getScheme()
  5. 前端学习(3146):react-hello-react之getBeforeUpdate
  6. 今天觉得自己好像比较紧张
  7. 苹果cms v8模板 高仿爱奇艺带PC+手机模板
  8. FLUSH TABLES WITH READ LOCK 和 LOCK TABLES比较
  9. gRPC-go源码(1):连接管理
  10. 为JavaScript日期添加天数
  11. [教程]控制反转(IoC)与依赖注入(DI)
  12. 第九章 (一)动态规划
  13. 黑暗星空中的秘密——《黑暗森林》简评
  14. 一、酷狗 歌词搜索 Indy TIdhttp
  15. java计算机毕业设计校园订餐系统源代码+数据库+系统+lw文档
  16. 发力数字化转型,容智信息携自研创新科技惊艳亮相2021世界人工智能大会
  17. H5 开发内嵌页面跨域问题
  18. 按键消抖及原理(硬件和软件方法详解)
  19. 手机APP测试(三)
  20. 4c钻石净度魔术语言,钻戒4c什么意思?详解钻石4C的秘密

热门文章

  1. 如何轻松成为年薪20万的程序员 ——致埋头撸码的程序员
  2. dyndns.org, which provides free Dynamic DNS forwarding Service!
  3. 小米崔宝秋:长线投入、持续努力,才能保证用户安全
  4. kindel 资源_Hanselminutes播客206-Windows Phone 7系列-Charlie Kindel的开发人员经验
  5. 今日好物推荐:音频转文字软件有哪些?
  6. shell 字符串出现的行数
  7. 计算机机房施工排名,机房监控十大品牌最新排名
  8. 儿童学python的平台-干货:孩子免费学习编程的十大网站&App
  9. TPAMI2015跟踪算法KCF原理及代码解析
  10. 使用postman进行juiceshop系统的接口自动化测试及生成报告