一、前言
本文主要分为三个部分,第一部分,介绍i2c字符设备驱动应用的背景以及本文测试需要的开发环境;第二部分,介绍主要的字符驱动源码及测试程序;第三部分,测试方法以及测试结果,i2c从设备的器件地址可以在该器件的datasheet查找。文章的最后会给大家分享本文的所有源码。
二、开发背景和环境
我已经讲解过利用i2c总线的去配置i2c从设备的方法,本文采用i2c设备驱动的方式完成同样的功能,在此完善工作记录。
优点:(1)当从设备需要多种功能操作时(比如修改摄像头的亮度、放大、曝光、分辨率等配置),把每个功能包装成子模块,相对总线方式的配置层次清晰,而且方便管理维护,而且在扩展时还能够在设备驱动中添加对系统内核资源的访问(操作时请注意安全);
(2)设备初始化顺序可以随意控制,想i2c从设备启动快点就把设备初始化添加到内核启动,想它启动慢一点,就以.ko的方式加载,等文件系统加载完毕了再初始化;
缺点 :(1)相比总线操作的方式编写代码较复杂,因为首先要熟悉字符驱动架构,而且还需要编写一个操作设备驱动的应用程序;
运行环境:ARM S3C6410平台
交叉编译器:ARM-LINUX-GCC
内核版本:2.6.28.6
三、源码的讲解
源码的讲解分为两个部分,第一个部分初略地介绍下i2c字符设备初始化过程,具体的字符驱动架构不再本文讲解的范围内,第二部分,讲解利用i2c设备驱动对i2c从设备的寄存器进行读写操作;
驱动源码初始化执行步骤,
module_init(ch7026_init)
首先执行ch7026_init函数
static __init int ch7026_init(void)
{
int ret;
dev_t devno;
printk(DEVICE_NAME " start init...\n");
p_bank = kmalloc(sizeof(struct ch7026_bank), GFP_KERNEL);
if (!p_bank)
return -ENOMEM;
memset(p_bank, 0, sizeof(struct ch7026_bank));
devno = MKDEV(CH7026_MAJOR,0);
ret = register_chrdev_region(devno,1,DEVICE_NAME);
if(ret<0)
{
printk(KERN_NOTICE "can not register ch7026 device");
return ret;
}
cdev_init(&cdev_ch7026,&ch7026_fops);
cdev_ch7026.owner = THIS_MODULE;
ret =cdev_add(&cdev_ch7026,devno,1);
if(ret)
{
printk(KERN_NOTICE "can not add ch7026 device");
return ret;
}
/*在/sys/class/下创建相对应的类目录*/
my_class = class_create(THIS_MODULE,"ch7026");
if(IS_ERR(my_class))
{
printk("Err: Failed in creating class\n");
return -1;
}
/*完成设备节点的自动创建,当加载模块时,就会在/dev下自动创建设备文件*/
device_create(my_class,NULL,MKDEV(CH7026_MAJOR,0),NULL,DEVICE_NAME);
printk(DEVICE_NAME " initialized\n");
i2c_add_driver(&ch7026_driver);
return 0;
}
执行回调函数 ch7026_probe()函数
static struct i2c_driver ch7026_driver = {
.driver = {
.name = "ch7026",
.owner = THIS_MODULE,
},
.id = I2C_DRIVERID_CH7026,
.attach_adapter = ch7026_probe,
.detach_client = ch7026_detach,
};
在执行ch7026_attach()函数
static int ch7026_probe(struct i2c_adapter *adap)
{
int ret = 0;
ret = i2c_probe(adap, &addr_data, ch7026_attach);
if (ret) {
printk("failed to attach ch7026 driver\n");
ret = -ENODEV;
}
return ret;
}
执行ch7026_attach函数,执行用户配置i2c从设备ch7026_config()函数
static int ch7026_attach(struct i2c_adapter *adap, int addr, int flags )
{
int ret = 0;
strcpy(p_bank->c.name, "ch7026");
p_bank->c.addr = addr;
p_bank->c.adapter = adap;
p_bank->c.driver = &ch7026_driver;
ret = i2c_attach_client(&p_bank->c);
ch7026_config(&p_bank->c);
printk("CH7026 attached successfully\n");
return ret;
}
第二部分下面讲解下应用层write、read、ioctl系统调用到设备驱动的读写函数,即i2c设备驱动的读写操作,熟悉设备驱动的人就知道,分别实现对应的ch7026_write、 ch7026_read、 ch7026_ioctl函数即可,
static struct file_operations ch7026_fops = {
.owner = THIS_MODULE,
.open = ch7026_open,
.write = ch7026_write,
.read = ch7026_read,
.ioctl = ch7026_ioctl,
};
ch7026_write函数的作用是实现用户空间的数据到内核空间的拷贝,然后再调用ch7026_i2c_write函数
static ssize_t ch7026_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int ret, err = 0;
unsigned char addr = *ppos;
unsigned char buffer;
if (copy_from_user(&buffer, buf, count)) {
err = -EFAULT;
return err;
}
ret = ch7026_i2c_write(&p_bank->c, addr, buffer);
if (ret == -EIO) {
printk("i2c transfer error\n");
return -EIO;
}
return ret;
}
ch7026_read函数读取地址的数据,再从内核空间拷贝该数据到用户空间,本文所讲的i2从设备寄存器地址是8bit(一个字节),如果遇到i2从设备寄存器地址是16bit(两个字节),对应修改下面buffer为unsigned short型,i2c_master_send(&p_bank->c, &buffer, 2),读操作也同理,改下数据接口就得,根据手册多测试就知道怎么改了。
static ssize_t ch7026_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
int ret = 0;
u8 p = *ppos;
u8 buffer;
u8 Rdval = 0;
buffer = p&0xff;
if (i2c_master_send(&p_bank->c, &buffer, 1) != 1) {
return -1;
}
if (i2c_master_recv(&p_bank->c, &Rdval, 1) != 1) {
return -1;
}
if (copy_to_user(buf, &Rdval, sizeof(Rdval))) {
ret = -EFAULT;
}
return ret;
}
要在ioctl实现从设备各种子功能的实现,就自定义实现一个设备驱动里能读写的函数接口,开机配置i2c从设备多个寄存器的接口函数作用如下,就用到了ch7026_i2c_write函数,
void inline ch7026_config(struct i2c_client *client)
{
int i;
int ret = 0;
for (i = 0; i < CH7026_INIT_REGS; i++) {
delay(50);
ret = ch7026_i2c_write(client,
ch7026_reg[i].subaddr, ch7026_reg[i].value);
if(ret != 0) printk("ch7026:write faild!\n");
}
}
以下为i2c设备驱动读写i2c从设备寄存器的接口,
static unsigned char ch7026_i2c_read(struct i2c_client *client, u8 subaddr)
{
u8 Regbuf = subaddr;
u8 Rdval = 0;
if (i2c_master_send(client, Regbuf, 1) != 1) {//发送要读取从设备的寄存器地址
return -1;
}
if (i2c_master_recv(client, &Rdval, 1) != 1) {//把读取到寄存器的值保存在Rdval并返回
return -1;
}
return Rdval;
}
static int ch7026_i2c_write(struct i2c_client *client, unsigned char subaddr, unsigned char val)
{
int ret;
unsigned char buf[2];
struct i2c_msg msg = { client->addr, 0, 2, buf };
buf[0] = subaddr;//所写寄存器的地址
buf[1] = val;//将要向寄存器写的值
printk("Kernel Reg: 0x%x Value: 0x%x\n",buf[0], buf[1]);
ret = i2c_transfer(client->adapter, &msg, 1) == 1 ? 0 : -EIO;
if(ret < 0)
printk("ch7026_i2c_write error!\n");
return ret;
}
以下为i2c读写接口函数的延展,展示了i2c_master_send、i2c_master_recv、i2c_transfer的函数关系,其中i2c_msg 数据结构非常关键
/usr/local/arm/4.2.2-eabi/usr/include/linux/i2c.h //i2c_msg 数据定义的头文件
/*
* I2C Message - used for pure i2c transaction, also from /dev interface
*/
struct i2c_msg {
__u16 addr; /* slave address---从设备地址*/
__u16 flags; /*以下的宏定义为可以对flags操作的位运算*/
#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
#define I2C_M_RD 0x01
#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 /* length will be first received byte */
__u16 len; /* msg length---数据长度,字节为单位*/
__u8 *buf; /* pointer to msg data---存在数据的指针*/
};
/opt/htx-linux-2.6.28-d300-20170531/drivers/i2c/i2c-core.c //i2c_master_send、
i2c_master_recv、i2c_transfer函数定义的文件
/**
* i2c_master_send - issue a single I2C message in master transmit mode
* @client: Handle to slave device
* @buf: Data that will be written to the slave
* @count: How many bytes to write
*
* Returns negative errno, or else the number of bytes written.
*/
int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
{
int ret;
struct i2c_adapter *adap=client->adapter;
struct i2c_msg msg;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.len = count;
msg.buf = (char *)buf;
ret = i2c_transfer(adap, &msg, 1);
/* If everything went ok (i.e. 1 msg transmitted), return #bytes
transmitted, else error code. */
return (ret == 1) ? count : ret;
}
EXPORT_SYMBOL(i2c_master_send);
/**
* i2c_master_recv - issue a single I2C message in master receive mode
* @client: Handle to slave device
* @buf: Where to store data read from slave
* @count: How many bytes to read
*
* Returns negative errno, or else the number of bytes read.
*/
int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
{
struct i2c_adapter *adap=client->adapter;
struct i2c_msg msg;
int ret;
msg.addr = client->addr;
msg.flags = client->flags & I2C_M_TEN;
msg.flags |= I2C_M_RD;
msg.len = count;
msg.buf = buf;
ret = i2c_transfer(adap, &msg, 1);
/* If everything went ok (i.e. 1 msg transmitted), return #bytes
transmitted, else error code. */
return (ret == 1) ? count : ret;
}
EXPORT_SYMBOL(i2c_master_recv);
从上可以看到,i2c_master_send和i2c_master_recv传输数据,最终调用的函数接口都是i2c_transfer,唯一的区别就在数据包中的msg.flags标志位,接收消息时多了个I2C_M_RD标志的或运算。
/* ----------------------------------------------------
* the functional interface to the i2c busses.
* ----------------------------------------------------
*/
/**
* i2c_transfer - execute a single or combined I2C message
* @adap: Handle to I2C bus
* @msgs: One or more messages to execute before STOP is issued to
* terminate the operation; each message begins with a START.
* @num: Number of messages to be executed.
*
* Returns negative errno, else the number of messages executed.
*
* Note that there is no requirement that each message be sent to
* the same slave address, although that is the most common model.
*/
int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
int ret;
/* REVISIT the fault reporting model here is weak:
*
* - When we get an error after receiving N bytes from a slave,
* there is no way to report "N".
*
* - When we get a NAK after transmitting N bytes to a slave,
* there is no way to report "N" ... or to let the master
* continue executing the rest of this combined message, if
* that's the appropriate response.
*
* - When for example "num" is two and we successfully complete
* the first message but get an error part way through the
* second, it's unclear whether that should be reported as
* one (discarding status on the second message) or errno
* (discarding status on the first one).
*/
if (adap->algo->master_xfer) {
#ifdef DEBUG
for (ret = 0; ret < num; ret++) {
dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
"len=%d%s\n", ret, (msgs[ret].flags & I2C_M_RD)
? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
(msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
}
#endif
if (in_atomic() || irqs_disabled()) {
ret = mutex_trylock(&adap->bus_lock);
if (!ret)
/* I2C activity is ongoing. */
return -EAGAIN;
} else {
mutex_lock_nested(&adap->bus_lock, adap->level);
}
ret = adap->algo->master_xfer(adap,msgs,num);
mutex_unlock(&adap->bus_lock);
return ret;
} else {
dev_dbg(&adap->dev, "I2C level transfers not supported\n");
return -EOPNOTSUPP;
}
}
EXPORT_SYMBOL(i2c_transfer);
它们同i2c总线操作一样,都要回归于i2c标准的子系统中去,最终接口为i2c_transfer()。ioctl的应用也是一样,应用层调用的参考代码如下,
enum imagerStatus {
SET_CONTRAT = 0x1,
SET_BRIGHTNESS,
POWER_ON,
POWER_OFF,
};
int ret;
if(m_imagerFd<=0) {
m_imagerFd = open("/dev/ch7026", O_RDWR);
if(m_imagerFd<0) {
perror("open device ch7026:");
return false;
}
}
if(var){
ret = ioctl(m_imagerFd, POWER_ON, NULL);
}else{
ret = ioctl(m_imagerFd, POWER_OFF, NULL);
}
if(ret < 0) {
perror("ioctl:device ch7026:");
return false;
}
设备驱动ioctl部分实现的部分,每个宏定义都可以配置成一个子功能块,每个子模块可以配置多个寄存器;
#define SET_CONTRAT 0x01
#define SET_BRIGHTNESS 0x02
#define POWER_ON 0x03
#define POWER_OFF 0x04
typedef struct samsung_t{
unsigned char subaddr;
unsigned char value;
} ch7026_t;
static int ch7026_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int ret = 0;
int i;
unsigned char contrat = 0x30;
unsigned char brightness = 0x31;
unsigned char value;
value = arg;
switch (cmd) {
case SET_CONTRAT:
ret = ch7026_i2c_write(&p_bank->c, contrat, value);
if (ret == -EIO) {
printk("i2c transfer error\n");
return -EIO;
}
printk("vga_contrat = 0x:%x\n", value);
break;
case SET_BRIGHTNESS:
ret = ch7026_i2c_write(&p_bank->c, brightness, value);
if (ret == -EIO) {
printk("i2c transfer error\n");
return -EIO;
}
printk("vga_brightness = 0x:%x\n", value);
break;
case POWER_ON:
ch7026_config(&p_bank->c);
printk("ch7026 power up!\n");
break;
case POWER_OFF:
for (i = 0; i < CH7026_POW_OFF_REGS; i++) {
delay(50);
ret = ch7026_i2c_write(&p_bank->c,
ch7026_reg_pow_off[i].subaddr, ch7026_reg_pow_off[i].value);
if(ret != 0) printk("ch7026:write faild!\n");
}
printk("ch7026 power down!\n");
break;
default:
printk("unexpect command\n");
break;
}
return ret;
}
四、程序测试
应用层测试main函数:
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <termios.h>
static int fd;
static void print_usage(void);
/* Main function */
int main(int argc, char *argv[])
{
int ret;
int num;
int i;
int buf;//char buf;
unsigned int tmp;
unsigned int addr;
unsigned char val;
int buffer;
float multiple;
fd = open("/dev/ch7026", O_RDWR);
if(fd<0) {
perror("open device ch7026");
exit(1);
}
if( (argc == 4)&& (strcmp(argv[1], "write") == 0) ) {
if((argv[2][0]=='0') && (argv[2][1]=='x'))
sscanf(argv[2], "0x%x", &addr);
else
addr = atoi(argv[2]);
if((argv[3][0]=='0') && (argv[3][1]=='x'))
{
sscanf(argv[3], "0x%x", &tmp);
val = tmp&0xFF;
}
else
val = atoi(argv[3]);
printf("Write: addr[0x%02x] [0x%02x] \n", addr, val);
lseek(fd, addr, SEEK_SET);
write(fd, &val, sizeof(unsigned char));
} else if( (argc == 3) && (strcmp(argv[1], "read") == 0)) {
if((argv[2][0]=='0') && (argv[2][1]=='x'))
sscanf(argv[2], "0x%x", &addr);
else
addr = atoi(argv[2]);
lseek(fd, addr, SEEK_SET);
if(ret < 0) {
perror("lseek");
exit(1);
}
read(fd, &val, sizeof(val));
printf("Read: addr[0x%02x] [0x%02x] \n", addr, val);
} else {
print_usage();
exit(1);
}
close(fd);
return 0;
}
static void print_usage(void)
{
printf("usage:./ch7026_test [commad]\n");
printf("./ch7026_test write [address] [value]\n");
printf("./ch7026_test read [address]\n");
printf("For example:\n");
printf("./ch7026_test write 0x03 0x01\n");
printf("./ch7026_test read 0x03\n");
}
在PC Linux上用交叉编译器编译设备驱动模块(make)和应用程序(make test),
测试结果如下,可以看到应用层读写i2c从设备的0x06寄存器成功:
在编译设备驱动模块的时候,首先要先编译内核,因为设备驱动中的许多函数定义都来自内核,如i2c子系统,".o"后缀的就是内核已经编译好的。当然,Makefile还要加入编译好的内核路径。
Makefile的内容,更换自己的内核路径和交叉编译器
obj-m += ch7026.o
KERNEL=/opt/kernel-s3c6410/htx-linux-2.6.28-g96p-***** #更换成自己的内核路径
CC=/usr/local/arm/4.2.2-eabi/usr/bin/arm-linux-gcc #更换成自己平台的交叉编译器
default:
@make -C $(KERNEL) M=`pwd` modules
install:
@make -C /lib/modules/`uname -r`/build M=`pwd` modules_install
clean:
@make -C /lib/modules/`uname -r`/build M=`pwd` clean
rm ch7026_test -rf
test:
arm-linux-gcc ch7026_test.c -o ch7026_test
cp ch7026_test /mnt/hgfs/upload/
最后我把i2c总线方式和i2c设备驱动方式配置从设备的源码整理在一起上传到资源区(https://download.csdn.net/download/psy6653/11014339),
写了这么多本想设置2个下载积分安慰下自己得,但不知道怎么的系统默认设置为5分(但修改不了),实在抱歉。不过没有关系,有多的积分的朋友就赞助下哈,没积分的朋友可以给我留言,我会用wan盘单独分享给你。