"./drivers/i2c/busses/i2c-s3c2410.c"是3.14.0内核中三星SoC的i2c控制器驱动程序, 本文试图通过对这个程序的分析, 剥离繁复的细节, 总结一套编写i2c主机控制器驱动的框架以及一个分析内核驱动的流程.

匹配之前

1287 static int __init i2c_adap_s3c_init(void)
1288 {
1289 return platform_driver_register(&s3c24xx_i2c_driver);
1290 }
1291 subsys_initcall(i2c_adap_s3c_init);

--1291-->将主机控制器驱动在系统启动的时候就注册好

--1289-->这个驱动是基于platform总线的, 设备信息的部分在板级文件i2c_board_info中描述并作为platform_device随内核启动被注册, 所以控制器驱动在系统启动的时候就可以工作了

1275 static struct platform_driver s3c24xx_i2c_driver = {
1276 .probe = s3c24xx_i2c_probe,
1277 .remove = s3c24xx_i2c_remove,
1278 .id_table = s3c24xx_driver_ids,
1279 .driver = {
1280 .owner = THIS_MODULE,
1281 .name = "s3c-i2c",
1282 .pm = S3C24XX_DEV_PM_OPS,
1283 .of_match_table = of_match_ptr(s3c24xx_i2c_match),
1284 },
1285 };

既然是遵循的platform编写, 那么所有的信息都要在一个platform_driver中描述, 分析也是围绕这个对象展开

--1276-->probe函数, 最重要的函数

--1278-->用于匹配的id表, 由于是平台文件编写的设备信息, 所以会使用这个域作为匹配的依据, 如下

 132 static struct platform_device_id s3c24xx_driver_ids[] = {
133 {
134 .name = "s3c2410-i2c",
135 .driver_data = 0,
136 }, {
137 .name = "s3c2440-i2c",
138 .driver_data = QUIRK_S3C2440,
139 }, {
140 .name = "s3c2440-hdmiphy-i2c",
141 .driver_data = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
142 }, { },
143 };

我们可以在"arch/arm/plat-samsung"中找到相应的设备信息

 485 struct platform_device s3c_device_i2c0 = {
486 .name = "s3c2410-i2c",
487 .id = 0,
488 .num_resources = ARRAY_SIZE(s3c_i2c0_resource),
489 .resource = s3c_i2c0_resource,
490 };

二者一匹配, probe就执行!

匹配之后

一旦匹配上, 分析流程就会有点变化, 驱动开发都是基于面向对象的思想的, 内核虽然给我们封装了很多"类", 但当我们开发一个具体的驱动的时候, 还是要对其进行"继承", 进而创建针对具体设备的资源对象, 资源对象管理着驱动中诸多函数的共用资源, 是整个驱动运行过程中资源管理者与桥梁, 主要包括:内核类+资源(io, irq,时钟, 寄存器)+状态表示+其他,所以, 设计驱动的工作中很重要的一个工作就是"设计资源类". 下面就是三星设计的类, 我把次要的部分剔除了

资源类

资源对象是整个驱动运作的核心, 所有的方法需要的资源都是对这个对象的操作, 它的设计是迭代的过程, 但当整个框架搭起来之后, 不应该有大的变化

 103 struct s3c24xx_i2c {
104 wait_queue_head_t wait;
108 struct i2c_msg *msg;
109 unsigned int msg_num;
110 unsigned int msg_idx;
111 unsigned int msg_ptr;
113 unsigned int tx_setup;
114 unsigned int irq;
116 enum s3c24xx_i2c_state state;
117 unsigned long clkrate;
119 void __iomem *regs;
120 struct clk *clk;
121 struct device *dev;
122 struct i2c_adapter adap;
124 struct s3c2410_platform_i2c *pdata;
125 int gpios[2];
130 };

struct s3c24xx_i2c

--108-->收到的i2c-core.c发送过来的i2c_msg对象数组首地址

--109-->i2c_msg数组的元素个数

--110-->i2c_msg数组元素的索引

--114-->使用的中断号

--116-->当前控制器的状态, 用枚举量表示STATE_IDLE, STATE_START,STATE_READ,STATE_WRITE,STATE_STOP

--117-->时钟频率

--120-->时钟

--121-->属于device, 按照device来管理

--122-->构造并使用的i2c_adapter对象, 和上一篇的框架图对应

--124-->封装的平台信息, 是一个数组首地址, 每一个元素包括从机地址, 标志位, 总线编号等

probe

probe主要负责"申请资源+初始化+提供接口", 通过对probe的分析, 就可以对整个驱动的构建有一个

提纲挈领的理解

1072 static int s3c24xx_i2c_probe(struct platform_device *pdev)
1073 {
1074 struct s3c24xx_i2c *i2c;
1075 struct s3c2410_platform_i2c *pdata = NULL;
1076 struct resource *res;
1077 int ret;
1078
1079 if (!pdev->dev.of_node) {
1080 pdata = dev_get_platdata(&pdev->dev);
1085 }
1086
1087 i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
1092
1093 i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
1098
1099 i2c->quirks = s3c24xx_get_device_quirks(pdev);
1100 if (pdata)
1101 memcpy(i2c->pdata, pdata, sizeof(*pdata));
1102 else
1103 s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
1104
1105 strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
1106 i2c->adap.owner = THIS_MODULE;
1107 i2c->adap.algo = &s3c24xx_i2c_algorithm;
1108 i2c->adap.retries = 2;
1109 i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
1110 i2c->tx_setup = 50;
1111
1112 init_waitqueue_head(&i2c->wait);
1113
1114 /* find the clock and enable it */
1116 i2c->dev = &pdev->dev;
1117 i2c->clk = devm_clk_get(&pdev->dev, "i2c");
1124
1126 /* map the registers */
1128 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
1129 i2c->regs = devm_ioremap_resource(&pdev->dev, res);
1136
1137 /* setup info block for the i2c core */
1139 i2c->adap.algo_data = i2c;
1140 i2c->adap.dev.parent = &pdev->dev;
1141
1142 i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);
1143
1144 /* inititalise the i2c gpio lines */
1146 if (i2c->pdata->cfg_gpio) {
1147 i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
1148 } else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) {
1149 return -EINVAL;
1150 }
1151
1152 /* initialise the i2c controller */
1154 clk_prepare_enable(i2c->clk);
1155 ret = s3c24xx_i2c_init(i2c);
1156 clk_disable_unprepare(i2c->clk);
1161 /* find the IRQ for this unit (note, this relies on the init call to
1162 * ensure no current IRQs pending
1163 */
1165 if (!(i2c->quirks & QUIRK_POLL)) {
1166 i2c->irq = ret = platform_get_irq(pdev, 0);
1171
1172 ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, 0,
1173 dev_name(&pdev->dev), i2c);
1179 }
1180
1181 ret = s3c24xx_i2c_register_cpufreq(i2c);
1192
1193 i2c->adap.nr = i2c->pdata->bus_num;
1194 i2c->adap.dev.of_node = pdev->dev.of_node;
1196 ret = i2c_add_numbered_adapter(&i2c->adap);
1202
1203 platform_set_drvdata(pdev, i2c);
1204
1205 pm_runtime_enable(&pdev->dev);
1206 pm_runtime_enable(&i2c->adap.dev);
1209 return 0;
1210 }

s3c24xx_i2c_probe()

--1074-1077-->准备好指针与变量, 准备从传入的对象中提取数据, 虽说这是C89的语法要求, 但这种写法确实比较舒服, 遇到不认识的变量就去函数开头找

--1079-->如果pdev->dev.of_node为空, 表示设备不是通过设备树获得的, 那么就调用dev_get_platdata获取pdev->dev.oplatform_data中的数据, 显然, 在编写设备文件的时候这里藏的是一个s3c2410_platform_i2c对象, 所以我们用pdata取出来以备使用

--1087-->pdev->dev是device类型, 以它为的detach为标志分配一个我们自己的对象的空间并将分配的首地址返回给i2c。 这里使用的是devm_kzalloc(), 函数 devm_kzalloc()和kzalloc()一样都是内核内存分配函数,但是devm_kzalloc()是跟设备(device)有关的,当设备(device)被detached或者驱动(driver)卸载(unloaded)时,内存会被自动释放。另外,当内存不在使用时,可以使用函数devm_kfree()释放。而kzalloc()则需要手动释放(使用kfree()),但如果工程师检查不仔细,则有可能造成内存泄漏

--1100-1103-->如果在--1079--中获得了相应的s3c2410_platform_i2c对象地址,就将其拷贝到资源对象中的相应的域存起来,否则自己去设备树中找

--1106-1110-->使用赋值的方式直接对一部分资源对象的域进行初始化

--1112-->初始化资源对象中的等待队列头wait_queue_head_t wait

--1116-->初始化资源对象中的device *dev

--1117-->初始化资源对象中的struct *clk

--1128-->获取pdev中的地址resource, ioremap之后用于初始化资源对象中的regs域, 使用的是devm_ioremap_resource(), 同样是基于device的资源自动回收API

--1139-->将自定义资源对象指针藏到algo_data中, 和--1203--的作用一样, 给xfer()接口函数用

--1140-->初始化资源对象中的i2c_adapter对象中的部分成员, 指定其父设备是控制器设备的device域

--1142-->初始化资源对象中的pctrl域, 使用的是devm_pinctrl_get_select_default()

--1147-->使用to_platform_device(其实就是container_of)通过i2c->dev找到包含它的platform_device对象, 回调cfg_gpio()函数, 配置GPIO引脚

--1154-->初始化时钟

--1166-->获取中断资源

--1171-->注册中断, devm_request_irq

--1193-1194-->初始化i2c->adap对象, 总线编号是来自于设备的

--1196-->将构造的adapter对象注册到内核

--1203-->设置私有数据, pdev->dev->p->driver_data = i2c; 由于i2c->dev==pdev->dev, 所以其实就是将资源对象的首地址赋值给藏到device->device_private->driver_data中, 因为所有的接口都是使用platform_device作为形参的, 这种方法可以方便的找到自定义资源对象, 所以才叫void * driver_data

--1205-->设置dev的电源管理

--1206-->设置adap的电源管理

s3c24xx_i2c_algorithm

probe中我们最关心的就是这个--1107--实现的接口了, i2c-core最终就是通过algo->xfer将设备驱动的数据发送出去的, 是一个硬件相关的函数

 787 static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
788 .master_xfer = s3c24xx_i2c_xfer,
789 .functionality = s3c24xx_i2c_func,
790 };
 748 static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs, int num)
750 {
751 struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
758 for (retry = 0; retry < adap->retries; retry++) {
760 ret = s3c24xx_i2c_doxfer(i2c, msgs, num);
770 udelay(100);
771 }
776 }

s3c24xx_i2c_xfer()

--760-->循环调用发送函数, 函数的实现如下, 可以看到其中对寄存器的读写, 设备驱动中的发送的请求, 就是通过这些readl(), writel()来实现的.

--770-->时序要求!

 256 static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,
257 struct i2c_msg *msg)
258 {
275 /* todo - check for whether ack wanted or not */
276 s3c24xx_i2c_enable_ack(i2c);
277
278 iiccon = readl(i2c->regs + S3C2410_IICCON);
279 writel(stat, i2c->regs + S3C2410_IICSTAT);
280
281 dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);
282 writeb(addr, i2c->regs + S3C2410_IICDS);
287 ndelay(i2c->tx_setup);
288
290 writel(iiccon, i2c->regs + S3C2410_IICCON);
291
292 stat |= S3C2410_IICSTAT_START;
293 writel(stat, i2c->regs + S3C2410_IICSTAT);
294
295 if (i2c->quirks & QUIRK_POLL) {
296 while ((i2c->msg_num != 0) && is_ack(i2c)) {
297 i2c_s3c_irq_nextbyte(i2c, stat);
298 stat = readl(i2c->regs + S3C2410_IICSTAT);
299
300 if (stat & S3C2410_IICSTAT_ARBITR)
301 dev_err(i2c->dev, "deal with arbitration loss\n");
302 }
303 }
304 }

Linux i2c子系统(四) _从i2c-s3c24xx.c看i2c控制器驱动的编写的更多相关文章

  1. Linux i2c子系统(一) _动手写一个i2c设备驱动

    i2c总线是一种十分常见的板级总线,本文以linux3.14.0为参考, 讨论Linux中的i2c驱动模型并利用这个模型写一个mpu6050的驱动, 最后在应用层将mpu6050中的原始数据读取出来 ...

  2. Linux usb子系统(一) _写一个usb鼠标驱动

    USB总线是一种典型的热插拔的总线标准,由于其优异的性能几乎成为了当下大小设备中的标配. USB的驱动可以分为3类:SoC的USB控制器的驱动,主机端USB设备的驱动,设备上的USB Gadget驱动 ...

  3. Linux i2c子系统(二) _通过i2c-dev.c访问设备的方法

    另外一种驱动 应用层除了使用上述的使用i2c_driver接口来访问i2c设备,Linux内核还提供了一种简单粗暴的方式--直接通过虚拟i2c设备驱动的方式,即上一篇中的i2c-dev提供的方式,这种 ...

  4. Linux i2c子系统(三) _解决probe无法执行

    如果你也遇到了填充了id_match_table,compitible怎么看都一样,但probe就是不执行(让我哭一会),你可以回头看一下上一篇的模板,我们这里虽然使用的是设备树匹配,但和platfo ...

  5. Linux时间子系统(四) timekeeping

    一.前言 timekeeping模块是一个提供时间服务的基础模块.Linux内核提供各种time line,real time clock,monotonic clock.monotonic raw ...

  6. Linux输入子系统(一) _驱动编码

    输入设备都有共性:中断驱动+字符IO,基于分层的思想,Linux内核将这些设备的公有的部分提取出来,基于cdev提供接口,设计了输入子系统,所有使用输入子系统构建的设备都使用主设备号13,同时输入子系 ...

  7. 十四、i2c子系统

    由于之后的触摸屏驱动分析中使用到了GPIO子系统和i2c子系统,因此在分析触摸屏驱动之前我准备把这两个子系统进行简单分析. 在读者学习本章以及后续i2c相关章节之前,最好了解i2c通信方式,可以参考: ...

  8. 嵌入式Linux内核I2C子系统详解

    1.1 I2C总线知识 1.1.1  I2C总线物理拓扑结构     I2C总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成.通信原理是通过对SCL和SDA线高 ...

  9. linux之i2c子系统架构---总线驱动

    编写i2c设备驱动(从设备)一般有两种方式: 1.用户自己编写独立的从设备驱动,应用程序直接使用即可. 2.linux内核内部已经实现了一个通用的设备驱动,利用通用设备驱动编写一个应用程序(用户态驱动 ...

随机推荐

  1. Keepalived+Nginx提供前端负载均衡+主从双机热备+自动切换

    原文链接:http://unun.in/linux/156.html 方案: 采用两台Nginx服务器作为前端,提供静态web内容,分发web请求,一主一从,Keepalived实现状态监测,保证 N ...

  2. layer弹窗插件实战用法小结1—— layer.alert()

    http://layer.layui.com 第一节:layer.alert()弹窗的用法 1.解压layer-v2.2.zip压缩包 2.拷贝layer文件夹到实战项目目录 3.注意:layer.j ...

  3. BNU Online Judge-34777-Magical GCD

    题目链接 http://www.bnuoj.com/bnuoj/problem_show.php?pid=34777 题意 如样例 输入 1530 60 20 20 20 输出 80 如  30 和 ...

  4. Servlet 获取IllegelStateException

    Servlet 获取IllegelStateException: response提交之后,进行requestDispatcher.forwar(),会产生这样的问题: 但是必须是outputStre ...

  5. KB奇遇记(5):奇葩的用人制度

    8月份入职,公司不给我们正式任命,导致了我们开展工作困难重重,基本上很少有人会鸟你,做事仿佛名不正言不顺.哪怕你是未来信息部的老大也一样,网管们根本不买你的账.所以做ERP选型,做旧OA的选型以及加密 ...

  6. GIS制图课程前言

    一直以来都想写一本关于电子地图制图的工具书,把过去所遇到的关于电子地图制图的种种方法.技巧和问题进行总结和归纳,传播电子地图制图的思路和心得. 从2010年开始,陆陆续续在华南地区开展了多场的电子地图 ...

  7. 2017<java技术>预备作业计科冀浩然

    1.阅读邹欣老师的博客,谈谈你期望的师生关系是什么样的? 我期望的师生关系是相互融洽的,老师能够在上课的时候尽量多的教我们专业知识,可以尽量多和我们进行互动,课下能和我们如同朋友一般就可以了. 2.你 ...

  8. 如何创建DLL文件

    动态链接库(DLL)是从C语言函数库和Pascal库单元的概念发展而来的.所有的C语言标准库函数都存放在某一函数库中.在链接应用程序的过程中,链接器从库文件中拷贝程序调用的函数代码,并把这些函数代码添 ...

  9. Python之文件与目录操作及压缩模块(os、shutil、zipfile、tarfile)

    Python中可以用于对文件和目录进行操作的内置模块包括: 模块/函数名称 功能描述 open()函数 文件读取或写入 os.path模块 文件路径操作 os模块 文件和目录简单操作 zipfile模 ...

  10. 酷派8702手机打开logcat

    机器在出厂时将log的级别做了限制,方法是:拨号盘输入*20121220# -> 选择日志输出级别 -> 选择Java log level -> 选择LOGD即可.