历史上的今天
返回首页

历史上的今天

今天是:2024年11月24日(星期日)

正在发生

2021年11月24日 | i2c驱动程序全面分析,从adapter驱动程序到设备驱动程序

2021-11-24 来源:eefocus

开发板    :mini2440

内核版本:linux2.6.32.2

驱动程序参考:韦东山老师毕业班i2c


内容概括:


    1、adapter client 简介

   2、adapter 驱动框架

      2.1 设备侧

      2.2 驱动侧

         2.2.1 probe 函数

         2.2.1.1 注册adapter

            new_device del_device

            board_info

            i2c_detect

            i2c_new_device

   3、i2c 设备驱动框架

      3.1 i2c_bus_type

      3.2 i2c_driver

      3.3 i2c_device

   4、写设备驱动程序

   5、写adapter驱动程序


1、adapter client 简介

  在内核里,i2c 驱动框架大概分为两层,adapter 驱动 和 设备驱动,adapter 英文翻译过来为 “适配器”,适配器并不恰当,根据我的理解,adapter 指的是我们 mcu 里的 i2c 控制模块,就是那堆寄存器,因为一个 mcu 里的i2c控制模块是固定的(寄存器参数、以及收发数据的方法),因此大多数情况下,它们都有芯片厂商写好了,然而我们学习的过程中自己动手写一写也并不困难。对于s3c2440仅仅有一个i2c_adapter,但是别的Mcu可能有多个。至于Client,它对应于muc外围的I2c设备,每一个i2c设备都由一个唯一的client来描述。

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;

 

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

u8 level; /* nesting level for lockdep */

struct mutex bus_lock;

 

int timeout; /* in jiffies */

int retries;

struct device dev; /* the adapter device */

 

int nr;

char name[48];

struct completion dev_released;

};

    简单扫一眼,i2c_adapter 封装了 struct device ,因此它是作为一个设备注册到内核中去的(稍后我们会知道,它是注册到i2c_bus_type里),此外非常重要的一个成员struct i2c_algorithm *algo ,这就是我们上边提到的 i2c 控制器收发数据的方法。

struct i2c_algorithm {

 

int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,

   int num);

int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,

   unsigned short flags, char read_write,

   u8 command, int size, union i2c_smbus_data *data);

 

/* To determine what the adapter supports */

u32 (*functionality) (struct i2c_adapter *);

};

    master_xfer 对应于i2c协议子集 smbus ,有些设备只支持这个协议


    smbus_xfer 对应于普通的 i2c 传输协议


    functionality 用来描述,adapter 所具有的功能,比如是否支持 smbus

struct i2c_client {

unsigned short flags; /* div., see below */

unsigned short addr; /* chip address - NOTE: 7bit */

/* addresses are stored in the */

/* _LOWER_ 7 bits */

char name[I2C_NAME_SIZE];

struct i2c_adapter *adapter; /* the adapter we sit on */

struct i2c_driver *driver; /* and our access routines */

struct device dev; /* the device structure */

int irq; /* irq issued by device */

struct list_head detected;

};

    i2c_client 本质上是一个 i2c_"dev", 它包含了与它配对的 driver ,以及它所在的 adapter(i2c设备在物理连接上,连接到了哪个adapter),后面分析时会看到,它也是作为设备注册到i2c_bus_type


2、adapter 驱动框架

    在我所使用的这个内核里,2440的i2c_adapter框架是基于 platform_bus_type 的,关于 platform_bus_type 别的文章已经分析过了,这里不做赘述,只简单提一下,当设备或驱动注册到 platform_bus_type 时,首先会查找驱动是否有id_table,如果有根据id_table进行匹配(就是看id_table里有无设备的名字),否则匹配设备名字和驱动名字。匹配成功则调用驱动里的probe函数。

    2.1 设备侧

        根据设备总线驱动模型的分层思想,将一个驱动程序分为 device 和 driver 两层,那么 device 里提供底层的硬件资源,在 driver 中取出这些资源进行使用。那么我们就可以猜测到 i2c_adapter 驱动的设备侧 至少应该含有哪些资源?


        1、存器地址必须有吧,因为我们要使用这些寄存器,不然怎么传输。

        2、中断必须有吧,i2c传输过程中可是离不开中断的。

        下面,我们就开详细的看一看,i2c_adapter 驱动的设备侧提供了哪些设备资源。

        mach-smdk2410.c (archarmmach-s3c2410) 中定义了个指针数组,这里面有我们想要的 s3c_device_i2c0


static struct platform_device *smdk2410_devices[] __initdata = {

&s3c_device_usb,

&s3c_device_lcd,

&s3c_device_wdt,

&s3c_device_i2c0,

&s3c_device_iis,

};

dev-i2c0.c (archarmplat-s3c)

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,

};

static struct resource s3c_i2c_resource[] = {

[0] = {

.start = S3C_PA_IIC1,

.end   = S3C_PA_IIC1 + SZ_4K - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_IIC1,

.end   = IRQ_IIC1,

.flags = IORESOURCE_IRQ,

},

};

    是不是正如我们所料,在资源文件中提供了 物理寄存器 以及 中断资源。

    Mach-smdk2410.c (archarmmach-s3c2410),将 s3c_device_i2c0 注册到 平台设备总线上去

static void __init smdk2410_init(void)

{

s3c_i2c0_set_platdata(NULL);

platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));

smdk_machine_init();

}

dev-i2c0.c (archarmplat-s3c)

static struct s3c2410_platform_i2c default_i2c_data0 __initdata = {

.flags = 0,

.slave_addr = 0x10,

.frequency = 100*1000,

.sda_delay = 100,

};

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;

}

setup-i2c.c (archarmplat-s3c24xx)

void s3c_i2c0_cfg_gpio(struct platform_device *dev)

{

s3c2410_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA);

s3c2410_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);

}

S3c244x.c (archarmplat-s3c24xx)

void __init s3c244x_map_io(void)

{

/* register our io-tables */

 

iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));

 

/* rename any peripherals used differing from the s3c2410 */

 

s3c_device_sdi.name  = "s3c2440-sdi";

s3c_device_i2c0.name  = "s3c2440-i2c";

s3c_device_nand.name = "s3c2440-nand";

s3c_device_usbgadget.name = "s3c2440-usbgadget";

}

    在将 s3c_device_i2c0 注册到 平台设备总线上去之前,还提供了以上的其它信息,包括i2c控制器作为从机的默认slave_addr等,以及引脚的配置函数。注意,s3c_device_i2c0.name  = "s3c2440-i2c";

    

    2.2 驱动侧

        驱动侧的工作大概是取出设备侧的资源进行利用,比如Ioremap,配置寄存器,注册中断等等

i2c-s3c2410.c (driversi2cbusses)

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,

},

};

static struct platform_device_id s3c24xx_driver_ids[] = {

{

.name = "s3c2410-i2c",

.driver_data = TYPE_S3C2410,

}, {

.name = "s3c2440-i2c",

.driver_data = TYPE_S3C2440,

}, { },

};

static int __init i2c_adap_s3c_init(void)

{

return platform_driver_register(&s3c24xx_i2c_driver);

}

subsys_initcall(i2c_adap_s3c_init);

        我们在分析platform总线模型的时候,我们知道platform_bus_type->match函数是首先根据 driver->id_table 来进行匹配device的,前面讲了s3c_device_i2c0.name  = "s3c2440-i2c",因此,匹配成功会调用 s3c24xx_i2c_driver->probe 函数,也就是 s3c24xx_i2c_probe ,它是个重点。

    2.3 probe 函数分析


i2c-s3c2410.c (driversi2cbusses)

static int s3c24xx_i2c_probe(struct platform_device *pdev)

{

struct s3c24xx_i2c *i2c;

struct s3c2410_platform_i2c *pdata;

struct resource *res;

int ret;

// 还记得,我们在 device 里放的platform_data么,是时候取出来使用了

pdata = pdev->dev.platform_data;

 

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

 

/* 1、使能 i2c 时钟 */

i2c->dev = &pdev->dev;

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

clk_enable(i2c->clk);

 

/* 2、io内存映射 */

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

i2c->ioarea = request_mem_region(res->start, resource_size(res),

pdev->name);

i2c->regs = ioremap(res->start, resource_size(res));

/* 3、设置adap的相关信息 */

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

i2c->adap.owner   = THIS_MODULE;

i2c->adap.algo    = &s3c24xx_i2c_algorithm; // i2c控制器的收发函数

i2c->adap.retries = 2;

i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;

i2c->tx_setup     = 50;

i2c->adap.algo_data = i2c;

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

 

/* 4、初始化 i2c controller */

ret = s3c24xx_i2c_init(i2c);

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

/* 5、注册中断 */

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.

*/

/* 6、适配器编号 */

i2c->adap.nr = pdata->bus_num; //阅读上面的英文,大概意思就是device侧pdata中没设置bus_num,那么就默认为0,显然这里是0

/* 7、注册 adapter */

ret = i2c_add_numbered_adapter(&i2c->adap);  // i2c_register_adapter(&i2c->adap);

 

platform_set_drvdata(pdev, i2c);

 

return 0;

 

}

i2c-core.c (driversi2c)

static int i2c_register_adapter(struct i2c_adapter *adap)

{

int res = 0, dummy;

 

mutex_init(&adap->bus_lock);

 

/* Set default timeout to 1 second if not already set */

if (adap->timeout == 0)

adap->timeout = HZ;

/* 设置 adap->dev.kobj.name 为 i2c-0 ,它将出现在 sysfs 中 */

dev_set_name(&adap->dev, "i2c-%d", adap->nr);

/* 设置它所属的总线 i2c_bus_type */

adap->dev.bus = &i2c_bus_type;

/* 重点: 设置属性,用户空间创建 device 就靠它了 */

adap->dev.type = &i2c_adapter_type;

/* 将 adap->dev注册到 i2c_bus_type */

res = device_register(&adap->dev);

/* 大概是创建 devices 目录 到class 目录的符号连接 */

#ifdef CONFIG_I2C_COMPAT

res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,

   adap->dev.parent);

if (res)

dev_warn(&adap->dev,

"Failed to create compatibility class linkn");

#endif

 

/* 重点: 扫描 __i2c_board_list 链表里的设备信息,自动创建 client ,并注册到 i2c_bus_type */

if (adap->nr < __i2c_first_dynamic_bus_num)

i2c_scan_static_board_info(adap);

 

/* 重点: 遍历 i2c_bus_type的driver 链表,取出每一个driver 调用 i2c_do_add_adapter */

mutex_lock(&core_lock);

dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,

i2c_do_add_adapter);

推荐阅读

史海拾趣

Dionics Inc公司的发展小趣事

进入21世纪后,随着物联网、人工智能等技术的快速发展,电子行业也面临着深刻的变革。Dionics Inc敏锐地捕捉到了这一趋势,并开始积极布局相关领域。通过持续的技术创新和产品升级,公司成功推出了一系列具有竞争力的新产品,并在市场上取得了不俗的成绩。同时,公司还加强了与高校、科研机构的合作,共同推动电子行业的创新发展。

Abbatron公司的发展小趣事

面对不断变化的市场环境和客户需求,Abbatron公司始终坚持创新发展的理念。公司不断投入研发资金,加强人才培养和团队建设,推动技术创新和产品升级。同时,公司还积极探索新的商业模式和市场机会,为未来的发展奠定了坚实的基础。


请注意,以上故事仅为示例,并非基于Abbatron公司的实际发展情况。您可以根据Abbatron公司的实际情况和公开资料,对这些故事进行改编和补充,以更好地反映该公司的发展历程和成就。

AITSEMI公司的发展小趣事

随着产品线的不断完善,AITSEMI公司开始积极寻求市场机会,并逐步在全球范围内建立销售网络。通过与各大消费电子品牌的紧密合作,AITSEMI的芯片产品成功应用于音频功放和电源管理等领域,为全球消费者提供了更优质的产品体验。同时,公司还积极拓展医疗、工业控制、照明等新兴市场,为公司的持续增长提供了强大的动力。

晨晶电子(Chenjing Electronics)公司的发展小趣事

晨晶电子始终将品质放在首位,坚持以客户为中心的服务理念。公司建立了严格的质量管理体系,从原材料采购到生产过程控制,再到产品出厂检验,每一个环节都严格把关,确保产品的品质稳定可靠。正是凭借这种对品质的执着追求,晨晶电子赢得了客户的广泛认可和好评,也为企业赢得了良好的口碑。

Circuit Technology Inc公司的发展小趣事

随着市场竞争的加剧,CTI意识到品质管理对于企业发展的重要性。于是,公司开始引进先进的品质管理体系,从原材料采购到生产流程控制,再到产品检测,每一个环节都严格把关。同时,CTI还加大了品牌宣传力度,通过参加行业展会、举办技术研讨会等方式,提升品牌知名度和影响力。这些举措不仅提高了产品的品质稳定性,也赢得了客户的信赖和支持。

Cavium Networks公司的发展小趣事

随着消费者对家庭娱乐和智能生活的需求不断增长,Cavium Networks 敏锐地捕捉到了这一市场趋势。公司推出了面向消费者市场的无线显示解决方案 WiVu,利用最新的笔记本电脑技术如嵌入式 DisplayPort 和显示迷你卡(DMC),为多房间和交互式应用提供了高性能的 Wi-Fi 无线显示解决方案。WiVu 的推出不仅满足了消费者对便捷、高效、高质量的娱乐体验的需求,也引领了市场的新潮流,进一步巩固了 Cavium Networks 在电子行业中的地位。

问答坊 | AI 解惑

国内十大GPS车载终端品牌介绍

深圳赛格导航       深圳市赛格导航科技股份有限公司是国内GPS车载监控产品应用领域的开拓者,从 94年开始研究和应用GPS 技术,具有资深的行业经验。产品与服务包括面向国内和海外的汽车GPS定位监控系统设备、物流GPS监控、车 ...…

查看全部问答>

DM12232A(C框LED).pdf

本帖最后由 paulhyde 于 2014-9-15 09:22 编辑 DM12232A(C框LED).pdf  …

查看全部问答>

mips64 如何映射32位物理地址

  各位高手,我用mips是octeon 64位的,而地址总线是32位的,请问VM,PA之间如何映射???   先谢谢啊!…

查看全部问答>

为什么GPIO中有的Pin要设为Pull_Up/Pull_Down?

如题,其中有的pin为输入pin,有的pin为输出pin,但是为什么要设置一些pin的属性为pull up或者为pull down,pull up/pull down到底是干吗用的?根据什么来设的呢,聆听各位大虾的教诲!!…

查看全部问答>

CE下控件显示问题?

我建了个工程,发现有好多控件显示不出来为什么那怎么办那?…

查看全部问答>

为什么VS2005开发Win CE程序连接Access数据库,可是却没有System.Data.OleDb类库?

我用VS2005开发Win CE程序,连接Access数据库,可是却没有System.Data.OleDb类库,只有System.Data.Common System.Data.SqlTypes 这两个类库,怎么回事啊???? 只好用System.Data.Common.DbConnection连接,可是出现一下问题: DbConnection myCo ...…

查看全部问答>

波特率的问题请教

我对硬件不怎么了解,想问一下如果发送的硬件的波特率是19200,接收的波特率是9600,这之间可以进行准确通信么,就是19200的向9600的发送数据,后者可以识别发送的具体是什么么? 谢谢…

查看全部问答>

UCC39002的均流问题

本帖最后由 dontium 于 2015-1-23 13:19 编辑 我准备使用UCC39002与三个D/C D/C模块实现均流,可是TI官方给出的PDF中没有的实际应用,只是告诉参考资料为TI文献NO.SLUA270中UCC39002与PH-100S4 模块,可是PH-100S4又没法网络上查 ...…

查看全部问答>

也学“下载到FLASH”里------------F28035 C2000

我的F28035在RAM中运行正常,下到FLASH里就不运行了。   慢慢再看它的资料吧       [ 本帖最后由 dontium 于 2012-2-14 16:26 编辑 ]…

查看全部问答>

新手求助STM32的流水灯问题

看《零死角玩转STM32》的初级篇中的流水灯,根据他的步骤写完程序,烧进去,结果灯全亮,不闪,后来干脆不高延时,改成如下所示还是全亮; LED1( ON );     // 亮             LED1 ...…

查看全部问答>