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接口
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4Q2MkRjZ4EzN1ITZ4E2M5EmM2QjYwgTOlRjMycDMkdzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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: [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
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
編譯成功後會生成一個.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 //解除安裝驅動子產品