Linux i2c子系統應用之Linux ARM嵌入式i2c通信(設備驅動完成i2c從設備寄存器的配置)

一、前言

        本文主要分爲三個部分,第一部分,介紹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盤單獨分享給你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章