Linux设备驱动之I2C驱动

Linux下I2C驱动分为两部分:主机驱动和设备驱动。

主机驱动:

        主机侧I2C控制器使用struct i2c_adapter描述,结构体中包含了i2c总线通信方法,设备结构体等。

struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /*  总线通信算法 */
void *algo_data;

/* data fields that are valid for all devices */
struct rt_mutex bus_lock;

int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */

int nr;
char name[48];
struct completion dev_released;

struct mutex userspace_clients_lock;
struct list_head userspace_clients;

struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};

        注意:这个结构体是Linux系统定义的,所有的SoC厂商的I2C控制器在Linux中都是这样表示的,这是相同点;不同点在于,不同的SoC的I2C控制器的相关寄存器不同,配置方式也不同,所以不同的厂商都要实现自己的I2C控制器的驱动,然后按照Linux系统的要求提供统一的接口。

        SoC厂商需要实现结构体中的总线通信方法,有了这个总线通信方法,我们在写设备驱动的时候才能直接调用该函数进行设备读写。struct i2c_algorithm结构体如下。

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 *);
......
};

        该结构体也是Linux系统定义的,厂商写好I2C的通信函数以后,把这个结构体中的函数指针指向自己写好的函数。这样就形成了这样一种情况:不管什么SoC,在Linux系统下进行通信的时候都使用统一的接口(master_xfer指针),但其实际的通信函数实现是由不同厂商来完成的,这其实很像面向对象中的多态,相同的函数名,但实现方式各有不同。

        SoC厂商一般会根据自己的I2C控制器的特点,把Linux提供的这个i2c_adapter结构体进行二次封装,形成自己SoC特有的I2C控制器的结构体描述,而不是直接使用i2c_adapter来描述自己的I2C控制器。举个例子,下面是Nvdia为自己的Tegra芯片的I2C控制器定制的结构体。

         综上所述,SoC厂商编写I2C主机驱动就是要干几个事:

        一、根据实际需要,封装一个本SoC定制的I2C控制器结构体,struct i2c_adapter需要包含其中。

        二、实现struct i2c_adapter中的I2C总线通信方法,并且把I2C控制器给初始化好。

        三、调用Linux系统提供的API进行I2C控制器的注册。

设备驱动

        对于I2C设备驱动,Linux系统定义了几个重要的结构体,不管什么I2C设备,到了Linux系统这里,都是用这些结构体来表示。

struct i2c_driver {
unsigned int class;


int (*attach_adapter)(struct i2c_adapter *) __deprecated;

int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
void (*shutdown)(struct i2c_client *);

void (*alert)(struct i2c_client *, unsigned int data);

int (*command)(struct i2c_client *client, unsigned int cmd,
void *arg);

struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};

        首先就是i2c_driver,我们写设备驱动需要实现其中的probe函数和remove函数,在driver中把设备树的匹配信息给写好。probe函数中要做的就是字符设备的相关操作,然后i2c设备的初始化也可以在这里完成。i2c设备驱动的编写中有个小技巧就是在probe函数中把strcut i2c_client这个表示设备本身信息的结构体给到这个设备的结构体中的私有成员变量。

         接下来是i2c设备的描述结构体,每个i2c设备都会被分配一个i2c_client,其结构体中包含的信息如下。

struct i2c_client {
unsigned short flags; /* 标志 */
unsigned short addr; /* 芯片地址,7 位,存在低 7 位 */
......
char name[I2C_NAME_SIZE]; /* 名字 */
struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
struct device dev; /* 设备结构体 */
int irq; /* 中断 */
struct list_head detected;
......
};

        现在使用Linux设备树的情况下,当我们在i2c适配器的节点下追加i2c设备信息,Linux系统实现了i2c核心层,这个核心层提供了我们上述说的i2c主机驱动的注册函数,注销函数,i2c设备驱动的注册与注销函数,以及I2C通信方法(不同的SoC厂商会实现自己的I2C通信方法,然后覆盖),还有与具体适配器无关的探测设备和检测设备地址的代码

        所以,当在设备树中写入了I2C设备的相关信息后,I2C核心会进行解析出设备地址,所属的I2C适配器,中断等信息,然后填充这个i2c_client结构体,最后在probe函数中这个i2c_client结构体变量会作为参数传入到probe函数中。

        i2c设备的数据读写是基于struct i2c_msg进行的

struct i2c_msg {
__u16 addr; /* 从机地址 */
__u16 flags; /* 标志 */
#define I2C_M_TEN 0x0010
#define I2C_M_RD 0x0001
#define I2C_M_STOP 0x8000
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK 0x0800
#define I2C_M_RECV_LEN 0x0400
__u16 len; /* 消息(本 msg)长度 */
__u8 *buf; /* 消息数据 */
};

        要通过I2C总线通信,首先要构建好i2c_msg,填充好其中的设备地址,读写标志,要发送的数据,长度信息等。构建好i2c_msg以后,通过以下函数进行发送。

int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)

        该函数是调用I2C控制器中的algorithm成员中的SoC厂商的实现的master_xfer函数进行通信的。

读写设备的示例代码

struct xxx_dev {
3 ......
4 void *private_data; /* 私有数据,一般会设置为 i2c_client */
5 };


* @param – dev  : I2C 设备
* @param – reg  : 要读取的寄存器首地址
* @param – val  : 读取到的数据
* @param – len  : 要读取的数据长度
* @return : 操作结果

static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)
dev->private_data;

/* msg[0],第一条写消息,发送要读取的寄存器首地址 */
msg[0].addr = client->addr; /* I2C 器件地址 */
msg[0].flags = 0; /* 标记为发送数据 */
msg[0].buf = ® /* 读取的首地址 */
msg[0].len = 1; /* reg 长度 */

/* msg[1],第二条读消息,读取寄存器数据 */
msg[1].addr = client->addr; /* I2C 器件地址 */
msg[1].flags = I2C_M_RD; /* 标记为读取数据  */
msg[1].buf = val; /* 读取数据缓冲区 */
msg[1].len = len; /* 要读取的数据长度 */

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

if(ret == 2) {
 ret = 0;
} else {
ret = -EREMOTEIO;
}
return ret;
}


* @param – dev  : 要写入的设备结构体
* @param – reg  : 要写入的寄存器首地址
* @param – buf  : 要写入的数据缓冲区
* @param – len  : 要写入的数据长度

static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,
u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)
dev->private_data;

b[0] = reg; /* 寄存器首地址 */
memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面  */

msg.addr = client->addr; /* I2C 器件地址 */
msg.flags = 0; /* 标记为写数据 */

msg.buf = b; /* 要发送的数据缓冲区 */
msg.len = len + 1; /* 要发送的数据长度 */

return i2c_transfer(client->adapter, &msg, 1);
}

 

        

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值