i2c适配器源码位置在driver/i2c/buss下这里以i2c-sunxi.c为例,i2c适配器设备和驱动的加载绑定过程也可以看做是i2c总线驱动的加载过程,I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
下面以源码进行分析:
函数的入口:
subsys_initcall(sunxi_i2c_adap_init); //和一般的驱动函数一样存在入口函数
static int __init sunxi_i2c_adap_init(void)//入口函数
{
I2C_DBG("Sunxi I2C adapt init\n");
return platform_driver_register(&sunxi_i2c_driver);//向platform总线注册这个驱动
}
定义适配器驱动:
static struct platform_driver sunxi_i2c_driver = {
.probe = sunxi_i2c_probe,//定义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, //定义匹配函数,这个函数
很重要,内核会依据这个
函数中compatible的值
是否相等来决定是否绑定
},
};
static const struct of_device_id sunxi_i2c_match[] = {
{ .compatible = "allwinner,sun8i-twi", },
{ .compatible = "allwinner,sun50i-twi", },
{ .compatible = "allwinner,sun3i-twi", },
{},//内核会自动将这里的值和设备树中的compatible的值进行匹配,匹配成功则将设备和驱动绑定并执行probe函数
};
probe函数:
主要完成adapter的注册和向i2c总线添加adapter工作
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 = IRQF_DISABLED;
if (np == NULL) {
I2C_ERR("I2C failed to get of node\n");//获取设备的节点
return -ENODEV;
}
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;
}
pdev->dev.platform_data = pdata;
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;
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;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
I2C_ERR("[I2C%d] failed to get irq\n", pdev->id);
ret = -EINVAL;
goto eiomap;
}
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;
}
pdev->dev.release = sunxi_i2c_release;
i2c->adap.owner = THIS_MODULE;
i2c->adap.nr = pdata->bus_num; //i2c号,使用第几组i2c总线
i2c->adap.retries = 3; //第一次和i2c设备的同学次数
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;
i2c->status = I2C_XFER_IDLE;
i2c->suspended = 0;
snprintf(i2c->adap.name, sizeof(i2c->adap.name), SUNXI_TWI_DEV_NAME"%u", i2c->adap.nr);
pdev->dev.init_name = i2c->adap.name;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
i2c->mclk = of_clk_get(np, 0);
if (IS_ERR_OR_NULL(i2c->mclk)) {
I2C_ERR("[i2c%d] request TWI clock failed\n", i2c->bus_num);
ret = -EIO;
goto eremap;
}
i2c->adap.algo = &sunxi_i2c_algorithm;
#ifndef CONFIG_SUNXI_ARISC
/* SUNXI_ARISC will only use twi0, enable gic interrupt when suspend */
if (0 == i2c->adap.nr)
int_flag |= IRQF_NO_SUSPEND;
#endif
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;
twi_used_mask |= SUNXI_TWI_CHAN_MASK(pdev->id);
if (sunxi_i2c_hw_init(i2c, pdata)) {
ret = -EIO;
goto ehwinit;
}
ret = i2c_add_numbered_adapter(&i2c->adap);//向i2c总线添加adapter
if (ret < 0) {
I2C_ERR( "[i2c%d] failed to add adapter\n", i2c->bus_num);
goto eadapt;
}
of_i2c_register_devices(&i2c->adap); //注册这个驱动对应的设备
platform_set_drvdata(pdev, i2c);
sunxi_i2c_sysfs(pdev);
I2C_DBG("I2C: %s: sunxi I2C adapter\n", dev_name(&i2c->adap.dev));
I2C_DBG("TWI_CTL 0x%p: 0x%08x \n", i2c->base_addr + 0x0c, readl(i2c->base_addr + 0x0c));
I2C_DBG("TWI_STAT 0x%p: 0x%08x \n", i2c->base_addr + 0x10, readl(i2c->base_addr + 0x10));
I2C_DBG("TWI_CLK 0x%p: 0x%08x \n", i2c->base_addr + 0x14, readl(i2c->base_addr + 0x14));
I2C_DBG("TWI_SRST 0x%p: 0x%08x \n", i2c->base_addr + 0x18, readl(i2c->base_addr + 0x18));
I2C_DBG("TWI_EFR 0x%p: 0x%08x \n", i2c->base_addr + 0x1c, readl(i2c->base_addr + 0x1c));
return 0;
eadapt:
clk_disable_unprepare(i2c->mclk);
ehwinit:
free_irq(irq, i2c);
ereqirq:
iounmap(i2c->base_addr);
eremap:
if (!IS_ERR_OR_NULL(i2c->mclk)) {
clk_put(i2c->mclk);
i2c->mclk = NULL;
}
eiomap:
release_mem_region(mem_res->start, resource_size(mem_res));
emem:
kfree(pdata);
kfree(i2c);
return ret;
}
注:这个probe函数只有在设备和驱动绑定成功的时候才会被执行,实际调试时可以在在此函数中打debug,来判断probe函数是否被执行。从而判断设备树的信息和驱动中的compatible的值是否配置正确;
在适配器中还有一个比较重要的函数,是用来和i2c设备进行通讯:
在i2c设备和适配器进行通讯时,i2c设备的驱动会调用i2c_transfer函数,通过i2c_transfer函数调用algorithm函数,algorithm函数实际就是对master_xfer的调用,而master_xfer函数在适配器被定义为sunxi_i2c_xfer,也就是i2c设备通过调用i2c_transfer,然后一层一层的调用,最终调用了是适配器的sunxi_i2c_xfer函数来完成和i2c设备的通讯;(sunxi_i2c_xfer会调用sunxi_i2c_do_xfer来完成收发通讯)在对i2c设备进行调试的时候要熟悉这个调用流程,才知道问题所在;
下面一层一层的看源码:
i2c->adap.algo = &sunxi_i2c_algorithm;
adap是一个i2c_adapter结构体
定义了master_xfer和sunxi_i2c_xfer的对应关系
static const struct i2c_algorithm sunxi_i2c_algorithm = {
.master_xfer = sunxi_i2c_xfer,
.functionality = sunxi_i2c_functionality,
};
调用sunxi_i2c_xfer来实现通讯,其内部是最终通过调用sunxi_i2c_do_xfer函数
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;
if (i2c->suspended) {
I2C_ERR("[i2c%d] has already suspend, dev addr:0x%x!\n", i2c->adap.nr, msgs->addr);
return -ENODEV;
}
for (i = 1; i <= adap->retries; i++) {
ret = sunxi_i2c_do_xfer(i2c, msgs, num); //最终是通过调用此函数进行通讯
if (ret != SUNXI_I2C_RETRY) {
goto out;
}
I2C_DBG("[i2c%d] Retrying transmission %d\n", i2c->adap.nr, i);
udelay(100);
}
ret = -EREMOTEIO;
out:
return ret;
}
sunxi_i2c_xfer 调用 sunxi_i2c_do_xfer函数
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;
//int i = 0, j =0;
twi_soft_reset(i2c->base_addr);
udelay(100);
/* test the bus is free,already protect by the semaphore at DEV layer */ //判断总线是否可用
while (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 != twi_query_irq_status(i2c->base_addr)) {
I2C_DBG("[i2c%d] bus is busy, status = %x\n", i2c->bus_num, twi_query_irq_status(i2c->base_addr));
if (SUNXI_I2C_OK == twi_send_clk_9pulse(i2c->base_addr, i2c->bus_num)) {
break;
} else {
ret = SUNXI_I2C_RETRY;
goto out;
}
}
/* may conflict with xfer_complete */
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); /* enable irq */
twi_disable_ack(i2c->base_addr); /* disabe ACK */
twi_set_efr(i2c->base_addr, 0); /* set the special function register,default:0. */
spin_unlock_irqrestore(&i2c->lock, flags);
/* START signal, needn't clear int flag */
ret = twi_start(i2c->base_addr, i2c->bus_num);//开始start,开始传输
if (ret == SUNXI_I2C_FAIL) {
twi_soft_reset(i2c->base_addr);
twi_disable_irq(i2c->base_addr); /* disable irq */
i2c->status = I2C_XFER_IDLE;
ret = SUNXI_I2C_RETRY;
goto out;
}
i2c->status = I2C_XFER_RUNNING;
/* sleep and wait, do the transfer at interrupt handler ,timeout = 5*HZ */
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;
}
该驱动的源码主要是ada和acl的gpio口的注册等等,在调试的时候一定注意通讯时函数的调用流程,这也是此类驱动的一个框架;