Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1951260
  • 博文数量: 185
  • 博客积分: 10707
  • 博客等级: 上将
  • 技术积分: 1777
  • 用 户 组: 普通用户
  • 注册时间: 2008-09-19 17:31
文章分类

全部博文(185)

文章存档

2014年(1)

2012年(6)

2011年(27)

2010年(13)

2009年(75)

2008年(63)

分类: LINUX

2009-03-31 09:15:28

这是一个为那些想以Linux系统作为协议宿主机(不是从机)写内核I2C或系统总线设备驱动的简单引导.

建立一个驱动,你必须做几件事,有些是可选的,或有相些是相似的或有些是完全不相同的.这个文档只是一个指引,而不是一个规则手册.

基本注意
========

尽可能保持内核空间干净.最好的办法是为所有的全局符号加上唯一的前缀.这对输出符号来说是特别重要的,同样,那对不需要输出的符号来说也是一个好主意.在这个手册中,我们用'foo_' 为前缀,而 'FOO_' 即是预处理对象的前缀.

驱动结构
========

通常,实现一个单独的驱动结构,而用它来示例所有的从设备.记住,一个驱动结构包含基本的访问程序,同时除了你提供的数据域以外的域你都必需0初始化.一个客户端包含着设备特定的信息,如驱动模型,设备节点和它的I2C总线地址.


static struct i2c_driver foo_driver = {
    .driver = {
        .name    = "foo",
    },
    
    /*如果驱动用驱动模型,即绑定这个模型:*/
    .probe        = foo_probe,
    .remove    = foo_remove,

    /*否则,即用先前的绑定模型*/
    .attach_adapter    = foo_attach_adapter,
    .detach_client     = foo_detach_client,

    /*如果忽略了驱动模型,即用下面这些*/
    .shutdown    = foo_shutdown, /*可选的*/
    .suspend    = foo_suspend, /*可选的*/
    .resume    = foo_resume, /*可选的*/
    .command    = foo_command, /*可选的*/
}


名字域是驱动的名称,它不能包括空格,且必须与模块名相一致(如果驱动可以被编译成模块的),然而你可以用MODULE_ALIAS(在这个例中是传递"foo")来为这个模块增加别名.如果驱动名与模块名不一致时,这个模块就不会自动被加载(热插/冷插).

下面解释其它所有的回调函数域

从设备额外数据
==============

任一个客户结构都有一个特定的用来指向任何结构的数据域.你可以用它来保存设备特殊的数据,特别是在处理多I2C总线和多SMBus总线的驱动中.你不一定都需要这个,但是在传感器驱动中,它显得特别有用.


/*保存数值*/
    void i2c_set_clientdata(struct i2c_client *client, void *data);

    /*获取数值*/
    void i2c_get_clientdata(struct i2c_client *client);

下面是一个结构例子.

    struct foo_data {
        struct i2c_clien client;
        enum chips type;    /*保存传感器驱动的芯片类型*/
    /*因为 I2C总线是缓慢的,所读取设备的信息到cache一些时间(如一秒或两秒),
    有通常是有用的.那当然是看那设备是否值得那样做或者对时间是否敏感的*/

    struct mutex update_lock;        /*当我们正在读取很多信息时,别的进程不能更新以下的值*/
    char valid        /*!=0 如果下面的域是有效*/
    unsigned long last_updated; /*一瞬间*/
    /*在这里添加实际的信息*/
};


访问从设备
==========

假设我们已经有了一个有效的从设备结构.某些时候,我们需要从从设备里收集信息或者把新的信息写到从设备中去.这时候,我们多么希望这个信息对用户空间来说是次要的(或许,对一些抽象的从设备来说,我们根本不需要做这些) 但是,我们需要基本的读写程序.

我发现为它定义foo_read 的 foo_write 函数是有用的.那样更容易地直接调用I2C功能,另外一些芯片上各种各样的寄存器值定义更容易地被读取进来.

下面的函数是一个简单的例子,但是不能逐字复制.


int foo_read_value(struct i2c_client *client, u8 reg)
{
    if(reg < 0x10) /*字节大小寄存器*/
        return i2c_smbus_read_byte_data(client,reg);
    else /*字大小寄存器*/
        return i2c_smbus_read_word_data(client,reg);
}

int foo_write_value(struct i2c_client *client, u8 reg, u16 value)
{
    if(reg == 0x10) /*不可写,设备错误*/
        return -1;
    else if(reg<0x10) /*字节寄存器*/
        return i2c_smbus_write_byte_data(client,reg,value);
    else /*字寄存器*/
        return i2c_smbus_write_word_data(client,reg,value);
}


探测和捆绑
==========

最 初写的Linux I2C栈是用来支持访问PC主板上的控制芯片硬件的.它嵌入了一些假设,这些假设相应SMBus(和计算机)来说,比I2更适当.其中之一的假设是这样 的,大多数的适配器和设备驱动支持SMBUS_QUICK协议探测设备的存在.另外一个假设是,只要使用最原始的探测,设备和它们的驱动就可以充分地被设 置.

随着Linux和它的I2C栈广泛应用在嵌入式,和出现像DVB适配器这样的复杂组件,这些假设变得更有问题了.I2C设备的驱动引 发的中断需要更多(和不同)的配置信息,因为驱动处理不同的芯片,不同的芯片是不能被探测协议所标识的,或者它需要特定主板信息来正确操作它.

因 此,现在I2C栈驱动有两种模型来合并I2C设备:最先遗传模型和在Linux 2.6内核完全兼容的设备驱动模型.这些模型没有混合,SMBus类型探测之后,因为遗传模型(legacy) 需要驱动去创建"i2c_client"设备对象,而Linux驱动模型即希望在probe()函数里,驱动能给这样的一个设备对象.

标准驱动模型绑定("New Style")
-------------------------------

基本系统,典型板特定初始化代码或者启动固件来报告存在的I2C设备. 例如,或许有一个表在内核或者启动器里来表示I2C设备和把它们连接到特定主板上的配置信息,像中断,其它写入设备,芯片类型等等.它可以用来为每一个I2C设备创建i2c_client 对象.

I2C设备驱动这种绑定模型像其它类型的Linux驱动一样工作: 它们提供probe()来绑定设备,rmove()来解除绑定.



    static int foo_probe(struct i2c_client *client,
                const struct i2c_device_id *id);
    static int foo_remove(struct i2c_client *client);


记住 i2c_driver 没有创建这些从设备句柄. 句柄在foo_probe()中可能被使用.如果foo_probe()报告成功的(0而不是一个负的状态值) 它可以保存和使用这个句柄直到foo_remove()返回为止.这种绑定被大多数Linux驱动所使用.

当i2c_client.driver_name和驱动的名称相同时,驱动比较设备;这种相近的办法也被用在另外总线中,那是在硬件中没有设备类型支持的. 驱动与模块的名称必须相配,所以热插/冷插都会(模块探测)modprobe 驱动.

创建设备(标准驱动模型)
----------------------

如 果你知道一个I2C设备是连接在一个已经给定的I2C总线上的这个事实,你就可以通过用设备地址,设备名称和调用i2c_new_device()简单地 填充i2c_board_info结构来表示设备.它创建设备,并在此时驱动的核心将维护查找适当的驱动,同时调用它的probe().如果一个驱动支持 不同的设备类型,你可以指定你想要用的类型域的类型.如果必要,你也可以指定中断号的平台数据.

有时候,你知道一个设备是连接到一个给定 的I2C总线,但是你不知它使用的确定地址.TV适配器就是一个例子,一个驱动可以支持几十个稍有不同的模块,而I2C设备之间的地址都不相同.在这种情 况下,你可以使用i2c_new_probed_device()的变体,除了它在后面加一个可能的I2C地址来探测以外,它是和 i2c_new_device()相似的.在列表中第一个响应的地址的设备被创建.如果你希望在一定的地址范围内表示多于一个设备,简单的做法就是多次调 用i2c_new_probed_device().

i2c_new_device()或i2c_new_probed_device()调用典型发生在I2C总线驱动中,或许你想保存i2c_client()的返回引用以便将来使用.

设备删除(标准设备模型)
---------------------

每 个用i2c_new_device()或者i2c_new_probed_device()创建的I2c设备都可以通过调用 i2c_unregister_device()来注销.如果你不确切地调用它,它在I2C总线移除之前自动地调用,因为在设备驱动模型中,一个设备不能 比它的父类活得更长.

遗传的驱动绑定模型
--------------------

多数的i2c设备可以通过几 个I2C地址来表现;一些是依靠硬件来实现的(通过焊接一些芯片的引脚到VCC或地).另外一些也可以通过软件来改变(通过写设备特定的寄存器).一些设 备通常有一个特定的地址,但不是全是;还有一些是智能的,所以你需要为你的从设备扫描几个i2c地址,和做一些排序来决定在你的驱动中,它是否实际支持你 的设备的.

为了提供用户一个最大限度的可能性,定义了一些缺省的模型参数来决定什么地址被扫描.在i2c.h 定义了一个宏来帮助你实现那样的功能,像一些基本的探测算法.

你不必要一定使用这个参数接口,如果你不这样做,请尽量不要用 i2c_probe()这样的函数.

探测类(遗传模型)
----------------

所有的参数都是以无符号16位整数列表给出.这个列表是以I2C_CLIENT_END为结束的.

下面的列表是被用在内部的:

  normal_i2c:由模块写者来填充.
    一个I2C地址列表必须被常规地检查.
  探测: 插入模块参数.
    两个参数列表.第一个参数是总线号(任何I2C总线都是 -1),
    第二个参数是I2C地址,这些地址值如果在常规的列表中也会被探测.
  忽略: 插入模块参数
    两个参数列表.第一个参数是总线号(任何I2C总线都是 -1).
    第二个参数是I2C地址,这些地址值永远不会被探测.
    这个参数只管理 'normal_i2c'列表.
  强制: 插入模块参数
    两个参数列表.第一个参数是总线号(任何I2C总线都是 -1).
    第二个参数是I2C地址,设备是盲目地被假设在给定的地址上的,不做探测.

另 外,如果驱动支持几种类型的芯片,各种强制的列表可能会被选择地定义,它们会被编组在一个命名为forces的NULL_terminated列表指针 中.这些指针是上面提及到基本的force列表中的第一个元素.每个额外增加的列表对应一个以force_插入的模块参数.

幸运的是,作为一个模块编写者,你只要定义'normal_i2c'这个参数就可以.完整的定义可能像以下这样:



    /*扫描0x4c 到 0x4f*/
    static const unsigned short normal_i2c[] = { 0x4c, 0x4d, 0x4e, 0x4f,I2C_CLIENT_END};
    
    /*魔法地定义其它所有的变量或东西*/
    I2C_CLIENT_INSMOD;
    /*或者,你的驱动支持2种设备*/
    I2C_CLIENT_INSMOD_2(foo, bar);


如果你使用多种形式,你可以定义一个枚举:
    enum chips {any_chip, foo, bar, ...}
那时你就可以(准确地)在你的驱动代码中使用它.

注意你必须调用定义了没有任务前缀的变量 'normal_i2c'.

归属到一个适配器(遗传模型)
--------------------------

无论什么时候一个新的适配器插入或者驱动为所有的适配器注册,都会调用attach_adapter()这个回调函数.之后是探测什么样的设备存在在适配器中,且各自为他们注册一个客户端.

回调函数attach_adapter是非常容易的: 我们调用基本的探测函数.这个函数用上面已经解释过的那些定义过的信息列表为我们扫描总线.如果在一个特定地址上探测到设备,另一个回调函数将会被调用.


    int foo_attach_adapter(struct i2c_adapter *adapter)
    {
        return i2c_probe(adapter, &addr_data, &foo_detect_client);
    }


记住, 'addr_data' 是上面已经解释过的宏定义,所以不必你自己定义它.

i2c_probe函数会为那些i2c地址有实际设备存在的i2c地址调用foo_detect_client函数(除非用了一个'force'的参数). 另外,那些已经使用了的地址(被另外一些从设备注册)将会被跳过.


探测从设备函数(遗传模型)
------------------------

探测从设备函数被i2c_probe()函数调用.如果参数'kind'是-1即是试探性的探测,如果是0即为强制性的探测,如果是一正值即是带有芯片类型的强制性探测.

返回不同-ENODEV的错误值将会引起探测函数停止探测:另外一些地址和适配器不再被扫描.只有发生重大或内部错误时才能这样做,像内存或者i2c_attach_client失效时.

现在,你可以忽略 'flags'这个参数,它是在将来要使用的.


int foo_detect_client(struct i2c_adapter *adapter,
             int address, int kind)
{
    int err = 0;
    int i;
    struct i2c_client    *client;
    struct foo_data     *data;
    const char *name = "";

    /*看这个适配器是否支持我们所需要的
     把这部份替代成你需要的部分*/

    if(!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA |
                         I2C_FUNC_SMBUS_WRITE_BYTE))
        goto ERROR0;
    /*好,现在我们假设已经拥有了一个合法的client.现在开始创建client结构,
    即使我们还不能完全填充它,但是它允许我们安全地访问几个i2c函数*/

    
    if(!(data = kzalloc(sizeof(struct foot_data),GFP_KERNEL))) {
        err = -ENOMEM;
        goto ERROR0;
    }

    client->addr = address;
    client->adapter = adapter;
    client->driver = &foo_driver;

    /*现在,如果'force'参数没有被利用,我们做剩下的探测*/

    /*首先,如果一些force参数被使用,一些基本的探测(如果存在)即被跳过*/
    if (kind < 0) {
    /*当然,下面这些不是真正的*/
    if (foo_read(client, FOO_REG_GENERIC) != FOO_GENERIC_VALUE) {
        goto ERROR1;
    }

    /*接着是特定的探测,这对传感器设备来说特别重要*/

    /*确定芯片类型,如果用了'force_CHIPTYPE'这个参数,即这步是不必要的*/
    if (kind <= 0) {
        i = foo_read(client, FOO_REG_CHIPTYPE);
        if (i == FOO_TYPE_1)
            kind = chip1; /*已定义在枚举里了的*/
        else if (i == FOO_TYPE_2)
            kind = chip2;
        else {
            printk("foo:Ignoring 'force' parameter for"
            "unknwn chip at adapter %d, address 0x%02x\n",
            i2c_adapter_id(adapter), address);
            goto ERROR1;
        }
    }

    /*现在设备芯片的类型的名称*/
    if (kind == chip1) {
        name = "chip1";
    } else if (kind == chip2) {
        name = "chip2";
    }

    /*填充设备剩下的域*/
    strlcpy(client->name, name, I2C_NAME_SIZE);
    data->type = kind;
    mutex_init(&data->update_lock); /*如果只有你使用该域*/

    /*另外一些初始化的数据必须在些初始化*/
    /*如果有必要,这个函数可以写一些缺省的数据到从设备的寄存器*/
    foo_init_client(client);

    /*告诉 i2c层一个新的从设备到达*/
    if ((err = i2c_attach_client(client)))
        goto ERROR1;
    return 0;

    /*好了,这可能不是编程最好的练习,但在这种情况下通常是非常有效的代码*/

    ERROR1:
        kfree(data);
    ERROR0:
        return err;
}


移除设备(遗传模型)
==================

移除一个设备时必须调用detach_client的回调函数.这个调用只有在蹦溃的时候才会失效.幸运的是,这部分代码比增加设备要简单得多.

int foo_detach_client(struct i2c_client *client)
{
    int err;

    /*企图从i2c空间卸载从设备*/
    if ((err = i2c_detach_client(client)))
        return err;
    
    kfree(i2c_get_clientdata(client));
    return 0;
}


初始化模块或者内核
===================

当你的内核启动或者你的驱动模块被插入时,你必需做初始化的工作.幸运的是,我们通常只要附加(注册)这个驱动模块就已经足够.

static int __init foo_init(void)
{
    int res;
    
    if ((res = i2c_add_driver(&foo_driver))) {
        printk("foo: Driver registration failed,"
            "module not inserted.\n");
        return res;
    }
    return 0;
}

static void __exit foo_cleanup(void)
{
    i2c_del_driver(&foo_driver);
}

    /*修改你的名字和Email 地址*/

MODULE_AUTHOR("Frodo looijaard ")
MODULE_DESCRIPTION("Driver for Barf Inc. Foo I2C devices");

/*很少非GPL协议类型是允许的*/
MODULE_LICENSE("GPL");

module_init(foo_init);
module_exit(foo_cleanup);


注意,标有 '__init'的函数和标有 '__initdata'的数据结构在内核启动完成或都模块加载完成后是可以移除的.


电源管理
========

系统进入低电状态时(像使收发机处于低电模式或者唤醒系统的机理),你的I2C设备需要特殊的处理时,可以用suspend()来使系统挂起,而resume()即是做suspend()相反的动作.

有几个标准的驱动模型调用,它们像为其它为其它驱动栈一样工作,这些调用可以睡眠,也可以用I2C消息来通知处于挂起和恢复状态的设备(这些调用能发生,是因为它的父I2C适配器处于活动状态,且中断是开启的).


系统关闭
========
系统关闭或者重启(包括kexec) 你的I2C设备需要一些特殊的处理时--像关闭一些东西--可以使用shutdown()的办法.

再有,这是一个标准的驱动模型调用,它像为其它驱动栈工作一样工作:这些调用可以睡眠和使用I2C消息.

命令函数
========

能支持一个基本的类ioctl回调函数,但你很少使用到它,另外也不赞成使用,所以在新的设计中不再使用它.设置它为NULL。

发送和接收
==========

如果你想与你的i2c设备进行通信,有几可函数可以使用.你可以在i2c.h头文件中找到他们.

如果你可以在无格式的i2c通信和SMBus级通信这两者之间选择的话,请使用最后一个.所用的适配器都可以理解SMBus级别的命令,但只有一些是可以理解无格式i2c的通信.

无格式的i2c通信
----------------


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

这些函数用来从从设备读取一些字节和向从设备写一些字节.从设备里包含了i2c地址,所以你不必一定包含它的.第二个参数即为读写的字节,第三个即为缓冲区的长度.返回值为实际读写的字节数.


    extern int i2c_transfer(struct i2c_adapter *adap,
                    struct i2c_msg *msg, num);


这个用来发送一系列的消息,每一个消息可以用来读也可以用来写,读写也可以以另外一种方式来混合.传送连接在一起:在各个传送动作之间没有停止位.i2c_msg结构包含了每一个消息要发送的从设备地址.字节数即为消息数据本身.

你可以读 'i2c-protocol'文档来获取实际i2c协议的更多信息.



SMBus 通信
------------


extern s32 i2c_smbus_xfer (struct i2c_adapter *adapter, u16 addr,
                unsigned short flags,
                char read_write, u8 command, int size,
                union i2c_smbus_data *data);


这是SMBus的基本函数,下面的函数必须以一组的形式来执行,不要直接使用这个函数.



 extern s32 i2c_smbus_write_quick(struct i2c_client * client, u8 value);
 extern s32 i2c_smbus_read_byte(struct i2c_client * client);
 extern s32 i2c_smbus_write_byte(struct i2c_client * client, u8 value);
 extern s32 i2c_smbus_read_byte_data(struct i2c_client * client, u8 command);
 extern s32 i2c_smbus_write_byte_data(struct i2c_client * client,
                                       u8 command, u8 value);
 extern s32 i2c_smbus_read_word_data(struct i2c_client * client, u8 command);
 extern s32 i2c_smbus_write_word_data(struct i2c_client * client,
                                       u8 command, u16 value);
 extern s32 i2c_smbus_write_block_data(struct i2c_client * client,
                                        u8 command, u8 length,
                                        u8 *values);
 extern s32 i2c_smbus_read_i2c_block_data(struct i2c_client * client,
                                           u8 command, u8 length, u8 *values);


其中一些在Linux 2.6.10 内核中已被删除,因没有人使用它们,但是,如果需要,可以在外面把它加回来.


extern s32 i2c_smbus_read_block_data(struct i2c_client * client,
                                       u8 command, u8 *values);
  extern s32 i2c_smbus_write_i2c_block_data(struct i2c_client * client,
                                            u8 command, u8 length,
                                            u8 *values);
  extern s32 i2c_smbus_process_call(struct i2c_client * client,
                                    u8 command, u16 value);
  extern s32 i2c_smbus_block_process_call(struct i2c_client *client,
                                          u8 command, u8 length,
                                          u8 *values)

所有的这些传输如果失败都是返回 -1. write传输成功返回0;除了read_block返回的是读取数据的数目以外,其与的读操作都是返回读取的字节数.块缓冲的长度不能大于32个字节.

你可以读 'smbus-protocol'文档来获取实际SMBus协议的更多信息.


基本目的程序
=============
下面列出的所有基本目的程序在之前是没有提及的.


/*这个调用为每一个注册了的适配器返回唯一的一个低符号标识*/
extern int i2c_adapter_id(struct i2c_adapter *adap);

阅读(2523) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~