linux i2c 设备驱动,Linux驱动 | I2C设备驱动(以ds1337.c为例)

本文以 Linux-2.6.22.6 内核树下的 drivers/i2c/chips/ds1337.c 为例, 讨论Linux系统下的I2C设备的驱动代码的主要构成。

ds1337是一款 i2c 接口的串行实时时钟芯片。

i2c_driver和i2c_client 是I2C设备驱动的核心数据结构。I2C

设备驱动要使用 i2c_driver 和 i2c_client 数据结构并填充其中的成员函数。

另外值得一提的是,i2c_adapter和i2c_algorithm这两者则为I2C总线驱动的核心数据结构。以上这四个数据结构都在include/linux/i2c.h中被定义。i2c_client是硬件上的i2c芯片/设备(比如:本文的ds1337芯片)的抽象,而i2c_driver是i2c_client的一系列操作方法的一个封装;类似的,i2c_adapter是硬件上的i2c_适配器(比如:s3c2440的I2C控制器)的抽象,而i2c_algorithm则是对i2c_adapter的一系列操作方法的一个封装。

i2c_client 被包含在设备的私有信息结构体 ds1337_data 中,而 i2c_driver 则被定义为全局变量并初始化。如下:

struct ds1337_data {

struct i2c_client client;

struct list_head list;

};

static struct i2c_driver ds1337_driver = {

.driver = {

.name= "ds1337",

},

.attach_adapter= ds1337_attach_adapter,

.detach_client= ds1337_detach_client,

.command= ds1337_command,

};

另外,内核定义了i2c_msg 结构体来用于I2C适配器和I2C设备之间的数据传输。

struct i2c_msg {

__u16 addr;/* slave address*/

__u16 flags;

#define I2C_M_TEN0x10/* we have a ten bit chip address*/

#define I2C_M_RD0x01

#define I2C_M_NOSTART0x4000

#define I2C_M_REV_DIR_ADDR0x2000

#define I2C_M_IGNORE_NAK0x1000

#define I2C_M_NO_RD_ACK0x0800

#define I2C_M_RECV_LEN0x0400 /* length will be first received byte */

__u16 len;/* msg length*/

__u8 *buf;/* pointer to msg data*/

};

可见,它其实就是I2C通信协议的数据格式在内核中的抽象。其成员:addr对应于从设备的地址;flags对应于读写控制位;len为消息长度;buf指针则指向主机中的缓存。

3f288ee285423cc6e29fa526842ca321.png

一、 I2C设备驱动模块加载与卸载

static int __init ds1337_init(void)

{

return i2c_add_driver(&ds1337_driver);

}

static void __exit ds1337_exit(void)

{

i2c_del_driver(&ds1337_driver);

}

加载

通过 I2C 核心的 i2c_add_driver()函数添加 i2c_driver。

卸载

通过 I2C 核心的 i2c_del_driver()函数删除

i2c_driver。

i2c_add_driver()函数和i2c_del_driver()函数最终都通过i2c_driver中的

probe, remove, attach_adapter, detach_adapter, detach_client 这些成员去实现真正的操作。

模块加载过程和卸载过程引发的一系列反应如下图所示,在下文会讨论具体的过程。

3f288ee285423cc6e29fa526842ca321.png

二、 i2c_driver的成员函数

static struct i2c_driver ds1337_driver = {

.driver = {

.name= "ds1337",

},

.attach_adapter= ds1337_attach_adapter,

.detach_client= ds1337_detach_client,

.command= ds1337_command,

};

1. ds1337_attach_adapter

I2C 设备驱动的加载函数进行i2c_add_driver(&ds1337_driver)的调用,会引发i2c_driver 结构体中ds1337_attach_adapter()函数的执行,它简单地调用 I2C 核心的i2c_probe()函数。ds1337_detect()函数的指针作为参数传入i2c_probe()中;i2c_probe()函数调用i2c_probe_address(),从而引发ds1337_detect()函数的调用。

static int ds1337_attach_adapter(struct i2c_adapter *adapter)

{

return i2c_probe(adapter, &addr_data, ds1337_detect);

}

在 ds1337_detect()函数中初始化i2c_client结构体的成员;调用i2c_attach_client()函数来建立i2c_client和i2c_adapter 的依附关系(把它加入队列的尾部:list_add_tail(&client->list, &adapter->clients));调用ds1337_init_client()函数来实现具体硬件相关的初始化;最后,把client加入本地链表(list_add(&data->list,

&ds1337_clients))。

static int ds1337_detect(struct i2c_adapter *adapter, int address, int kind)

{

struct i2c_client *new_client;

struct ds1337_data *data;

int err = 0;

const char *name = "";

if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA |

I2C_FUNC_I2C))

goto exit;

if (!(data = kzalloc(sizeof(struct ds1337_data), GFP_KERNEL))) {

err = -ENOMEM;

goto exit;

}

INIT_LIST_HEAD(&data->list);

/* The common I2C client data is placed right before the

* DS1337-specific data.

*/

new_client = &data->client;

i2c_set_clientdata(new_client, data);

new_client->addr = address;

new_client->adapter = adapter;

new_client->driver = &ds1337_driver;

new_client->flags = 0;

/* Default to an DS1337 if forced */

if (kind == 0)

kind = ds1337;

if (kind < 0) {/* detection and identification */

u8 data;

/* Check that status register bits 6-2 are zero */

if ((ds1337_read(new_client, DS1337_REG_STATUS, &data) < 0) ||

(data & 0x7c))

goto exit_free;

/* Check for a valid day register value */

if ((ds1337_read(new_client, DS1337_REG_DAY, &data) < 0) ||

(data == 0) || (data & 0xf8))

goto exit_free;

/* Check for a valid date register value */

if ((ds1337_read(new_client, DS1337_REG_DATE, &data) < 0) ||

(data == 0) || (data & 0xc0) || ((data & 0x0f) > 9) ||

(data >= 0x32))

goto exit_free;

/* Check for a valid month register value */

if ((ds1337_read(new_client, DS1337_REG_MONTH, &data) < 0) ||

(data == 0) || (data & 0x60) || ((data & 0x0f) > 9) ||

((data >= 0x13) && (data <= 0x19)))

goto exit_free;

/* Check that control register bits 6-5 are zero */

if ((ds1337_read(new_client, DS1337_REG_CONTROL, &data) < 0) ||

(data & 0x60))

goto exit_free;

kind = ds1337;

}

if (kind == ds1337)

name = "ds1337";

/* We can fill in the remaining client fields */

strlcpy(new_client->name, name, I2C_NAME_SIZE);

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

if ((err = i2c_attach_client(new_client)))

goto exit_free;

/* Initialize the DS1337 chip */

ds1337_init_client(new_client);

/* Add client to local list */

list_add(&data->list, &ds1337_clients);

return 0;

exit_free:

kfree(data);

exit:

return err;

}

2. ds1337_detach_client

I2C 设备驱动的卸载函数进行 i2c_del_driver(&ds1337_driver)调用后,会引发与ds1337_driver 关联的每个 i2c_client 与之解除关联,即ds1337_detach_client()函数将因此而被调用。

static int ds1337_detach_client(struct i2c_client *client)

{

int err;

struct ds1337_data *data = i2c_get_clientdata(client);

if ((err = i2c_detach_client(client)))

return err;

list_del(&data->list);

kfree(data);

return 0;

}

3. ds1337_command

i2c_driver的command成员函数实现了针对设备的控制命令。具体的控制命令是设备相关的,如对于本文的ds1337实时钟而言,命令将是设置时间和获取时间,而对于视频 AD 设备而言,命令会是设置采样方式、选择通道等。

ds1337 设备接受DS1337_GET_DATE 和DS1337_GET_DATE 这两种命令,而处理这两个命令的函数分别为ds1337_get_datetime和ds1337_set_datetime,如下:

static int ds1337_command(struct i2c_client *client, unsigned int cmd, void *arg)

{

dev_dbg(&client->dev, "%s: cmd=%d

", __FUNCTION__, cmd);

switch (cmd) {

case DS1337_GET_DATE:

return ds1337_get_datetime(client, arg);

case DS1337_SET_DATE:

return ds1337_set_datetime(client, arg);

default:

return -EINVAL;

}

}

具体命令的实现是通过组件 i2c_msg 消息数组,并调用 I2C 核心的i2c_transfer()。i2c_transfer()寻找到i2c_adapter 对应的 i2c_algorithm,并使用 i2c_algorithm 的master_xfer()函数真正驱动硬件流程,来实现 I2C 适配器和 I2C 设备之间的一组消息交互。

static int ds1337_get_datetime(struct i2c_client *client, struct rtc_time *dt)

{

int result;

u8 buf[7];

u8 val;

struct i2c_msg msg[2];

u8 offs = 0;

if (!dt) {

dev_dbg(&client->dev, "%s: EINVAL: dt=NULL

", __FUNCTION__);

return -EINVAL;

}

/*构造一条“写消息”*/

msg[0].addr = client->addr;

msg[0].flags = 0;

msg[0].len = 1;

msg[0].buf = &offs;

/*构造一条“读消息”*/

msg[1].addr = client->addr;

msg[1].flags = I2C_M_RD;

msg[1].len = sizeof(buf);

msg[1].buf = &buf[0];

/*完成数据的传输*/

result = i2c_transfer(client->adapter, msg, 2);

/*具体的硬件操作:读取缓存中接收到的BCD码,转换成二进制码*/

dev_dbg(&client->dev, "%s: [%d] %02x %02x %02x %02x %02x %02x %02x

",

__FUNCTION__, result, buf[0], buf[1], buf[2], buf[3],

buf[4], buf[5], buf[6]);

if (result == 2) {

dt->tm_sec = BCD2BIN(buf[0]);

dt->tm_min = BCD2BIN(buf[1]);

val = buf[2] & 0x3f;

dt->tm_hour = BCD2BIN(val);

dt->tm_wday = BCD2BIN(buf[3]) - 1;

dt->tm_mday = BCD2BIN(buf[4]);

val = buf[5] & 0x7f;

dt->tm_mon = BCD2BIN(val) - 1;

dt->tm_year = BCD2BIN(buf[6]);

if (buf[5] & 0x80)

dt->tm_year += 100;

dev_dbg(&client->dev, "%s: secs=%d, mins=%d, "

"hours=%d, mday=%d, mon=%d, year=%d, wday=%d

",

__FUNCTION__, dt->tm_sec, dt->tm_min,

dt->tm_hour, dt->tm_mday,

dt->tm_mon, dt->tm_year, dt->tm_wday);

return 0;

}

dev_err(&client->dev, "error reading data! %d

", result);

return -EIO;

}以上讨论的,即是Linux系统中I2C设备驱动的基本构成,至于其中跟底层的和具体硬件相关的代码,可以参阅Linux-2.6.22.6内核树下的 drivers/i2c/chips/ds1337.c 文件及其它相关源码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值