I2C总线(五)I2C子系统(3)-— EEPROM实例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JerryGou/article/details/82891050

这一篇主要是在友善的Smart210开发板上写一个符合linux的iic驱动模型的设备驱动程序,这样能有一个更感性的认识。
开发环境介绍:
主机linux版本:Ubuntu18.04
开发板:三星的Louis210
嵌入式linux版本:linux-3.10.46
交叉编译器:arm-linux-gcc-4.3.3
硬件简单介绍:

从图可以看出来EEPROM是和Louis210上的第0个iic适配器连接的。

注册设备信息
阅读linux下的Documentation/i2c/instantiating-devices 文档可以知道有两种方式可以注册,咱们只说前一种。打开:linux-3.10.46/arch/arm/mach-Louis210/mach-Louis210.c这个.c文件。就是在这个文件中填写咱们设备的信息的,这就是所说的bsp文件。首先添加头文件#include <linux/i2c/at24.h> 因为linux专门问iic接口的eeprom提供了相应的数据结构,要是不加,肯定要报错。接下来添加如下信息:

static struct at24_platform_data at24c08 = {
    .byte_len = SZ_2K / 8,  //eeprom的容量大小(地址的总数)
    .page_size = 8,        //eeprom的一页中包含的字节数
}; 

然后添加如下的信息,主要把eeprom的信息包装成符合iic模型中的设备信息的格式

static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {

	{ 
            I2C_BOARD_INFO("24c02", 0x50), 
            *platform_data = at24c08,
        },		/* add by Louis */
};
后边的0x50是eeprom的地址,可能有人说应该是0xa0,但linux中需要的是7bit的地址,所以向右移一位,
正好是0x50。当然了,最终会在linux的iic的读函数和写函数中变成0xa0和0xa1的格式

最后在Louis210_machine_init函数中把上面写的信息注册到iic总线上

static void __init Louis210_machine_init(void)
{
        ...
        s3c_i2c0_set_platdata(NULL);
	s3c_i2c1_set_platdata(NULL);
	s3c_i2c2_set_platdata(NULL);
	i2c_register_board_info(0, smdkv210_i2c_devs0, ARRAY_SIZE(smdkv210_i2c_devs0));
	i2c_register_board_info(1, smdkv210_i2c_devs1, ARRAY_SIZE(smdkv210_i2c_devs1));
	i2c_register_board_info(2, smdkv210_i2c_devs2, ARRAY_SIZE(smdkv210_i2c_devs2));
        ...
}

这就算把设备信息注册上了,重新编译一下你的linux内核吧,然后把编译好的内核烧进开发板,下面开始就是真真的驱动部分了。

设备驱动编写
首先咱们是用eeprom读写一些数据,数据量不会很大,所以它应该是个字符设备,尽管它从iic驱动模型的角度说,是iic设备,起始这并不矛盾。因为字符设备里包括了一部分的iic设备,下面就是整个驱动了

/*
 * at24.c - handle most I2C EEPROMs
 *
 * Copyright (C) 2005-2007 David Brownell
 * Copyright (C) 2008 Wolfram Sang, Pengutronix
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */
#include<linux/module.h>
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/fs.h>
#include<asm/uaccess.h>
#include<linux/i2c.h>
#include<linux/miscdevice.h>
#include<linux/slab.h>
#include<linux/list.h>
#include<linux/delay.h>

#define DEVICE_NAME "24c02"
//#define DEBUG
#define MEMDEV_SIZE (2048 / 8)
#define AT24_SIZE_BYTELEN 5
#define AT24_SIZE_FLAGS 8

struct At24c02_dev
{
    char name[30];
    unsigned write_max;
    struct mutex lock;  //互斥锁
    struct i2c_client *at24c02_client;
    struct miscdevice at24c02_miscdev;  //因为本身是一个字符设备,所以定义成一个杂项设备
};

struct At24c02_dev *At24c02_devp;

/*open函数将全局的At24c02_devp赋给file文件的私有数据,方便其他函数调用*/
static int at24c02_open(struct inode *inode, struct file *filp)
{
    filp->private_data = At24c02_devp;
    return 0;
}

static ssize_t at24_eeprom_read(struct file *filp, char *buf, loff_t offset, size_t count)
{
    int transferred = 0;
    struct At24c02_dev *dev = (struct At24c02_dev *)filp->private_data;

	if (!i2c_check_functionality(dev->at24c02_client->adapter, I2C_FUNC_SMBUS_READ_BYTE_DATA))
	    return -ENODEV;

    while(transferred < count)
    {
        msleep(10);
        //这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的
        buf[transferred++] = i2c_smbus_read_byte_data(dev->at24c02_client,offset + transferred);
        
        if(transferred >= dev->write_max)
            break;
    }
    
    return transferred;
}

static ssize_t at24_eeprom_write(struct file *filp, const char *buf, loff_t offset, size_t count)
{
    int transferred = 0;
    struct At24c02_dev *dev = (struct At24c02_dev *)filp->private_data;

	if (!i2c_check_functionality(dev->at24c02_client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
	    return -ENODEV;

    while(transferred < count)
    {
        msleep(10);
        //这里一定注意,要不这个延时加上,因为cpu速度比较快,eeprom速度比较慢,所以不加会出问题,我调试时就出问题了,后加的
        i2c_smbus_write_byte_data(dev->at24c02_client,offset + transferred, buf[transferred]);
        //这个函数通过adapter的通信方法把一个字节的数据发送 到iic设备中去
        transferred++;
        
        if(transferred >= dev->write_max)
            break;
    }

    return transferred;   
}

static ssize_t at24_bin_read(struct file *filp, char *buf, size_t count, loff_t *ppos)
{
    ssize_t retval = 0;
    ssize_t status;
    loff_t off;
    struct At24c02_dev *dev;
    
    if(unlikely(!count))
        return count;
    
    dev = (struct At24c02_dev *)filp->private_data;
    off = filp->f_pos;
    
    mutex_lock(&dev->lock);     /* 访问设备前,加锁*/
    
    while(count){
        status = at24_eeprom_read(filp, buf, off, count);
        
        if(status <= 0){
            if(retval == 0)
                retval = status;
            break;
        }
        
        buf += status;
        off += status;
        count -= status;
        retval += status;
    }
    
    mutex_unlock(&dev->lock);
    
    return retval;
}

static ssize_t at24_bin_write(struct file *filp, const char *buf, size_t count, loff_t *ppos)
{
    ssize_t retval = 0; 
    ssize_t status;   
    loff_t off;    
    struct At24c02_dev *dev;
    
    if(unlikely(!count))
        return count;
    
    dev = (struct At24c02_dev *)filp->private_data;
    off = filp->f_pos;
    
    mutex_lock(&dev->lock);     /* 访问设备前,加锁*/
    
    while(count){
        status = at24_eeprom_write(filp, buf, off, count);
        
        if(status <= 0){
            if(retval == 0)
                retval = status;
            break;
        }
        
        buf += status;
        off += status;
        count -= status;
        retval += status;
    }
    
    mutex_unlock(&dev->lock);
    
    return retval;    
}

static loff_t at24_llseek(struct file *filp, loff_t offset, int whence)
{
    loff_t newpos;
    switch(whence) {
      case 0: /* SEEK_SET */
        newpos = offset;
        break;
 
      case 1: /* SEEK_CUR */
        newpos = filp->f_pos + offset;
        break;
 
      case 2: /* SEEK_END */
        newpos = MEMDEV_SIZE - 1 + offset;
        break;
 
      default: /* can't happen */
        return -EINVAL;
    }
    if ((newpos<0) || (newpos > MEMDEV_SIZE))
        return -EINVAL;
    filp->f_pos = newpos;
    
    return newpos;
}

static struct file_operations at24c02_fops =
{
    .owner = THIS_MODULE,
    .open = at24c02_open,
    .write = at24_bin_write,
    .read = at24_bin_read,
    .llseek = at24_llseek,
    .release = NULL,
};

static int at24c02_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret;
#ifdef DEBUG
    printk(KERN_NOTICE"The routine of at24c02 probe!\n");
#endif
    At24c02_devp = kzalloc(sizeof(struct At24c02_dev), GFP_KERNEL);
    if(!At24c02_devp)
		return -ENOMEM;
    memset(At24c02_devp, 0, sizeof(struct At24c02_dev));
    At24c02_devp->at24c02_client = client;  //把分配的i2c_client赋给定义的全局变量

    //杂项设备定义
    At24c02_devp->at24c02_miscdev.minor = MISC_DYNAMIC_MINOR;
    At24c02_devp->at24c02_miscdev.name = DEVICE_NAME;
    At24c02_devp->at24c02_miscdev.fops = &at24c02_fops;
    
    At24c02_devp->write_max = 50;

    mutex_init(&At24c02_devp->lock);    	  /* 初始化设备锁 */

    ret = misc_register(&At24c02_devp->at24c02_miscdev);    //注册杂项设备
#ifdef DEBUG
    printk(KERN_NOTICE"The driver of at24c02 has register!\n");
#endif
    return ret;
}

static int at24c02_remove(struct i2c_client *client)
{
    misc_deregister(&At24c02_devp->at24c02_miscdev);
#ifdef DEBUG
    printk(KERN_NOTICE"The routine of at24c02 remove!\n");
#endif
    return 0;
}

#define AT24_DEVICE_MAGIC(_len, _flags) 		\
	((1 << AT24_SIZE_FLAGS | (_flags)) 		\
	    << AT24_SIZE_BYTELEN | ilog2(_len))

static const struct i2c_device_id at24_ids[] = {
	/* old variants can't be handled with this generic entry! */
	{ "24c01", AT24_DEVICE_MAGIC(1024 / 8, 0) },    //1K bit
	{ "24c02", AT24_DEVICE_MAGIC(2048 / 8, 0) },    //2K bit
	{ "24c04", AT24_DEVICE_MAGIC(4096 / 8, 0) },    //4K bit
	/* 24rf08 quirk is handled at i2c-core */
	{ "24c08", AT24_DEVICE_MAGIC(8192 / 8, 0) },    //8K bit
	{ "24c16", AT24_DEVICE_MAGIC(16384 / 8, 0) },    //16K bit
	{ "at24", 0 },
	{ /* END OF LIST */ }
};

//当把设备挂接到总线上时,就调用这里面的名字和注册在总线里的名字比对,
//如果一样就会调用probe函数,同时给挂接的设备分配i2c_client结构体
MODULE_DEVICE_TABLE(i2c, at24_ids);

struct i2c_driver at24c02_driver =
{
    .driver = {
        .name = DEVICE_NAME,
        .owner = THIS_MODULE,
    },
    .probe = at24c02_probe,
    .remove = at24c02_remove,
    .id_table = at24_ids,
};

static int __init at24c02_init(void)
{
    printk(KERN_NOTICE"The driver of at24c02 is insmod!\n");
    return i2c_add_driver(&at24c02_driver); //把iic设备挂接到总线上
}

static void __exit at24c02_exit(void)
{
    printk(KERN_NOTICE"at24c02 is remmod!\n");
    i2c_del_driver(&at24c02_driver); 
}

MODULE_DESCRIPTION("AT24C02 eeprom driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LouisGou");

module_init(at24c02_init);
module_exit(at24c02_exit);

上面就是完整的eeprom驱动,当然驱动写完了,需要写个简单的Makefile来编译这个驱动,好吧,下面就是Makefile文件的内容

obj-m := at24.o
KDIR := /root/code/linux-stable-3.10.46
all:
	@make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm
clean:
	@rm -rf *.o *.ko *.order *.symvers *.bak .*.cmd *.mod.o *.mod.c .tmp_versions
cp:
	@make clean
	@make
	cp *.ko /root/code/rootfs/root/at24c02

上面的Makefile文件很是简单,就不做过多的解释了。当把驱动编译好了,用动态的方式挂载到了linux内核上后,你还得做个简单的测试程序,来验证咱们写的驱动工作是否正常,下面就直接贴出来吧。

#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

void print_usage(char *file)
{
	printf("%s <i2c-0>  r addr\n", file);
	printf("%s <i2c-0>  w addr val\n", file);
}
 
int main(int argc, char **argv)
{
    int i;
    int ret;
    char value[19] = "eeprom-driver test!";
    char backvalue[19];
    unsigned char addr, data;
    
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}

    int fd;
    fd = open("/dev/24c02",O_RDWR);
    
    if(fd<0){
        printf("Open at24c02 device failed!\n");
        exit(1);
    }
    
    addr = strtoul(argv[2], NULL, 0);
    
    ret = lseek(fd, addr, SEEK_SET);
    if(ret == -1)
    {
        perror("<lseek>");
    }
    
    if(strcmp(argv[1], "r") == 0)
    {
        read(fd, &data, 1);
		printf("read data:%#x %#x %c\n", addr, data, data);
    }
    else if((strcmp(argv[1], "w") == 0) && (argc == 4))
    {
        data = strtoul(argv[3], NULL, 0);
		write(fd, &data, 1);
        printf("write data:%#x %#x %c\n", addr, data, data);
    }
    else
    {
        print_usage(argv[0]);
        close(fd);
		return -1;
    }
    
    close(fd);
    return 0;
}
#include <stdio.h>
#include <linux/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>

void print_usage(char *file)
{
	printf("%s <i2c-0>  r addr\n", file);
	printf("%s <i2c-0>  w addr val\n", file);
}
 
int main(int argc, char **argv)
{
    int i;
    int ret;
    char value[19] = "eeprom-driver test!";
    char backvalue[19];
    unsigned char addr;
    char data[100] = "0123456789qwertyuiopasdfghjklzxcvbnm,./;'[]\/*-+?><";
    
	if ((argc != 3) && (argc != 4))
	{
		print_usage(argv[0]);
		return -1;
	}

    int fd;
    fd = open("/dev/24c02",O_RDWR);
    
    if(fd<0){
        printf("Open at24c02 device failed!\n");
        exit(1);
    }
    
    addr = strtoul(argv[2], NULL, 0);
    
    ret = lseek(fd, addr, SEEK_SET);
    if(ret == -1)
    {
        perror("<lseek>");
    }
    
    if(strcmp(argv[1], "r") == 0)
    {
        printf("read data:%#x\n", addr);
        read(fd, &data, 100);
		printf("read data:%#x %#x %#s\n", addr, data, data);
    }
    else if((strcmp(argv[1], "w") == 0) && (argc == 4))
    {
        //data = strtoul(argv[3], NULL, 0);
        
		write(fd, &data, strlen(data));
        printf("write data:%#x %#x %#s\n", addr, data, data);
    }
    else
    {
        print_usage(argv[0]);
        close(fd);
		return -1;
    }
    
    close(fd);
    return 0;
}

哈哈,驱动就写完了,我自己测试了,没问题,你可以试试,下一篇我们会分析iic总线驱动。

猜你喜欢

转载自blog.csdn.net/JerryGou/article/details/82891050