linux i2c驱动分析

来源:互联网 发布:我国教育经费机制知 编辑:程序博客网 时间:2024/03/29 09:30

1.Linux的I2C驱动架

Linux中I2C总线的驱动分为两个部分,总线驱动(BUS)和设备驱动(DEVICE)。其中总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在在那里,等待设备驱动调用其函数。

设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。

(Sun备注:也许只看这样的说明有点不明白,可以联系一下301的I2C是如何做的。总线驱动就相对于driver项目中的I2C子项目,是如何对I2C控制器操作函数,提供有通过控制器发送/接收一个/多个字节,停止/启动传输等操作,但他并没有针对哪个I2C设备操作。设备驱动相对于FM模块驱动,FM模块驱动中会调用I2C总线驱动接口向FM物理模块发送/接收字节,操作FM模块正常工作.)

1.1. 总线驱动

在系统开机时,首先装载的是I2C总线驱动。一个总线驱动用于支持一条特定的I2C总线的读写(是否可以这样理解一个总线驱动对应于一个物理I2C控制器)。一个总线驱动通常需要两个模块,一个struct i2c_adapter和一个struct i2c_algorithm来描述

 

static struct i2c_adapter pb1550_board_adapter =

{

  name:              "pb1550 adapter",

  id:                I2C_HW_AU1550_PSC,

  algo:              NULL,

  algo_data:         &pb1550_i2c_info,

  inc_use:           pb1550_inc_use,

  dec_use:           pb1550_dec_use,

  client_register:   pb1550_reg,

  client_unregister: pb1550_unreg,

  client_count:      0,

};

这个样例挂接了一个叫做“pb1550 adapter”的驱动。(第一个模块和第二个模块并不对应,暂且把au1550_algo看做是pb1550_board_adapter的操作函数集)。但这个模块并未提供读写函数,具体的读写方法由第二个模块,struct i2c_algorithm提供。

 

static struct i2c_algorithm au1550_algo =

{

 .name         = "Au1550 algorithm",

 .id      = I2C_ALGO_AU1550,

 .master_xfer  = au1550_xfer,

 .functionality     = au1550_func,

};


i2c_adap->algo = &au1550_algo; //(这句是否应该为pb1550_board_adapter->algo = &au1550_algo)

这个样例给上述总线驱动增加了读写“算法”。通常情况下每个I2C总线驱动都定义一个自己的读写算法,但鉴于有些总线使用相同的算法,因而可以共用同一套读写函数。本例中的驱动定义了自己的读写算法模块,起名叫“Au1550 algorithm”。

全部填妥后,通过调用:

 

i2c_add_adapter(i2c_adap); //(该为i2c_add_adapter(pb1550_board_adapter))

将这两个模块注册到操作系统里,总线驱动就算装上了。对于AMD au1550,这部分已经由AMD提供了。

1.2 设备驱动

如前所述,总线驱动只是提供了对一条总线的读写机制,本身并不会去做通信。通信是由I2C设备驱动来做的,设备驱动透过I2C总线同具体的设备进行通讯。一个设备驱动有两个模块来描述,struct i2c_driver和struct i2c_client

当系统开机、I2C总线驱动装入完成后,就可以装入设备驱动了。首先装入如下结构:

 

static struct i2c_driver driver =

{

   .name           = "i2c TV tuner driver",

   .id             = I2C_DRIVERID_TUNER,

   .flags          = I2C_DF_NOTIFY,

   .attach_adapter = tuner_probe,

   .detach_client  = tuner_detach,

   .command        = tuner_command,

};


i2c_add_driver(&driver);

 

       其中struct i2c_driver并不具体的操作I2C设备,它只是实现设备与总线的挂接,而挂接在总线上的设备是千差万别;如果挂接的设备是字符设备,就要实现文件操作接口,即实现具体设备xxx的xxx_read(),xxx_write()和xxx_ioctrl()函数等。

 

这个i2c_driver一旦装入完成,其中的attach_adapter函数就会被调用。在其中可以遍历系统中的每个i2c总线驱动,探测想要访问的设备(设备驱动一方面遍历系统中每个i2c总线,另一方面探测要访问的设备):

 

static int tuner_probe(struct i2c_adapter *adap)

{

 return i2c_probe(adap, &addr_data, tuner_attach);

}

注意探测可能会找到多个设备,因而不仅一个I2C总线可以挂多个不同类型的设备,一个设备驱动也可以同时为挂在多个不同I2C总线上的设备服务。

每当设备驱动探测到了一个它能支持的设备,它就创建一个struct i2c_client来标识这个设备

 

new_client->addr = address;

new_client->adapter = adapter;           //设备所挂载的i2c总线

new_client->driver = &driver;               //设备所对应的设备驱动


/* Tell the I2C layer a new client has arrived */

err = i2c_attach_client(new_client);

if (err)

    goto error;

可见,一个i2c_client代表着位于adapter总线上,地址为address,使用driver来驱动的一个设备。它将总线驱动与设备驱动,以及设备地址绑定在了一起。一个i2c_client就代表着一个I2C设备

当得到I2C设备后,就可以直接对此设备进行读写:

 

/*

 * The master routines are the ones normally used to transmit data to devices

 * on a bus (or read from them). Apart from two basic transfer functions to

 * transmit one message at a time, a more complex version can be used to

 * transmit an arbitrary number of messages without interruption.

 */


extern int i2c_master_send(struct i2c_client *,const char* ,int);

extern int i2c_master_recv(struct i2c_client *,char* ,int);

与通常意义上的读写函数一样,这两个函数对i2c_client指针指定的设备,读写int个char。返回值为读写的字节数。对于我们现有的SLIC的驱动,只要将最后要往总线上进行读写的数据引出传输到这两个函数中,移植工作就算完成了,我们将得到一个Linux版的I2C设备驱动。