linux内核GPIO模拟I2C实例

linux内核GPIO模拟I2C实例


linux gpio模拟i2c的使用/用GPIO模拟I2C总线

实例解析linux内核I2C体系结构

GPIO模拟i2c

驱动模块使用I2C总线范例

i2c驱动调试经验 

I2C设备驱动流程

2010-10-11 作者:cvip302814来源:cvip302814blog

 

前言:

在许多情况下,我们并没有足够的I2C总线,本文主在介绍如何利用Linux内核中的i2c-gpio模块,利用2GPIO线模拟i2c总线,并挂载设备。

思路:

先通过对i2c-gpio所定义的结构体初始化(包括初始化i2c2条线,频率,timeout)并将i2c-gpio模块编译进内核,实现用GPIO_X,GPIO_Y 2GPIO线注册新的i2c总线。此时这个模块对i2c设备是透明的,及挂在这2GPIO线的i2c设备可以直接使用Linux内核通用的i2c设备注册,传输和注销等方法。

步骤:

首先确认在注册i2c-gpio模块前,所要用到的2GPIO口是没有被系统其它地方所调用的。

在每个系统平台启动时,都会打开一系列的设备,他们通常实现在arch/目录下相应的平台子目录中的例如setup.cdevices.c文件中,在这里我们进行i2c总线的注册以及设备的挂载。i2c-gpio定义的结构在include/linux/i2c-gpio.h中:

/**

 * struct i2c_gpio_platform_data - Platform-dependent data for i2c-gpio

 * @sda_pin: GPIO pin ID to use for SDA

 * @scl_pin: GPIO pin ID to use for SCL

 * @udelay: signal toggle delay. SCL frequency is (500 / udelay) kHz

 * @timeout: clock stretching timeout in jiffies. If the slave keeps

 * SCL low for longer than this, the transfer will time out.

 * @sda_is_open_drain: SDA is configured as open drain, i.e. the pin

 * isn't actively driven high when setting the output value high.

 * gpio_get_value() must return the actual pin state even if the

 * pin is configured as an output.

 * @scl_is_open_drain: SCL is set up as open drain. Same requirements

 * as for sda_is_open_drain apply.

 * @scl_is_output_only: SCL output drivers cannot be turned off.

 */

struct i2c_gpio_platform_data {

unsigned int sda_pin;

unsigned int scl_pin;

int udelay;

int timeout;

unsigned int sda_is_open_drain:1;

unsigned int scl_is_open_drain:1;

unsigned int scl_is_output_only:1;

};

其中sda_pinscl_pin分别是i2c总线的数据线和时钟线,在i2c-gpio中会通过gpio_request函数对这2个口进行申请,udelaytimeout如果不设初值,i2c-gpio中会自动将其设为默认值。

if (pdata->udelay)

bit_data->udelay = pdata->udelay;

else if (pdata->scl_is_output_only)

bit_data->udelay = 50; /* 10 kHz */

else

bit_data->udelay = 5; /* 100 kHz */

if (pdata->timeout)

bit_data->timeout = pdata->timeout;

else

bit_data->timeout = HZ / 10; /* 100 ms */

初始化这个结构体后再将其装入platform_device结构体,方便注册:

static struct platform_device i2c_device = {

.name = "device-name",

.id = your-id,

.dev = {

.platform_data = &i2c_data,       // i2c_gpio_platform_data

},

};

注册i2c-gpio设备

i2c设备挂入我们注册的总线:

platform_device_register(&i2c_device);

 

static struct i2c_board_info i2c_device[] = {

{

I2C_BOARD_INFO("name", i2c_device_addr),

}

};

i2c_register_board_info(your-id, i2c_device, ARRAY_SIZE(i2c_device));

此时我们就可以在i2c设备的驱动程序中通过遍历所在i2c总线,得到其所在的地址i2c_device_addr

i2c驱动中,需要注册一个i2c_driver的结构体,例如:

static const struct i2c_device_id lis35de_id[] = {

{ "lis35de", 0 },

{ }

};

static struct i2c_driver st_lis35de_driver = {

.probe = st_lis35de_probe,

.remove = st_lis35de_remove,

.suspend = st_lis35de_suspend,

.resume = st_lis35de_resume,

.id_table  = lis35de_id,

.driver = {

.name = "lis35de",

},

};

static int __init st_lis35de_init(void)

{

printk(KERN_INFO "st_lis35de_init\n");

return i2c_add_driver(&st_lis35de_driver);

}

init时用i2c_add_driver(&st_lis35de_driver),此时将会对所在i2c总线进行遍历并得到该设备的适配器等信息,主要目的即是使驱动得到自己的i2c_client,在这个i2c_client中,已经有了该i2c设备的地址等信息,我们在驱动中定义一个新的i2c_client全局变量,把得到的这个i2c_client传给这个全局变量,从而可以继续后面的i2c操作。

此时我们就可以使用通用的i2c读写操作了。

总结:

直接用GPIO口模拟I2C时序和利用内核模块i2c-gpio虚拟i2c总线的区别:

1GPIO口模拟I2C时序不需要在系统启动时注册I2C总线,只需要在I2C设备驱动中单独实现。用i2c-gpio模块虚拟i2c总线需要在系统启动时注册新的I2C总线,并将i2c设备挂载到新的i2c总线,涉及的范围较广。

2GPIO口模拟I2C时序,代码操作较繁琐,且不方便挂载多个i2c设备。用i2c-gpio模块可以完全模拟i2c总线,可以挂载多个设备。

3i2c读写操作时,用GPIO口模拟I2C时序需要每次根据读/写操作发送器件地址<<1+1/0,然后再发送寄存器地址。用i2c-gpio模块相当于直接在i2c总线上操作,在系统启动挂载i2c设备时已经告诉了i2c总线它的地址,在该设备自己的驱动中,只需要通过i2c_add_driver操作即可以得到其地址等诸多信息,读写操作只需要发送寄存器地址即可。

附:i2c一般的读写操作

#include 

/*

读操作:

*/

static int i2c_RxData(char *rxData, int length)

{

struct i2c_msg msgs[] = {

         /* 1个字节的i2c设备寄存器地址告诉总线 */

{

 .addr = client->addr,

 .flags = 0,                     //写操作

 .len = 1,

 .buf = rxData,

 },

          /* 从总线读取length个字节的数据,存入rxData */

{

 .addr =client ->addr,

 .flags = I2C_M_RD,             //I2C_M_RDi2c.h中被定义为1,读操作

 .len = length,

 .buf = rxData,

 },

};

if (i2c_transfer(client->adapter, msgs, 2) < 0) {   /* 传输并判断是否传输错误 */

printk(KERN_ERR "I2C_RxData: transfer error\n");

return -EIO;

} else

return 0;

}

/*

写操作

*/

static int i2c_TxData(char *txData, int length)

{

struct i2c_msg msg[] = {

     /* 1个字节是器件寄存器地址,后面的字节是写入的数据 */

{

 .addr = client->addr,

 .flags = 0,

 .len = length,

 .buf = txData,

 },

};

if (i2c_transfer(client->adapter, msg, 1) < 0) {

printk(KERN_ERR "I2C_TxData: transfer error\n");

return -EIO;

} else

return 0;

}


 

 例子1

1,kernel\arch \arm\mach-pxa\board-test.c 增加

/* i2c */
static struct i2c_gpio_platform_data i2c_bus_data = {
 .sda_pin = VIPER_RTC_I2C_SDA_GPIO,
 .scl_pin = VIPER_RTC_I2C_SCL_GPIO,
 .udelay  = 10,
 .timeout = 100,
};

 

static struct platform_device i2c_bus_device = {
 .name  = "i2c-gpio",
 .id  = 1, /* pxa2xx-i2c is bus 0, so start at 1 */
 .dev = {
  .platform_data = &i2c_bus_data,
 }
};

 

static struct i2c_board_info __initdata viper_i2c_devices[] = {
 {
  I2C_BOARD_INFO("ds1338", 0x68),
 },
};

 


第一个结构体中sda_pin和 scl_pin是开发板上对应的gpio口(data线和clock线),udelay是与具体芯片时钟相关的参数,需要参考具 体的datasheet。下面的两个open_drain是表明两个管脚是否是开漏电路,如果是则填1,否则填0。下面一个机构 体中需要注意name应该填写i2c-gpio,另外id要注意设定为2,因为系统当中已经有两个I2C设备了。

 

 

添加上需要的头文 件:#include 。在头文件devices.h中添加上设备结构体的声 明,extern struct platform_device gpio_device_i2c;

然后将 gpio_device_i2c放在board-XXXX.c的数组devices中,形式请参考该数组中其他的设备。   

xiewen202
10-7-1
宋老师,各位朋友大家好:
     由于需要用两个gpio模拟i2c,现在我使用linux自带的i2c-gpio.c,我的注册信息如下:
     static struct i2c_gpio_platform_data i2c3_data = {
        .sda_pin = GPIO_F7,
        .scl_pin = GPIO_F8,
        .udelay = 0,
        .timeout = 0,
        .sda_is_open_drain = 1,
        .scl_is_open_drain = 1,
        .scl_is_output_only = 0,

};

static struct platform_device i2c3_device = {
        .name                = "i2c-gpio",
        .id                        = 2,
        .dev = {
                .platform_data        = &i2c3_data,
        },
};
static struct platform_device *platform_devs_pre[] __initdata = {
        &pnx_wavex_gpio_device,
               & i2c3_device,//注册
};
static struct i2c_board_info i2c_devices[] = {
        {
                I2C_BOARD_INFO("lis35de", 0x1C),
        },

};
int __init pnx67xx_devices_init(void)
{
        pr_debug("%s()\n", __func__);
        platform_add_devices(platform_devs_pre,
ARRAY_SIZE(platform_devs_post));
        i2c_register_board_info(1, i2c_devices, ARRAY_SIZE(i2c_devices));
        return 0;
}
在我的lis35de驱动中:
static const struct i2c_device_id lis35de_id[] = {
        { "lis35de", 0 },
        { }
};

static struct i2c_driver st_lis35de_driver = {
        .probe                = st_lis35de_probe,
        .remove                = st_lis35de_remove,
        .suspend        = st_lis35de_suspend,
        .resume                = st_lis35de_resume,
        .id_table         = lis35de_id,
        .driver                = {
                .name        = "lis35de",
        },
};

static int __devinit st_lis35de_init(void)
{
        printk(KERN_INFO "st_lis35de_init\n");
        return i2c_add_driver(&st_lis35de_driver);
}

static void __exit st_lis35de_exit(void)
{
        i2c_del_driver(&st_lis35de_driver);
}

module_init(st_lis35de_init);
module_exit(st_lis35de_exit);
,现在我在我的st_lis35de_probe中我该如何初始化i2c_client,然后使用i2c_transfer或
i2c_master_send发送数据啊。i2c_transfer或i2c_master_send有什么区别啊?请教宋老师
chris
10-7-1
"现在我在我的st_lis35de_probe中我该如何初始化i2c_client,然后使用i2c_transfer或
i2c_master_send发送数据啊。i2c_transfer或i2c_master_send有什么区别啊?请教宋老师"
这两个的程序在什么地方呀,你不写出来怎么分析呀!!!
 
i2c的协议看了吗?不是很难吧,


 

 
          
- 显示引用文字 -
send有什么区别啊?请教宋老师 > >--  >_______________________________________________ >Linux设备驱动开发邮件列表  [email protected] > http://groups.google.gr/group/linuxdriver?hl=zh-CN >退订请使用原订阅邮箱发邮件至: [email protected]

xiewen202
10-7-1
Re:Re:[linux-driver] 求助宋老师各位朋友:i2c-gpio使用
chris谢谢你的回复,使用i2c_transfer或i2c_master_send不需要关心i2c发送细节,我是使用i2c子系统,i2c_client是内核生成的数据结构,我不是太明白怎么初始化。

在 2010-07-01 20:04:48,chris < [email protected]> 写道:
- 显示引用文字 -
jimmy
10-7-2
Re: Re:Re:[linux-driver] 求助宋老师各位朋友:i2c-gpio使用
1. 你这是属于i2c的probe方式,在csdn 刘洪涛老师说的明白,在平台文件中声明i2c_board_info,再注册i2c_register_board_info(1 );
2. 初始化i2c_client,你可以自己为这个设备新建一个设备结构体,将struct i2c_client *client封装在里面,先进行一些必要有初始化,申请空间.用i2c_check_functionality 检测,最终调用
3. 使用 i2c_transfer来发送数据,int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
                        int num); 要知道该client绑定的adapter,在adapter中已经实现算法,从而发送数据, 你这个用i2c_register_board_info(1 );说明ID是1, 可以用i2c_get_adapter(1) 来获取adapter,从页实现发送
4. 总而言知,声明client,无疑是对一些变量初始化,检测设备地址, 申请空间,绑定client 调用i2c_set_client_data, 然后发送时注意client依付的adapter,
这是我的理解,有不对的,请大家指点
 
2010-07-02

jiangmin7790594

发件人: xiewen202
发送时间: 2010-07-01  21:43:51
收件人: linuxdriver
抄送:
主题: Re:Re:[linux-driver] 求助宋老师各位朋友:i2c-gpio使用
- 显示引用文字 -
xiewen202
10-7-2
Re:Re: Re:Re:[linux-driver] 求助宋老师各位朋友:i2c-gpio使用
jiangmin7790594谢谢,我是和你一样理解的,我去看了刘洪涛老师的csda.
我看了他的例子client是自己分配和初始化的,并且实现了:
attach_adapter= pca953x_attach_adapter, //调用适配器连接设备
.detach_client= pca953x_detach_client,//让设备脱离适配器
但是在i2c-gpio中我看原码好是内核分配的,attach_adapter和detach_client也用内核自带的。
 
其实我的驱动我已实现了但是这个client 还是不能理解.

 

在 2010-07-02 00:28:52,jiangmin7790594 < [email protected]> 写道:
- 显示引用文字 -






你可能感兴趣的:(linux,设备驱动)