分析平台:全志 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; // 配置等待设备响应的超时时间 // 指明该适配器支持哪些类型的从设备(具体看下文:适配器类型说明)
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 进行数据读写,由于驱动没有实际产生通信时序,直接按原有的消息数量返回,所以检测和读写都可以成功。




【分析笔记】全志 i2c-sunxi.c 控制器驱动分析的更多相关文章

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

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

  2. Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】

    本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...

  3. I2C(三) linux3.4(内核分析)

    目录 I2C(三) linux3.4(内核分析) (一)总线流程 bus.probe match i2c_device_probe (二)client注册 方式(一)静态加载 方式(二)指定设备 方式 ...

  4. linux驱动基础系列--Linux下Spi接口Wifi驱动分析

    前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...

  5. 【分析笔记】展讯 RDA8810PL 平台 Camera 驱动分析和移植(Android 4.4 )

    前言概述 因以下原因,导致不得不通过代码分析来学习如何在该平台下进行摄像头驱动移植 香橙派开发商(迅龙软件)仅提供能跑起来的源代码.固件,以及简单的编译文档,不提供其它技术支持 baidu.googl ...

  6. Linux i2c子系统(四) _从i2c-s3c24xx.c看i2c控制器驱动的编写

    "./drivers/i2c/busses/i2c-s3c2410.c"是3.14.0内核中三星SoC的i2c控制器驱动程序, 本文试图通过对这个程序的分析, 剥离繁复的细节, 总 ...

  7. linux驱动基础系列--Linux I2c驱动分析

    前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...

  8. Linux I2C总线控制器驱动(S3C2440)

    s3c2440的i2c控制器驱动(精简DIY),直接上代码,注释很详细: #include <linux/kernel.h> #include <linux/module.h> ...

  9. linux i2c驱动架构-dm368 i2c驱动分析

      linux i2c驱动架构-dm368 i2c驱动分析   在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...

随机推荐

  1. Modbus协议及python库实现

    基础知识 硬件层协议:解决0和1的可靠传输,常有RS232.RS485.CAN.IIC.SPI - 软件层协议:解决传输目的,常有Modbus.TCP/IP.CANopen - 协议优点: Modbu ...

  2. 基于socket开发网络调试助手

    1.什么是Socket? 在计算机领域socket被翻译为套接字,它是计算机之间进行通信的一种方式,通过socket这种约定,一台计算机可以向另外一台计算机发送数据和接收数据. 2.Socket的本质 ...

  3. 关于C#Section配置未初始化的问题

    转载 https://www.cnblogs.com/lxshwyan/p/10828305.html 如果使用了configSection节点,则configSection必须位于根节点的第0个.A ...

  4. cookie中 防止重复存值 (可用于历史记录等)

    function makeCookie($key,$val){ // 查看cookie中是否已经存过键为history_ids if(Cookie::has($key)){ // 已经存过了 $jso ...

  5. i春秋broken

    点开一个附带超链接的网页,直接点击file跳转到broken网页 网页里面是一个jsfuck代码 Jsfuck代码的执行方法 ①复制 ②打开firefox浏览器 ③按下F12 ④选择上方的控制台 ⑤在 ...

  6. requests模块/openpyxl模块/简单爬虫实战

    内容概要 第三方模块的下载及使用 网络爬虫及requests模块 网络爬虫实战爬取二手房信息 自动化办公领域模块openpyxl 练习题及答案 第三方模块的下载 第三方模块就类似与别人写好的模块,我们 ...

  7. servlet包找不到,webservlet注解无效

    把tomcat/lib/  中的annotations-api.jar和servlet-api.jar复制到jdk/jre/lib/ext/目录中就行了 mine:C:\environment\apa ...

  8. 关于linux上实现arp攻击截取密码

    前言 这几天简单的研究了一下arp攻击,有一些进展,记录一下 环境准备 这里我是利用arpspoof 这个软件简单实现arp攻击,这个命令是属于dsniff 软件包中的 所以首先安装软件 sudo a ...

  9. Nginx 安装篇-yum安装

    yum安装教程引用: https://www.cnblogs.com/AprilBlank/p/11388990.html#1-yum安装推荐 避坑事项: 暂无

  10. js-day05-对象

    为什么要学习对象 没有对象时,保存网站用户信息时不方便,很难区别 对象是什么 1.对象是一种数据类型 2.无序的数据集合 对象有什么特点 1.无序的数据的集合 2.可以详细的描述某个事物' 对象使用 ...