【分析笔记】全志 i2c-sunxi.c 控制器驱动分析
分析平台:全志 A64
内核版本:Linux 4.9
数据手册:Allwinner_A64_User_Manual_V1.1.pdf (whycan.com)
驱动框架
I2C 设备驱动
作为方案应用来说,我们是最经常要动的地方,这一层主要与具体的芯片功能强关联,不同的芯片具有不同的使用方法,如触摸屏设备驱动。
核心框架层
Linux 提供的硬件抽象层,起到承上启下的作用,对上提供注册设备驱动的统一接口,对下提供硬件控制器接入统一接口,负责维护众多的设备驱动和适配器驱动。
适配器层
由 Soc 芯片原厂提供,通常 Soc 支持多少路 I2C 总线,就会有多少个硬件控制器,这些硬件控制器才是真正实现与外设芯片通信的地方。我们也可以通过 GPIO 模拟 I2C 时序来实现一个硬件适配器,对于设备驱动来说,它不需要关心 Soc 是通过何种方式产生通信时序来跟外设芯片通信的。
本文主要分析位于适配器层的全志 i2c-sunxi.c 硬件控制器驱动程序,目的在于了解 I2C 适配器驱动的使用方法。
代码分析
一、平台设备驱动
I2C 控制器驱动是通过平台驱动的方式注册到系统中:
lichee\linux-4.9\drivers\i2c\busses\i2c-sunxi.c
// 匹配条件:只要与 compatible 所指向的字符串完全相同即可
static const struct of_device_id sunxi_i2c_match[] = {
{ .compatible = "allwinner,sun8i-twi", },
{ .compatible = "allwinner,sun50i-twi", },
{},
};
MODULE_DEVICE_TABLE(of, sunxi_i2c_match);
static struct platform_driver sunxi_i2c_driver = {
.probe = sunxi_i2c_probe, // 匹配成功后会被调用
.remove = sunxi_i2c_remove, // 驱动移除时会被调用
.driver = {
.name = SUNXI_TWI_DEV_NAME,
.owner = THIS_MODULE,
.pm = SUNXI_I2C_DEV_PM_OPS,
.of_match_table = sunxi_i2c_match, // 指定平台设备资源匹配调节(dts)
},
};
static int __init sunxi_i2c_adap_init(void)
{
// 注册平台驱动
return platform_driver_register(&sunxi_i2c_driver);
}
static void __exit sunxi_i2c_adap_exit(void)
{
// 卸载平台驱动
platform_driver_unregister(&sunxi_i2c_driver);
}
fs_initcall(sunxi_i2c_adap_init);
module_exit(sunxi_i2c_adap_exit);
A64 有四路 I2C 控制器,取其中一路 dts 内容如下:
lichee\linux-4.9\arch\arm64\boot\dts\sunxi\sun50iw1p1.dtsi
设备树(dts)里面的配置信息,都会在内核被解析为平台设备(platform_device)注册到系统里面,其本质还是平台设备(platform_device)。
twi0: twi@0x01c2ac00{
#address-cells = <1>;
#size-cells = <0>;
compatible = "allwinner,sun50i-twi";
device_type = "twi0";
reg = <0x0 0x01c2ac00 0x0 0x400>;
interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk_twi0>;
clock-frequency = <400000>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&twi0_pins_a>;
pinctrl-1 = <&twi0_pins_b>;
status = "disabled";
};
二、初始化部分
关键结构:struct i2c_algorithm
lichee\linux-4.9\include\linux\i2c.h
- I2C 的实际数据传输均依赖于该数据结构定义的回调接口。
- master_xfer:通用的 I2C 传输接口实现,是适配器驱动必须实现的一个功能接口。
- smbus_xfer:smbus 子协议传输接口实现,如果未实现,核心层将会通过 master_xfer 模拟实现。
- functionality:被用于查询该适配器驱动所支持的功能。
- 通过 i2c_add_numbered_adapter() 将该数据结构注册到 I2C 核心层中。
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
入口函数:sunxi_i2c_probe
lichee\linux-4.9\drivers\i2c\busses\i2c-sunxi.c
static int sunxi_i2c_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct sunxi_i2c *i2c = NULL;
struct resource *mem_res = NULL;
struct sunxi_i2c_platform_data *pdata = NULL;
int ret, irq;
unsigned long int_flag = 0;
const char *str_vcc_twi;
// 创建一个 I2C 控制器对象
i2c = kzalloc(sizeof(struct sunxi_i2c), GFP_KERNEL);
if (!i2c)
return -ENOMEM;
// 开辟一个用于存储平台相关的数据内存
pdata = kzalloc(sizeof(struct sunxi_i2c_platform_data), GFP_KERNEL);
if (pdata == NULL) {
kfree(i2c);
return -ENOMEM;
}
i2c->dev = &pdev->dev;
pdev->dev.platform_data = pdata;
pdev->dev.driver_data = i2c;
// 通过 dts 里面的 aliases 来确定总线编号
//aliases {
// twi0 = &twi0;
// ...};
pdev->id = of_alias_get_id(np, "twi");
if (pdev->id < 0) {
I2C_ERR("I2C failed to get alias id\n");
ret = -EINVAL;
goto emem;
}
pdata->bus_num = pdev->id;
// 从 dts 获取寄存器地址段资源,对应 dts: reg = <0x0 0x01c2ac00 0x0 0x400>
mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (mem_res == NULL) {
I2C_ERR("[I2C%d] failed to get MEM res\n", pdev->id);
ret = -ENXIO;
goto emem;
}
// 申请占用该段寄存器内存
if (!request_mem_region(mem_res->start, resource_size(mem_res),
mem_res->name)) {
I2C_ERR("[I2C%d] failed to request mem region\n", pdev->id);
ret = -EINVAL;
goto emem;
}
// 将寄存器地址段映射出来,方便后续进行操作
i2c->base_addr = ioremap(mem_res->start, resource_size(mem_res));
if (!i2c->base_addr) {
ret = -EIO;
goto eiomap;
}
// 从 dts 获取中断信息,对应 dts: interrupts = <GIC_SPI 6 IRQ_TYPE_LEVEL_HIGH>
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
I2C_ERR("[I2C%d] failed to get irq\n", pdev->id);
ret = -EINVAL;
goto eiomap;
}
// 从 dts 获取时钟频率,如需要 I2C 总线工作在 100KHz 就可以修改该参数
ret = of_property_read_u32(np, "clock-frequency", &pdata->frequency);
if (ret) {
I2C_ERR("[I2C%d] failed to get clock frequency\n", pdev->id);
ret = -EINVAL;
goto eiomap;
}
// 从 dts 获取电源相关的配置,一般是对应该 I2C 的 SCL\SDA GPIO 的供电
ret = of_property_read_string(np, "twi_regulator", &str_vcc_twi);
if (ret)
I2C_ERR("[I2C%d] failed to get regulator id\n", pdev->id);
else {
pr_info("[I2C%d] twi_regulator: %s\n", pdev->id, str_vcc_twi);
strcpy(pdata->regulator_id, str_vcc_twi);
}
// 初始化适配器相关的接口
pdev->dev.release = sunxi_i2c_release; // 关闭时用于清理的接口
i2c->adap.owner = THIS_MODULE;
i2c->adap.nr = pdata->bus_num; // 指定适配器编号
i2c->adap.retries = 3; // 指定通信失败时重试的次数
i2c->adap.timeout = 5*HZ; // 配置等待设备响应的超时时间
// 指明该适配器支持哪些类型的从设备(具体看下文:适配器类型说明)
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 控制器驱动分析的更多相关文章
- 【分析笔记】Linux 4.9 backlight 子系统分析
相关信息 内核版本:Linux version 4.9.56 驱动文件:lichee\linux-4.9\drivers\video\backlight\backlight.c 驱动作用 对上,面对应 ...
- Linux I2C驱动分析(三)----i2c_dev驱动和应用层分析 【转】
本文转载自:http://blog.chinaunix.net/uid-21558711-id-3959287.html 分类: LINUX 原文地址:Linux I2C驱动分析(三)----i2c_ ...
- I2C(三) linux3.4(内核分析)
目录 I2C(三) linux3.4(内核分析) (一)总线流程 bus.probe match i2c_device_probe (二)client注册 方式(一)静态加载 方式(二)指定设备 方式 ...
- linux驱动基础系列--Linux下Spi接口Wifi驱动分析
前言 本文纯粹的纸上谈兵,我并未在实际开发过程中遇到需要编写或调试这类驱动的时候,本文仅仅是根据源码分析后的记录!基于内核版本:2.6.35.6 .主要是想对spi接口的wifi驱动框架有一个整体的把 ...
- 【分析笔记】展讯 RDA8810PL 平台 Camera 驱动分析和移植(Android 4.4 )
前言概述 因以下原因,导致不得不通过代码分析来学习如何在该平台下进行摄像头驱动移植 香橙派开发商(迅龙软件)仅提供能跑起来的源代码.固件,以及简单的编译文档,不提供其它技术支持 baidu.googl ...
- Linux i2c子系统(四) _从i2c-s3c24xx.c看i2c控制器驱动的编写
"./drivers/i2c/busses/i2c-s3c2410.c"是3.14.0内核中三星SoC的i2c控制器驱动程序, 本文试图通过对这个程序的分析, 剥离繁复的细节, 总 ...
- linux驱动基础系列--Linux I2c驱动分析
前言 主要是想对Linux I2c驱动框架有一个整体的把控,因此会忽略协议上的某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型.sysfs等也不进行详细说明原理,涉及到i2c协议部分也只 ...
- Linux I2C总线控制器驱动(S3C2440)
s3c2440的i2c控制器驱动(精简DIY),直接上代码,注释很详细: #include <linux/kernel.h> #include <linux/module.h> ...
- linux i2c驱动架构-dm368 i2c驱动分析
linux i2c驱动架构-dm368 i2c驱动分析 在阅读本文最好先熟悉一种i2c设备的驱动程序,并且浏览一下i2c-core.c以及芯片提供商的提供的i2c总线驱动(i2c-davinc ...
随机推荐
- Vivado_8位流水灯
Design 代码中的计数器设置是因为我的开发板的时钟是100MHZ的,也就是1秒完成了100_000_000次时钟信号,所以我设置计数器为100_000_000次. 也就是说,我让流水灯的变化周期为 ...
- 通过jmeter连接人大金仓数据库
某项目用的人大金仓数据库,做性能测试,需要用jmeter来连接数据库处理一批数据.jmeter连接人大金仓,做个记录. 1. 概要 在"配置元件"中添加"JDBC Con ...
- java安全之CC1浅学(2)
前言 上一篇了解了commons-collections中的Transformer,并且构造了一个简单的payload,接下来就需要将其改造为一个可利用的POC AnnotationInvocatio ...
- 如何快捷地修改虚拟机镜像——libguestfs-tools工具集介绍
前言 在使用云服务器产品时,由于问题修复.功能添加.软件更新等原因,往往需要对已有系统镜像进行二次修改. 这种情况下,最为简单的做法是:使用原镜像创建实例,在实例中进行修改,修改完毕后再转镜像.这种做 ...
- Ajax基础(中)
这节主要在上节的基础上学会如何使用Ajax 源码下载: 链接:https://pan.baidu.com/s/1kG-vACFxneAZqONdo97XrQ 提取码:k21y 在WebStorm中打开 ...
- win10系统VMWare16 Pro 安装CentOS8
目录 一.本机环境与问题解决 二.下载软件 三.VMWare16 Pro安装 四.CentOS8 安装 一.本机环境与问题解决 装了好几遍,感觉坑都踩了一遍,泪奔~,还好终于跑起来了! 查看电脑是否开 ...
- 【深入浅出 Yarn 架构与实现】4-1 ResourceManager 功能概述
前面几篇文章对 Yarn 基本架构.程序基础库.应用设计方法等进行了介绍.之后几篇将开始对 Yarn 核心组件进行剖析. ResourceManager(RM)是 Yarn 的核心管理服务,负责集群管 ...
- 【Devexpress】Gridcontorl的列隐藏后再显示位置发生了变化
首先在可视化界面中排序好每个列的显示位置索引 在窗口初始化时进行记录在字段中 /// <summary> /// 当前显示列的位置索引,用于隐藏后显示进行重新排序位置 /// </s ...
- OpenLooKeng
一.登录注册 网址为:https://tryme.openlookeng.io/,进入该网址后可以看到以下界面: 可以选择左边的GitHub或者右边的Gitee进行登录,如果没有账号需要注册一个账号进 ...
- 前端开发:4、JavaScript简介、变量与常量、数据类型及内置方法、运算符、流程控制、循环结构、内置方法
前端开发之JavaScript 目录 前端开发之JavaScript 一.JavaScript简介 二.JS基础 三.变量与常量 四.基本数据类型 1.数值类型 2.字符类型 3.布尔类型 五.特殊数 ...