linux系统下,不管什么设备(总线驱动、总线设备驱动..),挂接在总线上都分两种资源:1.驱动设备资源(驱动设备资源注册)2.驱动(针对设备的驱动注册)

所以在实现和学习驱动的时候,就分这两块来进行。

一:我会查找i2c驱动设备资源添加(i2c适配器平台资源)

//Mach-mini2440.c (arch\arm\mach-s3c24xx)

(1)

//平台数据

struct s3c2410_platform_i2c default_i2c_data __initdata = {
.flags= 0,
.slave_addr= 0x10,//从机地址
.frequency= 100*1000,//总线工作频率
.sda_delay= 100, //数据传输延时
};

s3c_i2c0_set_platdata(NULL);//平台数据设置

s3c_i2c0_set_platdata()函数将S3C2440上的I2C控制器进行了一些初始化,但是并没有写入硬件寄存器,仅仅是保存在了s3c2410_platform_i2c结构体中

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
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);
/* s3c_i2c0_cfg_gpio为 配置I2C控制器GPIO函数指针 */
if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;
}

void __init *s3c_set_platdata(void *pd, size_t pdsize,
     struct platform_device *pdev)
{
void *npd;
if (!pd) {
/* too early to use dev_name(), may not be registered */
printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
return NULL;
}
npd = kmemdup(pd, pdsize, GFP_KERNEL);
if (!npd) {
printk(KERN_ERR "%s: cannot clone platform data\n", pdev->name);
return NULL;
}
/*最后将struct device 中的platform_data指针直指向了初始化后的 s3c2410_platform_i2c结构体  */
pdev->dev.platform_data = npd;
return npd;
}

(2)

platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));

主要是将开发平台的设备注册进了系统,也就是将device注册到了platform虚拟的总线上,并进行了一些初始化的工作

-》platform_device_register(devs[i]);-》device_initialize(&pdev->dev)-》

将设备加入到平台中。mini2440_devices包含所有的划分为平台的总线驱动设备的资源,其中包括&s3c_device_i2c0

//Devs.c (arch\arm\plat-samsung)

//平台资源

static struct resource s3c_i2c0_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),//i2c控制器寄存器的地址范围
[1] = DEFINE_RES_IRQ(IRQ_IIC),//中断号
};
//平台资源
struct platform_device s3c_device_i2c0 = {

/*设备名,platform总线的match函数中会用设备名和驱动名的比较来绑定设备和驱动程序*/
.name = "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1
.id = 0,
#else
.id = -1,
#endif
.num_resources= ARRAY_SIZE(s3c_i2c0_resource),
.resource = s3c_i2c0_resource,
};

以上就是编写i2c总线驱动的时候,需要构造的总线驱动设备资源。

上面是一些板级的硬件设备资源向系统的注册,没有设计到具体的硬件操作,在加载驱动程序时,驱动程序会根据已经注册到系统的具体设备的硬件资源进行初始化,也就是进行一些硬件操作,控制硬件设备的正常工作,下面来分析驱动程序的加载过程。

二:i2c驱动注册

在 kernel中提供了两个adapter注册接口,分别为i2c_add_adapter()和i2c_add_numbered_adapter().由于在系统中可能存在多个adapter,因为将每一条I2C总线对应一个编号,下文中称为 I2C总线号.这个总线号的PCI中的总线号不同.它和硬件无关,只是软件上便于区分而已.
对于i2c_add_adapter()而言,它使用的是动态总线号,即由系统给其分析一个总线号,而i2c_add_numbered_adapter()则是自己指定总线号,如果这个总线号非法或者是被占用,就会注册失败.

//I2c-s3c2410.c (drivers\i2c\busses)

/* device driver for platform bus bits */
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);
}

其中probe是加载driver后调用以初始化设备的,结构体中的id_table中的name是用来和device也就是适配器设备配对的,两者名字一样即配对成功,在有的版本的内核中,driver结构体中没有.id_table的赋值,与device的配对是通过driver中的name来进行配对的。

1:i2c总线初始化函数s3c24xx_i2c_probe

这个函数的主要目的是对I2C总线进行初始化,方法是:1、先声明一个s3c24xx_i2c结构体 然后对其初始化一些基本信息,我们可以看到里面就包含信息 .name = "s3c2410-i2c", 正好和之前驱动结构体里面的name吻合(这个是i2c_adper结构中的成员,也就是说一个适配器与一个平台设备相匹配)。2、将传入的平台结构体(其他程序调用驱动的时候应该会付给一个平台结构体)中的一些不用修改的信息付给i2c(也就是s3c24xx_i2c结构体),这样就构造出一个我们想要的i2c结构体,其中包含的是完整信息,然后再将i2c付给平台结构体,使用platform_set_drvdata(pdev, i2c);这条语句,这样平台结构就关联上了I2C数据。

在设备与驱动匹配成功后,会执行s3c24xx_i2c_probe()函数,其源码如下:

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
struct resource *res;
int ret;
if (!pdev->dev.of_node) {

//s3c_i2c0_set_platdata(NULL);->s3c_set_platdata;->pdev->dev.platform_data = npd;
pdata = pdev->dev.platform_data;

if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
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;
}
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);
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;//maybe the num of retry
i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup     = 50;/* delay time here to ensure the data byte has gotten onto the bus
* before the transaction is started */
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev, "i2c");//clk_get从一个时钟list链表中以字符id名称来查找一个时钟clk结构体并且返回
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}

dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
//clk_get_rate函数可以从clk_get得到的某设备结构体中获得该设备的时钟频率
clk_enable(i2c->clk);//使能对应的外设时钟源,系统启动的时候暂时不需要的时钟是disable的,需要的时候enable

/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
dev_err(&pdev->dev, "cannot find IO resource\n");
ret = -ENOENT;
goto err_clk;
}

//request_mem_region函数并没有做实际性的映射工作,只是告诉内核要使用一块内存地址,声明占有,也方便内核管理这些资源
i2c->ioarea = request_mem_region(res->start, resource_size(res),
pdev->name);
if (i2c->ioarea == NULL) {
dev_err(&pdev->dev, "cannot request IO\n");
ret = -ENXIO;
goto err_clk;
}
i2c->regs = ioremap(res->start, resource_size(res));
if (i2c->regs == NULL) {
dev_err(&pdev->dev, "cannot map IO\n");
ret = -ENXIO;
goto err_ioarea;
}
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;

/* inititalise the i2c gpio lines */
if (i2c->pdata->cfg_gpio) {
i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
} else if (s3c24xx_i2c_parse_dt_gpio(i2c)) {
ret = -EINVAL;
goto err_iomap;
}

/* initialise the i2c controller */
ret = s3c24xx_i2c_init(i2c);
if (ret != 0) {
dev_err(&pdev->dev, "I2C controller init failed\n");
goto err_iomap;
}
/* 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");
goto err_iomap;
}
ret = request_irq(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);
goto err_iomap;
}
ret = s3c24xx_i2c_register_cpufreq(i2c);//给i2c中注册一个cpufreq(变频通知机制)驱动
if (ret < 0) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
goto err_irq;
}
/* 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 = i2c->pdata->bus_num;//The bus number to use。使用的总线地址
i2c->adap.dev.of_node = pdev->dev.of_node;//Associated device tree node
//使用静态总线号来声明IIC适配器
ret = i2c_add_numbered_adapter(&i2c->adap);//注册I2C的adapter,否则i2c设备无法被监测到
if (ret < 0) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
goto err_cpufreq;
}
//of_i2c_register_devices接口会解析i2c总线节点的子节点(挂载在该总线上的i2c设备节点),
//获取i2c设备的地址、中断号等硬件信息。
//然后调用request_module()加载设备对应的驱动文件,调用i2c_new_device(),生成i2c设备
of_i2c_register_devices(&i2c->adap);
platform_set_drvdata(pdev, i2c);

pm_runtime_enable(&pdev->dev);
pm_runtime_enable(&i2c->adap.dev);

dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
clk_disable(i2c->clk);//??为什么要disable clock??,知道的可以留言,谢谢了
return 0;

err_cpufreq:
s3c24xx_i2c_deregister_cpufreq(i2c);
 err_irq:
free_irq(i2c->irq, i2c);
 err_iomap:
if (i2c->gpios[0])
s3c24xx_i2c_dt_gpio_free(i2c);
iounmap(i2c->regs);
 err_ioarea:
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
 err_clk:
clk_disable(i2c->clk);
clk_put(i2c->clk);
 err_noclk:
return ret;
}

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->pdata;
/* write slave address */
/* 写入从设备的地址  */
writeb(pdata->slave_addr, i2c->regs + S3C2410_IICADD);
dev_info(i2c->dev, "slave address 0x%02x\n", pdata->slave_addr);
/* 使能接收发送中断和I2C总线应答信号  */
writel(iicon, i2c->regs + S3C2410_IICCON);
/* we need to work out the divisors for the clock... */
/*这里freq用来获取实际的I2C时钟频率,具体指为97KHZ,后面会分析*/
if (s3c24xx_i2c_clockrate(i2c, &freq) != 0) {
writel(0, i2c->regs + S3C2410_IICCON);
dev_err(i2c->dev, "cannot meet bus frequency required\n");
return -EINVAL;
}
/* todo - check that the i2c lines aren't being dragged anywhere */
dev_info(i2c->dev, "bus frequency set to %d KHz\n", freq);
dev_dbg(i2c->dev, "S3C2410_IICCON=0x%02lx\n", iicon);
return 0;
}

static int s3c24xx_i2c_clockrate(struct s3c24xx_i2c *i2c, unsigned int *got)
{
struct s3c2410_platform_i2c *pdata = i2c->pdata;
unsigned long clkin = clk_get_rate(i2c->clk);/*从系统平台时钟队列中获取pclk的时钟频率,大小为50MHZ */
unsigned int divs, div1;
unsigned long target_frequency;
u32 iiccon;
int freq;
i2c->clkrate = clkin;
clkin /= 1000; /* clkin now in KHz */
dev_dbg(i2c->dev, "pdata desired frequency %lu\n", pdata->frequency);
target_frequency = pdata->frequency ? pdata->frequency : 100000;
target_frequency /= 1000; /* Target frequency now in KHz */
/* 目标频率在前面default_i2c_data0中frequency为100KHZ,根据PCLK和目标频率计算分频系数,计算后实际频率为97KHZ,即freq 为97K*/
freq = s3c24xx_i2c_calcdivisor(clkin, target_frequency, &div1, &divs);
if (freq > target_frequency) {
dev_err(i2c->dev,
"Unable to achieve desired frequency %luKHz." \
" Lowest achievable %dKHz\n", target_frequency, freq);
return -EINVAL;
}
*got = freq;/*通过传入的指针返回实际频率 */
/* 根据时钟选择和分频系数配置对应硬件寄存器 */
iiccon = readl(i2c->regs + S3C2410_IICCON);
iiccon &= ~(S3C2410_IICCON_SCALEMASK | S3C2410_IICCON_TXDIV_512);
iiccon |= (divs-1);
if (div1 == 512)
iiccon |= S3C2410_IICCON_TXDIV_512;
writel(iiccon, i2c->regs + S3C2410_IICCON);
/*  判断是否为S3C2440  */
if (i2c->quirks & QUIRK_S3C2440) {
unsigned long sda_delay;
if (pdata->sda_delay) {
sda_delay = clkin * pdata->sda_delay;
sda_delay = DIV_ROUND_UP(sda_delay, 1000000);
sda_delay = DIV_ROUND_UP(sda_delay, 5);
if (sda_delay > 3)
sda_delay = 3;
sda_delay |= S3C2410_IICLC_FILTER_ON;
} else
sda_delay = 0;
dev_dbg(i2c->dev, "IICLC=%08lx\n", sda_delay);
writel(sda_delay, i2c->regs + S3C2440_IICLC);
}
return 0;
}

iic总线驱动(适配器驱动)详解相关推荐

  1. 《Windows驱动开发技术详解》学习笔记

    Abstract   如果推荐 Windows 驱动开发的入门书,我强烈推荐<Windows驱动开发技术详解>.但是由于成书的时间较早,该书中提到的很多工具和环境都已不可用或找不到,而本文 ...

  2. 15、Windows驱动开发技术详解笔记(11) 基本概念

    9.Windows驱动程序的入口函数规定为_DriverEntry@8,所以用C++编写时要用extern. 驱动程序中,不能使用编译器运行时函数,甚至C语言中的malloc,C++的new函数都不能 ...

  3. STM32L475 SPI驱动LCD ST7789V2详解

    概述 最近在学习正点原子潘多拉开发板,在此结合原子哥的代码, 对SPI驱动LCD做一个详细介绍. TFTLCD 和 SPI TFTLCD介绍 TFT-LCD 即薄膜晶体管液晶显示器.其英文全称为:Th ...

  4. c语言windows驱动编程入门,Windows驱动开发技术详解 PDF扫描版[175MB]

    Windows驱动开发技术详解由浅入深.循序渐进地介绍了windows驱动程序的开发方法与调试技巧.本书共分23章,内容涵盖了windows操作系统的基本原理.nt驱动程序与wdm驱动程序的构造.驱动 ...

  5. 9、Windows驱动开发技术详解笔记(5) 基本语法回顾

    5.在驱动中获取系统时间 1)获取启动毫秒数 在ring3 我们可以通过一个GetTickCount 函数来获得自系统启动开始的毫秒数,在ring0也有一个与之对应的KeQueryTickCount ...

  6. 激光二极管驱动电路图大全(六款激光二极管驱动电路设计原理图详解) - 全文

    激光二极管驱动电路图大全(六款激光二极管驱动电路设计原理图详解) - 全文 来源:网络整理 作者:2018年03月01日 14:36 1 分享 订阅 关键词:驱动电路激光二极管 激光二极管驱动电路图( ...

  7. 驱动开发函数详解之Wdm

    驱动开发函数详解之Wdm 前言 IRP(I/O Request Package) == 输入输出请求包 上层应用程序需要访问底层输入输出设备时,发出I/O请求,系统会把这些请求转化为IRP数据,不同的 ...

  8. STM32通过IIC读取MPU6050原始数据过程详解

    STM32通过IIC读取MPU6050数据过程详解 一:硬件介绍 此款MPU6050是通过IIC来与MCU通信的,它有两个IIC接口,第一个是主IIC,通过SCL和SDA两条线与MCU通信:第二个辅助 ...

  9. IIC(I2C)协议详解

    1.简介 IIC,即I²C,全称 Inter-Integrated Circuit,字面上的意思是集成电路之间,它其实是I²C Bus简称,所以中文应该叫 集成电路总线 ,它是一种串行通信总线,使用多 ...

  10. linux小红帽系统装打印机驱动,Linux系统详解 第三篇:红帽RHEL的安装

    Linux系统详解 第二篇:红帽RHEL的安装 前言: 本系列文章取材广泛,有来自于互联网的,有来自教科书的,有来自自己的笔记的,也有来自自己对Linux的经验积累的.此系列的文章都是经过长时间的整理 ...

最新文章

  1. db2 springboot 整合_springboot的yml配置文件通过db2的方式整合mysql的教程
  2. JavaScript = TypeScript 类入门
  3. 0012-求滑动距离
  4. leetcode 703. 数据流中的第K大元素 最小堆解法 c语言
  5. Android中引入第三方Jar包的方法(java.lang.NoClassDefFoundError)
  6. C++ STL : 模拟实现STL中的容器适配器stack和queue
  7. 【BZOJ1814】Ural 1519 Formula 1 插头DP
  8. 视频|每日CeBIT:沃森机器人走进生活、三星无线很便利
  9. allure报告---动态显示模块名和用例标题
  10. oracle+技术面试,Oracle技术面试问题
  11. 以太坊搭建联盟链_区块链知识普及:什么是以太坊
  12. 【Data guard】SWITCHOVER_STATUS为FAILED DESTINATION解决办法
  13. 《C语言入门经典》读后感(一)
  14. 深入了解Unity剔除(草稿)
  15. 【抽象代数】环、子环、理想、商环、环的同态
  16. python3 爬虫神器pyquery的使用实例之爬网站图片
  17. 使用 javaScript 编写倒计时小程序,到时提交表单
  18. php重载求圆锥体积,编写一函数文件,实现求一个圆锥体的体积。
  19. C++中如何判断一个字符串中包含另一个字符串
  20. 关于ebay平台接口(php)对接示例

热门文章

  1. Inno Setup打包exe
  2. 导入技能要素三大类_教学能力综合训练(李平)-中国大学mooc-题库零氪
  3. FastRNABindR:快速准确预测蛋白质-RNA界面残基
  4. 为什么100M的带宽测试网速还不到10M/s?
  5. 混合开发架构|Android工程集成React Native、Flutter、ReactJs
  6. mysql 中文匹配_MYSQL-中文检索匹配与正则表达式
  7. 并发编程1 - 线程基础及其常见方法
  8. 98%的人都不会使用这6种地图可视化方法,学会直接涨薪5K
  9. 软考初级程序员上午单选题(20)
  10. 5 Business logic vulnerabilities 业务逻辑漏洞