天天看點

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動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接口

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

2.2 EEPROM子產品原理圖

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

3 修改裝置樹檔案

3.1 驅動參數配置

I2C 的參數配置最主要就是 I2C 頻率的配置,可配 I2C frequency 除了與晶片有關外,主要是由 I2C SCL rise time 決定的,因為 I2C 協定标準裡面對上升沿和下降沿時間有規定要求特别是上升沿時間,如果超 過了協定規定的最大值,則 I2C 通訊可能失敗,下面是協定裡面規定的最大最小值範圍,下圖表示了二 者之間的關系:

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

上升沿 Tr 和下降沿 Tf,需要用示波器測量,參考下面示圖:

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

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: [email protected]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

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

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("/[email protected]/[email protected]");
   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

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

編譯成功後會生成一個.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

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

驅動加載成功以後就可以使用eepromApp軟體來測試驅動是否正常,輸入如下指令:

./eepromApp /dev/eeprom

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動

rmmod at24c64.ko //解除安裝驅動子產品

firefly-rk3288j開發闆--linux I2C實驗之eeprom驅動firefly-rk3288j開發闆–linux I2C實驗之eeprom驅動