Bootstrap

firefly-rk3288j开发板--linux I2C实验之eeprom驱动

firefly-rk3288j开发板–linux I2C实验之eeprom驱动

1 准备工作

开发板:aio-rk3288j
SDK版本:rk3288_linux_release_20210304
下载工具:Linux_Upgrade_Tool_v2.1
内核版本:4.4.194
文件系统:buildroot
Ubuntu版本:18.04
交叉编译工具:gcc version 6.3.1 20170404

2 硬件原理图

2.1 开发板I2C接口

在这里插入图片描述

2.2 EEPROM模块原理图

在这里插入图片描述

3 修改设备树文件

3.1 驱动参数配置

I2C 的参数配置最主要就是 I2C 频率的配置,可配 I2C frequency 除了与芯片有关外,主要是由 I2C SCL rise time 决定的,因为 I2C 协议标准里面对上升沿和下降沿时间有规定要求特别是上升沿时间,如果超 过了协议规定的最大值,则 I2C 通讯可能失败,下面是协议里面规定的最大最小值范围,下图表示了二 者之间的关系:
在这里插入图片描述
上升沿 Tr 和下降沿 Tf,需要用示波器测量,参考下面示图:
在这里插入图片描述

3.2 配置DTS

设备树文件位于内核kernel/arch/arm/boot/dts目录下,我们需要打开rk3288.dtsi、rk3288-linux.dtsi、rk3288-firefly-port.dtsi、rk3288-firefly-aio.dtsi.对于i2c设备只需要打开rk3288-firefly-aio.dtsi文件,添加i2c4设备节点:

&i2c4 {
	status = "okay";
	//i2c-scl-rising-time-ns = <400>;
	//i2c-scl-falling-time-ns = <20>;
	clock-frequency = <100000>;
	at24c64: at24c64@50 {
			compatible = "firefly,24c64";
			reg = <0x50>;
	};
};

clock-frequency: 默认 frequency 为 100k 可不配置,其它 I2C 频率需要配置,最大可配置频率由 i2c- scl-rising-time-ns 决定;例如配置 400k,clock-frequency=<400000>。
i2c-scl-rising-time-ns:SCL 上升沿时间由硬件决定,改变上拉电阻可调节该时间,需通过示波器量 测, 参考上图;例如测得 SCL 上升沿 400ns,i2c-scl-rising-time-ns=<400>。(默认可以不配置,但必须保证当前的上升沿时间不能超过所配置频率下的 I2C 标准所定义的最大上升沿时间)
i2c-scl-falling-time-ns: SCL 下降沿时间, 一般不变, 等同于 i2c-sda-falling-time-ns。(默认也可以不配置)
编译内核,输入如下命令
./build.sh kernel
./build.sh updateimg

在这里插入图片描述

4 API函数

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)

5 AT24C64驱动编写

#include <linux/module.h>//模块加载卸载函数
#include <linux/kernel.h>//内核头文件
#include <linux/types.h>//数据类型定义
#include <linux/fs.h>//file_operations结构体
#include <linux/device.h>//class_create等函数
#include <linux/ioctl.h>
#include <linux/kernel.h>/*包含printk等操作函数*/
#include <linux/of.h>/*设备树操作相关的函数*/
#include <linux/gpio.h>/*gpio接口函数*/
#include <linux/of_gpio.h>
#include <linux/platform_device.h>/*platform device*/
#include <linux/i2c.h> /*i2c相关api*/
#include <linux/delay.h> /*内核延时函数*/
#include <linux/slab.h> /*kmalloc、kfree函数*/
#include <linux/cdev.h>/*cdev_init cdev_add等函数*/
#include <asm/gpio.h>/*gpio接口函数*/
#include <asm/uaccess.h>/*__copy_from_user 接口函数*/

#define  DEVICE_NAME     "eeprom"
#define  DEVICE_SIZE     256  

typedef struct
{
    struct device_node *node;//设备树节点
    struct cdev cdev;//定义一个cdev结构体
    struct class *class;//创建一个at24cxx类
    struct device *device;//创建一个at24cxx设备 该设备是需要挂在at24cxx类下面的
    int major;//主设备号
    dev_t  dev_id;
    struct i2c_client   *client; /*适配器 probe函数中会填充此变量*/
    /*使用SMBUS协议方式读写*/
    int use_smbus;
    int use_smbus_write;
    struct mutex lock;
}at24xx_typdef;

static at24xx_typdef at24cxx_dev;//定义一个AT24Cxx设备
static unsigned char io_limit = 128; /*一次最多读取128字节*/
static unsigned char write_timeout = 25;/*i2c通信超时时间*/
static unsigned char at24cxx_page_size = 8;/*at24cxx 每页8字节*/

static ssize_t at24_eeprom_read(at24xx_typdef *at24, char *buf,unsigned offset, size_t count)
{
    struct i2c_msg msg[2];
    u8 msgbuf[2];
    struct i2c_client *client;
    unsigned long timeout, read_time;
    int status, i;
    
    memset(msg, 0, sizeof(msg));

   /*获取设备信息*/
    client = at24->client;

    if (count > io_limit)
        count = io_limit;

    if (at24->use_smbus) 
   {
        /* Smaller eeproms can work given some SMBus extension calls */
        if (count > I2C_SMBUS_BLOCK_MAX)
            count = I2C_SMBUS_BLOCK_MAX;
    } 
   else 
   {
        i = 0;
        msgbuf[i++] = offset;/*读取首地址*/

      /* msg[0]为发送要读取的首地址 */
        msg[0].addr = client->addr;
        msg[0].buf = msgbuf;
        msg[0].len = i;

      /* msg[1]读取数据 */
        msg[1].addr = client->addr;
        msg[1].flags = I2C_M_RD;
        msg[1].buf = buf;        /* 读取数据缓冲区*/
        msg[1].len = count;      /* 读取数据长度 */
    }
   
   /*超时时间设置为write_timeout毫秒*/
    timeout = jiffies + msecs_to_jiffies(write_timeout);
    do {
        read_time = jiffies;
        if (at24->use_smbus) /*使用SMBUS协议*/
        {
            status = i2c_smbus_read_i2c_block_data_or_emulated(client, offset,count, buf);
        } 
        else /*普通I2C协议*/
        {
            status = i2c_transfer(client->adapter, msg, 2);
            if (status == 2)
                status = count;
        }

        if (status == count)
            return count;
    
        /* REVISIT: at HZ=100, this is sloooow */
        msleep(1);
    } while (time_before(read_time, timeout));   
    return -ETIMEDOUT;
}

static ssize_t at24_eeprom_write(at24xx_typdef *at24,  char *buf,unsigned offset, size_t count)
{
    struct i2c_client *client;
    struct i2c_msg msg;
    ssize_t status = 0;
    unsigned long timeout, write_time;
    int i = 0;
    
   /*获取设备信息*/
    client = at24->client;

    /* 最大写入数据是1页 */
    if (count > at24cxx_page_size)
        count = at24cxx_page_size;

    /*msg.buf申请内存*/
    msg.buf = kmalloc(count+2,GFP_KERNEL);
    if(!msg.buf)
       return -ENOMEM;
    
    /* 不使用SMBUS协议  需要填充msg */
    if (!at24->use_smbus) 
    {
        msg.addr = client->addr;
        msg.flags = 0;           /*标记为写数据*/

        msg.buf[i++] = offset;   /*写的起始地址*/
        memcpy(&msg.buf[i], buf, count);
        msg.len = i + count;     /*写数据的长度*/
    }

    timeout = jiffies + msecs_to_jiffies(write_timeout);
    do {
        write_time = jiffies;
        if (at24->use_smbus_write) 
        {
            switch (at24->use_smbus_write) 
            {
                case I2C_SMBUS_I2C_BLOCK_DATA:
                status = i2c_smbus_write_i2c_block_data(client,offset, count, buf);break;
                case I2C_SMBUS_BYTE_DATA:
                status = i2c_smbus_write_byte_data(client,offset, buf[0]);break;
            }
            if (status == 0)
                status = count;
        } 
        else 
        {
                status = i2c_transfer(client->adapter, &msg, 1);
                if (status == 1)
                    status = count;
        }

        if (status == count)
        {
            kfree(msg.buf);
            return count;
        }               
        /* REVISIT: at HZ=100, this is sloooow */
        msleep(1);
    } while (time_before(write_time, timeout));

    kfree(msg.buf);
    return -ETIMEDOUT;
}

static int at24cxx_open(struct inode *inode, struct file *filp)
{
    /*使用普通I2C模式读写*/  
    at24cxx_dev.use_smbus = 0;
    at24cxx_dev.use_smbus_write = 0; 
    filp->private_data = &at24cxx_dev;  
    printk("open at24cxx success\n");
    return 0;
}

static int at24cxx_release(struct inode* inode ,struct file *filp)
{

    return 0;
}

static int at24cxx_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos)
{   
    int ret = -EINVAL;
    char *buffer;/*缓冲区*/
    unsigned char pages;/*页数*/
    unsigned char num;/*不足一页剩下的字节数*/
    unsigned char pos = filp->f_pos;/*写入的地址*/
    int i = 0; 
    at24xx_typdef *dev = (at24xx_typdef *)filp->private_data;
    
    printk("w_count = %d w_pos = %d\n",count,pos);

    buffer =(char *)kmalloc(count,GFP_KERNEL);
    if (!buffer)
        return -ENOMEM;

    pages = count / at24cxx_page_size;
    num = count % at24cxx_page_size;
    
    /*将需要写入的数据拷贝到内核空间的缓冲区*/
    ret = copy_from_user((void *)buffer,buf,count);

    mutex_lock(&dev->lock);
    for(i = 0; i < pages; i++)
    {
        ret = at24_eeprom_write(dev,&buffer[i*at24cxx_page_size],pos,at24cxx_page_size);
        if(ret < 0) 
        {
            printk("at24cxx write error\n");
            kfree(buffer);
            return ret;
        }
        pos += 8;
    }
    if(num)
    {   
        ret = at24_eeprom_write(dev,&buffer[i*at24cxx_page_size],pos,num);
        if(ret < 0) 
        {
            printk("at24cxx write error\n");
            kfree(buffer);
            return ret;
        }
    }   
   mutex_unlock(&dev->lock);    
   /*释放缓冲区内存*/
   kfree(buffer);
  return 0;
}

static ssize_t at24cxx_read(struct file *filp,char __user *buf, size_t count,loff_t *f_pos)
{
   int ret = -EINVAL;
   char *buffer;/*数据缓存区*/
   unsigned char pos = filp->f_pos; /*读取位置*/

   at24xx_typdef *dev = (at24xx_typdef *)filp->private_data;  
   printk("r_count = %d  r_pos = %d\n",count,pos);

   buffer =(char *)kmalloc(count,GFP_KERNEL);
   if(!buffer)
    return -ENOMEM;

   mutex_lock(&dev->lock);
   ret = at24_eeprom_read(dev,buffer,pos,count);
   if(ret < 0 )
   {
       printk("at24cxx read error\n");
       kfree(buffer);
       return ret;
   }
   /*将读取到的数据返回用户层*/
   ret = copy_to_user(buf,(void *)buffer,ret);
   mutex_unlock(&dev->lock);

   /*释放缓冲区内存*/
   kfree(buffer);
   return 0;
}

loff_t at24cxx_llseek(struct file *file, loff_t offset, int whence)
{
    loff_t ret,pos,oldpos;
    oldpos = file->f_pos;
    switch (whence) 
    {
        case SEEK_SET:
            pos = offset; 
            break;
        case SEEK_CUR:
            pos = oldpos + offset;
            break;
        case SEEK_END:
            pos = DEVICE_SIZE - offset;
            break;  
        default:
            printk("cmd not supported\n");
            break;
    }
    
    if(pos < 0 || pos > DEVICE_SIZE)
    {   
        printk("error: pos > DEVICE_SIZE !\n");
        ret = -EINVAL;
        return ret;
    }
    file->f_pos = pos;
    ret = offset;   
    return ret;
}

static struct file_operations at24cxx_fops={
    .owner      = THIS_MODULE,
    .open       = at24cxx_open,
    .write      = at24cxx_write,
    .read       = at24cxx_read,
    .release    = at24cxx_release,
    .llseek      = at24cxx_llseek,
};

static int at24cxx_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
   int ret = -1;
   const char *string = NULL;

   at24xx_typdef *dev = &at24cxx_dev;
   
   printk("at24cxx probe!\n"); 
   /*获取设备节点*/
   at24cxx_dev.node = of_find_node_by_path("/i2c@ff160000/at24c64@50");
   if(at24cxx_dev.node == NULL)
   {
      printk("find node by path fialed!\r\n"); 
      return -1;
   }
   
   /*读取at24cxx设备节点的compatible属性值*/
   ret = of_property_read_string(at24cxx_dev.node,"compatible",&string);
   if(ret == 0)
   {
      printk("%s\n",string);
   }

   /*申请设备号*/
   alloc_chrdev_region(&at24cxx_dev.dev_id,0,1,DEVICE_NAME);

   /*初始化一个cdev*/
   cdev_init(&at24cxx_dev.cdev,&at24cxx_fops);
  
   /*向cdev中添加一个设备*/
   cdev_add(&at24cxx_dev.cdev,at24cxx_dev.dev_id,1);

   /*创建一个eeprom_class类*/
   at24cxx_dev.class = class_create(THIS_MODULE, "eeprom_class");
   if(at24cxx_dev.class == NULL)
   {
      printk("class_create failed\r\n");
      return -1;
   }

   /*在eeprom_class类下创建一个eeprom_class设备*/
   at24cxx_dev.device = device_create(at24cxx_dev.class, NULL, at24cxx_dev.dev_id, NULL, DEVICE_NAME);

   /*每个设备都会分配一个client*/
   at24cxx_dev.client = client;
   printk("slave address is %x\n",client->addr);

   mutex_init(&dev->lock);

   return  0;
}

static int at24cxx_remove(struct i2c_client *client)
{  
    printk("at24cxx remove!\n"); 

    /*删除at24cxx类*/
    cdev_del(&at24cxx_dev.cdev);

    /*释放at24cxx设备号*/
    unregister_chrdev_region(at24cxx_dev.dev_id, 1);

    /*注销at24cxx设备*/
    device_destroy(at24cxx_dev.class, at24cxx_dev.dev_id);

    /*注销at24cxx类*/
    class_destroy(at24cxx_dev.class);

   return 0;
}

static const struct of_device_id at24cxx_of_match[] = {
   {.compatible = "firefly,24c64"},
   {},
};

static const struct i2c_device_id at24cxx_id[] = {
    { "xxxx", 0 },
    {},
};

static struct i2c_driver at24cxx_driver = {

   .driver = {
      .owner = THIS_MODULE,
      .name = "firefly,24c64",
      .of_match_table = at24cxx_of_match,
   },
   .probe = at24cxx_probe,
   .remove  = at24cxx_remove,  
   .id_table    = at24cxx_id,      
};

static int __init at24cxx_init(void)
{
   return i2c_add_driver(&at24cxx_driver);
}

static void at24cxx_exit(void)
{
   i2c_del_driver(&at24cxx_driver);
   printk("module exit ok\n");
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("at24cxx driver");
MODULE_AUTHOR("lsjml2022");

6 编写测试App

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <limits.h>
#include <asm/ioctls.h>
#include <time.h>
#include <pthread.h>
#include<string.h>

void print_data(const char *title, char *dat,int count)
{
   int i = 0; 

   printf(title);
   for(i = 0; i < count; i++) 
   {
      printf(" 0x%x", dat[i]);
   }
   printf("\n");
}

int main(int argc, char *argv[])
{
   int fd,i,ret;
   int count = 128;
   char offset = 0;
   char writebuf[128],readbuf[128];

   /*判断传入的参数是否合法*/
   if(argc != 2)
   {
      printf("Usage:error!\n");
      return -1;
   }
  
   /*解析传入的参数*/
   offset =atoi(argv[1]);

   printf("offset = %d\n",offset);
 
    /*打开设备文件*/
   fd = open("/dev/eeprom",O_RDWR);
   if(fd < 0)
   {
      printf("open eeprom fail fd = %d\n",fd); 
      close(fd);
      return fd;
   }

   /*缓存数组赋值*/
   memset(writebuf, 0xaa, sizeof(writebuf));
      
   /*写入数据*/ 
   lseek(fd,offset, SEEK_SET);

   ret = write(fd, writebuf, sizeof(writebuf));

   if(ret < 0)
   {
      printf("write to at24c64 error\n");
      close(fd);
      return ret;
   }

   /*打印数据*/
   print_data("write to at24c64:", writebuf, count);

   /*读取数据*/
   ret = lseek(fd,offset,SEEK_SET);

   printf("lseek = %d\n",ret);
   
   memset(readbuf, 0, sizeof(readbuf));

   ret = read(fd, readbuf,  count);
   if(ret < 0)
   {
      printf("read from at24c64 error\n");
      close(fd);
      return ret;
   }
   
   /*打印数据*/
   print_data("read from at24c64:",readbuf,count);
   /*比较写入数据与读出数据是否一致*/
   ret = memcmp(readbuf, writebuf,  count);
   if(ret)
   {
      printf("Writing data is different from reading data...\n");
   }
   else
   {
      printf("Write data is the same as read data...\n");
   }

   close(fd);

   return 0;   
}

7 编译驱动程序和测试APP

7.1 编译驱动程序

KDIR:=/rk3288_linux/rk3288_linux_release_20220607/kernel
obj-m:=at24c64.o
PWD:=$(shell pwd)
all:
    $(MAKE) -C $(KDIR) M=$(PWD) 
clean:
    rm -rf *.ko *.order *.symvers *.cmd *.o *.mod.c *.tmp_versions .*.cmd .tmp_versions

输入如下命令编译出驱动模块文件:
make -j8
在这里插入图片描述
编译成功后会生成一个.ko文件拷贝到开发板上并加载

7.2 编译测试App

输入如下命令编译测试 eepromApp.c 这个测试程序:
arm-linux-gnueabihf-gcc eepromApp.c -o eepromApp
编译成功以后就会生成 eepromApp 这个应用程序

7.3 运行测试

编译出来的.ko 和 eepromApp 这两个文件拷贝到/lib/modules/4.4.194目录中,重启开发板,进入目录/lib/modules/4.4.194中输入加载.ko驱动模块:
insmod at24c64.ko
在这里插入图片描述
驱动加载成功以后就可以使用eepromApp软件来测试驱动是否正常,输入如下命令:
./eepromApp /dev/eeprom
在这里插入图片描述
rmmod at24c64.ko //卸载驱动模块
在这里插入图片描述

;