1.I2C概念
2.I2C硬件结构图
3.I2C总线初始化
4.I2C控制器device 节点添加及driver注册
5.I2C设备节点添加及driver注册
5.adapter设备及驱动添加要点及绑定过程
6.client设备及驱动添加要点及绑定过程
7.设备是如何使用I2C通讯的
I2C是philips提出的外设总线.
I2C只有两条线,一条串行数据线:SDA,一条是时钟线SCL ,使用SCL,SDA这两根信号线就实现了设备之间的数据交互,它方便了工程师的布线。
因此,I2C总线被非常广泛地应用在EEPROM,实时钟,小型LCD等设备与CPU的接口中
当前很多ARM内部封装了I2C控制芯片,通过cpu引脚会将控制芯片相关接口暴露出来,通过查看CPU引脚手册会发现存在几组I2C控制引脚(从软件角度讲一个控制器对应一个adapter),将支持I2C设备的SDA与SCL与其中一个控制芯片的对应引脚连接在一起即从硬件上将设备挂载到了I2C控制芯片上,cpu即可通过软件的相关操作来与设备进行通信了
I2C设备分两类:(当前RTC芯片支持I2C通信)
1.采用GPIO模拟
2.设备支持I2C即存在SDA和SCL引脚
如图2.2所示,控制器与设备是一对多的关系,那么控制器是如何寻找特定的设备呢?
从控制器的原理图我们看出控制器实际上只有SCL与SDA两个引脚,针对SDA采用分时复用,在和设备通讯时首先通过SDA发送设备的地址,然后再发送和设备交互的数据,此即与I2C协议挂钩了
I2C总线核心在于i2c-core.c,其注册方式与一般的总线注册方式一样,唯一的区别在于我们注册了一个空的dummy_driver,具体原因留给读者自己分析。
注意I2c总线初始化调用的是postcore_initcall(i2c_init);,查看其具体的宏我们得知其优先级为2,请留意该优先级,因为与后续的I2c设备驱动注册优先级存在强相关,必须要先有I2c总线,才能将i2c设备挂载到I2c总线上(稍后我们进行具体分析)
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
分析i2c_init,其只做了2件事,其一添加了一个空的driver,其二想内核注册了I2c总线,至此I2c总线注册完成(so easy!)
注册总线做了哪些事情呢:
分析bus_register得知,申请了一个i2c总线的结构体,并将父节点设置为bus,同时将i2c总线结构体挂入bus的维护链表,后续通过遍历该维护链表即可得知支持哪些总线,在sys/bus目录下创建i2c,i2c/driver,i2c/devices目录
//i2c-core.c
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
static struct i2c_driver dummy_driver = {
.driver.name = "dummy",
.probe = dummy_probe,
.remove = dummy_remove,
.id_table = dummy_id,
};
static int __init i2c_init(void)
{
int retval;
retval = of_alias_get_highest_id("i2c");
down_write(&__i2c_board_lock);
if (retval >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = retval + 1;
up_write(&__i2c_board_lock);
retval = bus_register(&i2c_bus_type);
if (retval)
return retval;
is_registered = true;
#ifdef CONFIG_I2C_COMPAT
i2c_adapter_compat_class = class_compat_register("i2c-adapter");
if (!i2c_adapter_compat_class) {
retval = -ENOMEM;
goto bus_err;
}
#endif
retval = i2c_add_driver(&dummy_driver);
if (retval)
goto class_err;
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier));
return 0;
class_err:
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
is_registered = false;
bus_unregister(&i2c_bus_type);
return retval;
}
static void __exit i2c_exit(void)
{
if (IS_ENABLED(CONFIG_OF_DYNAMIC))
WARN_ON(of_reconfig_notifier_unregister(&i2c_of_notifier));
i2c_del_driver(&dummy_driver);
#ifdef CONFIG_I2C_COMPAT
class_compat_unregister(i2c_adapter_compat_class);
#endif
bus_unregister(&i2c_bus_type);
tracepoint_synchronize_unregister();
}
/* We must initialize early, because some subsystems register i2c drivers
* in subsys_initcall() code, but are linked (and initialized) before i2c.
*/
postcore_initcall(i2c_init);
module_exit(i2c_exit);
Notes:查看i2c_core.c,看似十分庞大,其实只有3块,smbus(无需关注),i2c总线注册,i2c适配接口,其中后面2者才是我们需要关注的重点
文件路径:arch/arm/mach-s3c24xx/mach-mini2440.c
以platform总线形式注册了i2c的控制器adapter
调用流程图:注意体会arch_initcall与i2c驱动注册调用的postcore_initcall优先级别
arch_initcall(customize_machine);
-customize_machine()
-mini2440_init()
-s3c_i2c0_set_platdata()
-s3c_set_platdata()
-i2c_register_board_info()
-platform_add_devices()
-platform_device_register()
-device_initialize()
-arch_setup_pdev_archdata()
-platform_device_add()
-device_add()
//设备的资源信息,例如设备的地址,中断号等
static struct resource s3c_i2c0_resource[] = {
//adapter设备对应的物理内存及内存空间,对应datasheet上模块的物理地址的基地址
//用处:在驱动初始化硬件信息时用于对相关寄存器设置
[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),
//datasheet上划分给该adapter对应的中断号
[1] = DEFINE_RES_IRQ(IRQ_IIC),
};
//adapter设备信息,如支持多个控制器,则下列为设备信息数组,driver侧也应该位数组
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",//设备名,用于驱动加载时匹配
.id = 0,//adapter标签,如支持多个控制器,则均有属于自己不同的id编号
.num_resources = ARRAY_SIZE(s3c_i2c0_resource),
.resource = s3c_i2c0_resource,
};
//adapter控制器通用设置,如具体控制器不同导致的差异可在自身资源再次标注后面予以修正
struct s3c2410_platform_i2c default_i2c_data __initdata = {
.flags = 0,
.slave_addr = 0x10,//i2c控制器作为从机时地址
.frequency = 100*1000,//时钟频率为100khz
.sda_delay = 100,//sda间隔时间
};
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
//1.将作为s3c芯片的特定硬件资源信息s3c_device_i2c0注入平台资源s3c_device_i2c0的dev->platform_data中
//2.将平台对应的gpio配置函数注入
struct s3c2410_platform_i2c *npd;
if (!pd) {pd = &default_i2c_data;pd->bus_num = 0;}npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c), &s3c_device_i2c0);if (!npd->cfg_gpio)npd->cfg_gpio = s3c_i2c0_cfg_gpio;}
i2c_register_board_info(int busnum,
struct i2c_board_info const *info, unsigned len)
{
int status;
down_write(&__i2c_board_lock);
/* dynamic bus numbers will be assigned after the last static one */
if (busnum >= __i2c_first_dynamic_bus_num)
__i2c_first_dynamic_bus_num = busnum + 1;
for (status = 0; len; len--, info++) {
struct i2c_devinfo *devinfo;
devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
if (!devinfo) {
pr_debug("i2c-core: can't register boardinfo!\n");
status = -ENOMEM;
break;
}
devinfo->busnum = busnum;
devinfo->board_info = *info;
list_add_tail(&devinfo->list, &__i2c_board_list);
}
up_write(&__i2c_board_lock);
return status;
}
static struct platform_device *mini2440_devices[] __initdata = {
&s3c_device_ohci,
&s3c_device_wdt,
&s3c_device_i2c0,
&s3c_device_rtc,
&s3c_device_usbgadget,
&mini2440_device_eth,
&mini2440_led1,
&mini2440_led2,
&mini2440_led3,
&mini2440_led4,
&mini2440_button_device,
&s3c_device_nand,
&s3c_device_sdi,
&s3c_device_iis,
&uda1340_codec,
&mini2440_audio,
};
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
Notes:platform_add_devices详见平台总线章节
文件路径:kernel/drivers/i2c/busses/i2c-s3c2410.c
驱动开发都是基于面向对象的思想的, 内核虽然给我们封装了很多"类", 但当我们开发一个具体的驱动的时候, 还是要对其进行"继承", 进而创建针对具体设备的资源对象, 资源对象管理着驱动中诸多函数的共用资源, 是整个驱动运行过程中资源管理者与桥梁, 主要包括:内核类+资源(io, irq,时钟, 寄存器)+状态表示+其他,所以, 设计驱动的工作中很重要的一个工作就是"设计资源类". 下面就是三星设计的类, 我把次要的部分剔除了资源类,资源对象是整个驱动运作的核心, 所有的方法需要的资源都是对这个对象的操作, 它的设计是迭代的过程, 但当整个框架搭起来之后, 不应该有大的变化
struct s3c24xx_i2c {
wait_queue_head_t wait;
unsigned int quirks;
unsigned int suspended:1;
struct i2c_msg *msg;//收到的i2c-core.c发送过来的i2c_msg对象数组首地址
unsigned int msg_num;//i2c_msg数组的元素个数
unsigned int msg_idx;//i2c_msg数组元素的索引
unsigned int msg_ptr;
unsigned int tx_setup;
unsigned int irq;//使用的中断号
enum s3c24xx_i2c_state state;//当前控制器的状态, 用枚举量表示STATE_IDLE, STATE_START,STATE_READ,STATE_WRITE,STATE_STOP
unsigned long clkrate;//时钟频率
void __iomem *regs;
struct clk *clk;//时钟
struct device *dev;//属于device, 按照device来管理
struct i2c_adapter adap;//构造并使用的i2c_adapter对象, 和上一篇的框架图对应
struct s3c2410_platform_i2c *pdata;//封装的平台信息, 是一个数组首地址, 每一个元素包括从机地址, 标志位, 总线编号等
int gpios[2];
struct pinctrl *pctrl;
#ifdef CONFIG_CPU_FREQ
struct notifier_block freq_transition;
#endif
};
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name = "s3c2410-i2c",
.driver_data = 0,
}, {
.name = "s3c2440-i2c",
.driver_data = QUIRK_S3C2440,
}, {
.name = "s3c2440-hdmiphy-i2c",
.driver_data = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
}, { },
};
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table = of_match_ptr(s3c24xx_i2c_match),
},
};
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);
s3c24xx_i2c_probe:
adapter为控制器实际的初始化执行实体,其具体的调用流程如下:
subsys_initcall(i2c_adap_s3c_init);
-i2c_adap_s3c_init()
-platform_driver_register()
-driver_register()
-bus_add_driver()
-driver_attach()
-bus_for_each_dev()
-__driver_attach()
-driver_match_device
-drv->bus->match//(platform_bus_type.match = platform_match)
-platform_match_id()//如存在多个adpater,则逐一比较列表各成员找到匹配项目(s3c24xx_driver_ids包含3个)
-strcmp(pdev->name, drv->name)//如只有1个i2c adpater则直接比较name
-driver_probe_device()
-really_probe()
-dev->bus->probe(dev)//(i2c_bus_type.probe = i2c_device_probe)
-i2c_device_probe()
-driver->probe()//(s3c24xx_i2c_driver.probe = s3c24xx_i2c_probe)
-s3c24xx_i2c_probe()
功能:以平台总线方式注册adapter驱动
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;//绑定总线类别,主要注意name,dev_attrs,match
if (drv->probe) //当前已经被填充,不会再次填充
drv->driver.probe = platform_drv_probe;
if (drv->remove)//当前已经被填充,不会再次填充
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)//需要填充
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
int bus_add_driver(struct device_driver *drv)
{
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);//加载驱动
if (error)
goto out_unregister;
} //创建sys子系统下文件目录
module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent);
if (error) {
......
}
error = driver_add_attrs(bus, drv);if (error) {
......
}
}
int driver_attach(struct device_driver *drv)
{
//attach链路上所有驱动
return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
static int __driver_attach(struct device *dev, void *data)
{
......
//用name来比较看之前是否有此驱动对应的设备添加进来,如发现匹配则进如driver内设备初始化函数即device_driver.probe()
if (!driver_match_device(drv, dev))
return 0;
......
if (!dev->driver)
driver_probe_device(drv, dev);
......
return 0;
}
int driver_probe_device(struct device_driver *drv, struct device *dev)
{
......
ret = really_probe(dev, drv);//只是简单的封装really_probe
......
return ret;
}
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
static int really_probe(struct device *dev, struct device_driver *drv)
{
......
//如挂在总线上,先经总线的probe转一把再进入对应设备的driver->probe
if (dev->bus->probe) {
ret = dev->bus->probe(dev);//,当前我们挂在plat_form总线上,如上所示其probe为NULL
if (ret)
goto probe_failed;
else if (drv->probe) {
设备:硬件设备物理信息的封装
驱动:软件层次对硬件物理信息的设置
由此可见,驱动最终是对硬件信息的获取以及对硬件寄存器的设置,分层的主要原因就是解耦合,进入此函数前请仔细回顾devices的添加,此处操作硬件资源与其息息相关
static int s3c24xx_i2c_probe(struct platform_device *pdev)//即为4.1.2中s3c_device_i2c0
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
struct resource *res;
int ret;
//表示设备是否通过设备树添加方式获得
if (!pdev->dev.of_node) {
pdata = pdev->dev.platform_data;//查看4.1.3得知编写设备文件时设置为platform_data = default_i2c_data
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
//pdev->dev是device类型, 以它为的detach为标志分配一个我们自己的对象的空间并将分配的首地址返回给i2c。 这里使用的是devm_kzalloc(),
函数 devm_kzalloc()和kzalloc()一样都是内核内存分配函数,但是devm_kzalloc()是跟设备(device)有关的,当设备(device)被detached或者
驱动(driver)卸载(unloaded)时,内存会被自动释放。另外,当内存不在使用时,可以使用函数devm_kfree()释放。而kzalloc()则需要手动释放
(使用kfree()),但如果工程师检查不仔细,则有可能造成内存泄漏
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata) {
dev_err(&pdev->dev, "no memory for platform data\n");
return -ENOMEM;
}
//如果在--1079--中获得了相应的s3c2410_platform_i2c对象地址,就将其拷贝到资源对象中的相应的域存起来,否则自己去设备树中找
i2c->quirks = s3c24xx_get_device_quirks(pdev);
if (pdata)
memcpy(i2c->pdata, pdata, sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);//获取slave_addr,frequency,sda_delay信息(详见4.1.2default_i2c_data)
//使用赋值的方式直接对一部分资源对象的域进行初始化
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;
//初始化资源对象中的等待队列头wait_queue_head_t wait
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
//初始化资源对象中的device dev
i2c->dev = &pdev->dev;
//初始化资源对象中的struct clk
i2c->clk = devm_clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return -ENOENT;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
/* map the registers */
//获取pdev中的地址resource, ioremap之后用于初始化资源对象中的regs域, 使用的是devm_ioremap_resource(), 同样是基于device的资源自动回收API
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c->regs))
return PTR_ERR(i2c->regs);
dev_dbg(&pdev->dev, "registers %p (%p)\n",
i2c->regs, res);
/* setup info block for the i2c core */
//将自定义资源对象指针藏到algo_data中, 和--1203--的作用一样, 给xfer()接口函数用
i2c->adap.algo_data = i2c;
//初始化资源对象中的i2c_adapter对象中的部分成员, 指定其父设备是控制器设备的device域
i2c->adap.dev.parent = &pdev->dev;
//初始化资源对象中的pctrl域, 使用的是devm_pinctrl_get_select_default()
i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);
/* inititalise the i2c gpio lines */
//使用to_platform_device(其实就是container_of)通过i2c->dev找到包含它的platform_device对象, 回调cfg_gpio()函数, 配置GPIO引脚(见4.1.3)
if (i2c->pdata->cfg_gpio) {
i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
} else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) {
return -EINVAL;
}
/* initialise the i2c controller */
//初始化时钟
clk_prepare_enable(i2c->clk);
//初始化adapter相关寄存器
ret = s3c24xx_i2c_init(i2c);
clk_disable_unprepare(i2c->clk);
if (ret != 0) {
dev_err(&pdev->dev, "I2C controller init failed\n");
return ret;
}
/* 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);
if (ret <= 0) {
dev_err(&pdev->dev, "cannot find IRQ\n");
return ret;
}
//注册中断, devm_request_irq
ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0,
dev_name(&pdev->dev), i2c);
if (ret != 0) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
return ret;
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
return ret;
}
/* 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对象, 总线编号是来自于设备(4.1.3 bus_num)
i2c->adap.nr = i2c->pdata->bus_num;
i2c->adap.dev.of_node = pdev->dev.of_node;
ret = i2c_add_numbered_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
s3c24xx_i2c_deregister_cpufreq(i2c);
return ret;
}
//将构造的adapter对象注册到内核
of_i2c_register_devices(&i2c->adap);
//设置私有数据, pdev->dev->p->driver_data = i2c; 由于i2c->dev==pdev->dev, 所以其实就是将资源对象的首地址赋值给藏到device->device_private->driver_data中, 因为所有的接口都是使用platform_device作为形参的, 这种方法可以方便的找到自定义资源对象, 所以才叫void * driver_data
platform_set_drvdata(pdev, i2c);
//设置dev的电源管理
pm_runtime_enable(&pdev->dev);
//设置adap的电源管理
pm_runtime_enable(&i2c->adap.dev);
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return 0;
}