×
单片机 > 单片机程序设计 > 详情

I2C子系统分析

发布时间:2024-06-01 发布时间:
|

本文以s3c2440的I2C子系统为例, 分析其代码实现


本人学习驱动不久, 如有瑕疵纰漏, 欢迎指教, 谢谢


从硬件的角度看, I2C子系统由总线适配器和挂在总线上的设备组成

因此, 很容易想到, Linux的I2C子系统至少要提供:

总线上设备的支持, 以及其驱动

总线适配器的支持, 以及其驱动


1. S3C2440的I2C总线作为一个平台设备, 来看下添加平台设备的代码: /arch/arm/mach-s3c2440/mach-smdk2440.c


static struct platform_device *smdk2440_devices[] __initdata = {

&s3c_device_usb,

&s3c_device_lcd,

&s3c_device_wdt,

&s3c_device_i2c0,

&s3c_device_iis,

};

static void __init smdk2440_machine_init(void)

{

s3c24xx_fb_set_platdata(&smdk2440_fb_info);

s3c_i2c0_set_platdata(NULL);

platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));

smdk_machine_init();

}

其中, s3c_device_i2c0即为I2C的平台设备, 看下它是怎么定义的: /arch/arm/plat-s3c/dev-i2c0.c

static struct resource s3c_i2c_resource[] = {

[0] = {

.start = S3C_PA_IIC,

.end = S3C_PA_IIC + SZ_4K - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_IIC,

.end = IRQ_IIC,

.flags = IORESOURCE_IRQ,

},

};

struct platform_device s3c_device_i2c0 = {

.name = "s3c2410-i2c",

#ifdef CONFIG_S3C_DEV_I2C1

.id = 0,

#else

.id = -1,

#endif

.num_resources = ARRAY_SIZE(s3c_i2c_resource),

.resource = s3c_i2c_resource,

};

设备名称为s3c2410-i2c, 同时定义了两个资源, 一段IO内存, 一个中断号

在smdk2440_machine_init中, 即初始化过程中, 调用了s3c_i2c0_set_platdata(NULL)

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {

.flags = 0,

.slave_addr = 0x10,

.bus_freq = 100*1000,

.max_freq = 400*1000,

.sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON,

};

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)

{

struct s3c2410_platform_i2c *npd;

if (!pd)

pd = &default_i2c_data0;

npd = kmemdup(pd, sizeof(struct s3c2410_platform_i2c), GFP_KERNEL);

if (!npd)

printk(KERN_ERR "%s: no memory for platform datan", __func__);

else if (!npd->cfg_gpio)

npd->cfg_gpio = s3c_i2c0_cfg_gpio;

s3c_device_i2c0.dev.platform_data = npd;

}

阅读s3c_i2c0_set_platdata函数, 是对s3c_device_i2c0添加一些设备参数(default_i2c_data0)

简单的看下系统默认提供的这套参数, slave_addr应该是作为从机的地址, bus_freq应该是总线频率, 为100kHz, ...


所以, 设备结构体s3c_device_i2c0最后应该是这个样子:

struct platform_device s3c_device_i2c0 = {

.name = "s3c2410-i2c",

.id = -1,

.num_resources = ARRAY_SIZE(s3c_i2c_resource),

.resource = s3c_i2c_resource,

};

s3c_device_i2c0.dev.platform_data = {

.flags = 0,

.slave_addr = 0x10,

.bus_freq = 100*1000,

.max_freq = 400*1000,

.sda_delay = S3C2410_IICLC_SDA_DELAY5 | S3C2410_IICLC_FILTER_ON,

.cfg_gpio = s3c_i2c0_cfg_gpio;

};

其中有个函数指针cfg_gpio, 指向了s3c_i2c0_cfg_gpio, 作用是配置I2C相应IO接口:

void s3c_i2c0_cfg_gpio(struct platform_device *dev)

{

s3c2410_gpio_cfgpin(S3C2410_GPE15, S3C2410_GPE15_IICSDA);

s3c2410_gpio_cfgpin(S3C2410_GPE14, S3C2410_GPE14_IICSCL);

}


自此, I2C作为平台设备, 被添加到了平台总线上.


2. 设备添加完了, 就该驱动了, 来看下I2C设备驱动定义: /drivers/i2c/buses/i2c-s3c2410.c

static struct platform_driver s3c2410_i2c_driver = {

.probe = s3c24xx_i2c_probe,

.remove = s3c24xx_i2c_remove,

.suspend_late = s3c24xx_i2c_suspend_late,

.resume = s3c24xx_i2c_resume,

.driver = {

.owner = THIS_MODULE,

.name = "s3c2410-i2c",

},

};

驱动名字为s3c2410-i2c, 和设备名字一样

然后将注册驱动, 将驱动添加到平台总线上:

static int __init i2c_adap_s3c_init(void)

{

int ret;

ret = platform_driver_register(&s3c2410_i2c_driver);

if (ret == 0) {

ret = platform_driver_register(&s3c2440_i2c_driver);

if (ret)

platform_driver_unregister(&s3c2410_i2c_driver);

}

return ret;

}


回想一下, 当添加一个平台设备或者平台驱动时, 都会调用总线的match函数来将设备和驱动配对

而平台总线的match是根据设备和驱动的名字, 当相同时, 即匹配成功, 并调用驱动的probe函数


那么, 接着来看下s3c24xx_i2c_probe函数, 为了看着清晰些, 我把错误检测和错误恢复的代码删了

/* s3c24xx_i2c_probe

*

* called by the bus driver when a suitable device is found

*/

static int s3c24xx_i2c_probe(struct platform_device *pdev)

{

struct s3c24xx_i2c *i2c;

struct s3c2410_platform_i2c *pdata;

struct resource *res;

int ret;

pdata = pdev->dev.platform_data;

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);

strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));

i2c->adap.owner = THIS_MODULE;

i2c->adap.algo = &s3c24xx_i2c_algorithm;

i2c->adap.retries = 2;

i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;

i2c->tx_setup = 50;

spin_lock_init(&i2c->lock);

init_waitqueue_head(&i2c->wait);

/* find the clock and enable it */

i2c->dev = &pdev->dev;

i2c->clk = clk_get(&pdev->dev, "i2c");

dev_dbg(&pdev->dev, "clock source %pn", i2c->clk);

clk_enable(i2c->clk);

/* map the registers */

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,

pdev->name);

i2c->regs = ioremap(res->start, (res->end-res->start)+1);

dev_dbg(&pdev->dev, "registers %p (%p, %p)n",

i2c->regs, i2c->ioarea, res);

/* setup info block for the i2c core */

i2c->adap.algo_data = i2c;

i2c->adap.dev.parent = &pdev->dev;

/* initialise the i2c controller */

ret = s3c24xx_i2c_init(i2c);

/* find the IRQ for this unit (note, this relies on the init call to

* ensure no current IRQs pending

*/

i2c->irq = ret = platform_get_irq(pdev, 0);

ret = request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,

dev_name(&pdev->dev), i2c);

ret = s3c24xx_i2c_register_cpufreq(i2c);

/* Note, previous versions of the driver used i2c_add_adapter()

* to add the bus at any number. We now pass the bus number via

* the platform data, so if unset it will now default to always

* being bus 0.

*/

i2c->adap.nr = pdata->bus_num;

ret = i2c_add_numbered_adapter(&i2c->adap);

platform_set_drvdata(pdev, i2c);

dev_info(&pdev->dev, "%s: S3C I2C adaptern", dev_name(&i2c->adap.dev));

return 0;

}

比较常规的probe, 使能时钟, 申请IO内存, 映射IO内存, 申请中断就不说了, 删去, 得到以下:

static int s3c24xx_i2c_probe(struct platform_device *pdev)

{

struct s3c24xx_i2c *i2c;

struct s3c2410_platform_i2c *pdata;

struct resource *res;

int ret;

pdata = pdev->dev.platform_data;

i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL);

strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));

i2c->adap.owner = THIS_MODULE;

i2c->adap.algo = &s3c24xx_i2c_algorithm;

i2c->adap.retries = 2;

i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;

i2c->tx_setup = 50;

/* setup info block for the i2c core */

i2c->adap.algo_data = i2c;

i2c->adap.dev.parent = &pdev->dev;

/* initialise the i2c controller */

ret = s3c24xx_i2c_init(i2c);

ret = s3c24xx_i2c_register_cpufreq(i2c);

/* Note, previous versions of the driver used i2c_add_adapter()

* to add the bus at any number. We now pass the bus number via

* the platform data, so if unset it will now default to always

* being bus 0.

*/

i2c->adap.nr = pdata->bus_num;

ret = i2c_add_numbered_adapter(&i2c->adap);

platform_set_drvdata(pdev, i2c);

return 0;

}

是不是清爽多了

函数定义了一个struct s3c24xx_i2c *i2c, 然后对其初始化

而且可以看出, 主要是对i2c->adap进行初始化

i2c->adap指向一个struct i2c_adapter结构体, 可以翻译为i2c适配器, 来看下

/*

* i2c_adapter is the structure used to identify a physical i2c bus along

* with the access algorithms necessary to access it.

*/

struct i2c_adapter {

struct module *owner;

unsigned int id;

unsigned int class; /* classes to allow probing for */

const struct i2c_algorithm *algo; /* the algorithm to access the bus */

void *algo_data;

/* --- administration stuff. */

int (*client_register)(struct i2c_client *);

int (*client_unregister)(struct i2c_client *);

/* data fields that are valid for all devices */

u8 level; /* nesting level for lockdep */

struct mutex bus_lock;

struct mutex clist_lock;

int timeout; /* in jiffies */

int retries;

struct device dev; /* the adapter device */

int nr;

struct list_head clients; /* DEPRECATED */

char name[48];

struct completion dev_released;

};

struct i2c_adapter中还有个const struct i2c_algorithm *algo

将i2c_adapter简称为adp, i2c_algorithm简称为algo

具体讲一下这两个数据结构有什么用:

I2C子系统主要有三部分组成: core, adap, algo

core是i2c子系统的核心层, 将与具体硬件相关的代码交给adap

而adap含有一个algo, algo定义了与具体硬件通讯的代码所以

如果有一个应用程序希望与i2c设备通讯, 将依次通过core, adap, algo, 最终将数据发到总线上



继续看probe初始化过程, ret = s3c24xx_i2c_init(i2c);

static int s3c24xx_i2c_init(struct s3c24xx_i2c *i2c)

{

unsigned long iicon = S3C2410_IICCON_IRQEN | S3C2410_IICCON_ACKEN;

struct s3c2410_platform_i2c *pdata;

unsigned int freq;

/* get the plafrom data */

pdata = i2c->dev->platform_data;

/* inititalise the gpio */

if (pdat

[1] [2] [3]
I2C子系统s3c2440

『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
采用AT89C2051的数字可调稳压电源单片机源程序