Linux源码中I2C驱动目录介绍:
目录/文件 | 介绍 |
---|---|
i2c-core.c | I2C核心功能以及proc/bus/i2c*接口 |
i2c-dev.c | I2C适配器的设备文件,每一个I2C适配器都视为一个设备,主设备号都是89,并提供通用的open、read、write接口,用户层可以直接调用这些接口访问挂在此适配器下的真实I2C设备。 |
busses文件夹 | 包含不同芯片的I2C主机控制器的驱动 |
algos文件夹 | 实现了一些I2C适配器的通信方法 |
Linux I2C的设备体系主要分为3个部分:
I2C核心提供了I2C总线驱动和设备驱动注册、注销函数,I2C通信函数、探测设备、检测设备地址函数等。
//Linux/drivers/i2c/i2c-core.c
/*注册和注销i2c驱动*/
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
/*注测和注销i2c适配器*/
int i2c_add_adapter(struct i2c_adapter *adapter);
void i2c_del_adapter(struct i2c_adapter *adap);
/*i2c传输函数*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
/*i2c地址探测函数*/
static int i2c_detect_address(struct i2c_client *temp_client,
struct i2c_driver *driver)
I2C总线驱动就是SOC中I2C控制器(在Linux中称为I2C适配器)驱动,其主要包括下面几个部分:
struct i2c_adapter {
struct module *owner;
unsigned int class; /* classes to allow probing for */
const struct i2c_algorithm *algo; /* the algorithm to access the bus */
void *algo_data;
/* data fields that are valid for all devices */
struct rt_mutex bus_lock;
int timeout; /* in jiffies */
int retries;
struct device dev; /* the adapter device */
int nr;
char name[48];
struct completion dev_released;
struct mutex userspace_clients_lock;
struct list_head userspace_clients;
struct i2c_bus_recovery_info *bus_recovery_info;
const struct i2c_adapter_quirks *quirks;
};
//kernel/include/linux/i2c.h
struct i2c_algorithm {
/* If an adapter algorithm can't do I2C-level access, set master_xfer
to NULL. If an adapter algorithm can do SMBus access, set
smbus_xfer. If set to NULL, the SMBus protocol is simulated
using common I2C messages */
/* master_xfer should return the number of messages successfully
processed, or a negative value on error */
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
/* To determine what the adapter supports */
u32 (*functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
int (*reg_slave)(struct i2c_client *client);
int (*unreg_slave)(struct i2c_client *client);
#endif
};
I2C设备驱动就是真实物理设备的驱动程序,这些设备通常都是挂在I2C适配器下面的,用户程序不能直接访问设备,必须通过I2C适配器来访问,而访问用到的接口函数都是由I2C核心层提供。
I2C设备驱动有两个重要的数据结构:i2c_client和i2c_driver。
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
i2c_slave_cb_t slave_cb; /* callback for slave mode */
#endif
};
一个设备对应一个 i2c_client,每检测到一个 I2C 设备就会给这个 I2C 设备分配一个i2c_client。
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared. You should avoid
* using this, it will be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *) __deprecated;
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
/* Alert callback, for example for the SMBus alert protocol.
* The format and meaning of the data value depends on the protocol.
* For the SMBus alert protocol, there is a single bit of data passed
* as the alert response's low bit ("event flag").
*/
void (*alert)(struct i2c_client *, unsigned int data);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, struct i2c_board_info *);
const unsigned short *address_list;
struct list_head clients;
};
i2c_driver结构体类似platform_driver,是我们编写 I2C设备驱动重点要处理的内容。
i2c设备和驱动的匹配过程是依靠I2C核心完成的,I2C总线定义如下:
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
EXPORT_SYMBOL_GPL(i2c_bus_type);
.match 就是 I2C 总线的设备和驱动匹配函数,在这里就是 i2c_device_match 这个函数,此函数内容如下::
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
/* Attempt an OF style match */
if (of_driver_match_device(dev, drv))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
与platform总线设备与驱动匹配很类似,I2C设备和驱动匹配有三种方式:
i2c4个重要的数据结构就是上文提到的:i2c_adapter、i2c_algorithm、i2c_driver和i2c_client。
i2c_adapter对应物理上的1个i2c接口(主机),i2c_algorithm是I2C主机通信的一套方法,i2c_adapter需要使用i2c_algorithm提供的通信函数控制适配器产生特定的波形。i2c_adapter结构体中包含其所使用i2c_algorithm的指针。
i2c_algorithm数据结构中关键的方法master_xfer()用于产生I2C通信的波形,以i2c_msg(I2C消息)为单位。
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
#define I2C_M_TEN 0x0010 /* this is a ten bit chip address */
#define I2C_M_RD 0x0001 /* read data, from slave to master */
#define I2C_M_STOP 0x8000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_NOSTART */
#define I2C_M_REV_DIR_ADDR 0x2000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK 0x1000 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
i2c_msg定义了I2C通信的地址,方向,缓存区等信息。
i2c_driver是i2c设备驱动,i2c_client是真实的设备,每个设备都需要一个i2c_client描述。当I2C设备和驱动匹配后,驱动中的probe函数就会被执行。每个i2c_driver驱动可以对应多个i2c_client,也就是同一个驱动程序,可以驱动多个同类型的物理设备。
i2c_client是挂在i2c_adapter下面的,因为物理设备肯定是直接接在主机I2C接口上的。一个适配器可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adapter中包括依附于他的i2c_client的链表。
I2C适配器驱动是一个标准的platform驱动,它的作用是向内核注册适配器,这里以RK3288开发板的I2C适配器驱动为例。驱动源码位置:kernel/drivers/i2c/busses/i2c-rk3x.c
rk3x_i2c结构体包含了RK3288 I2C控制器的所有私有数据(时钟,寄存器,i2c信息块,i2c状态)
struct rk3x_i2c {
struct i2c_adapter adap;
struct device *dev;
struct rk3x_i2c_soc_data *soc_data;
/* Hardware resources */
void __iomem *regs;
struct clk *clk;
struct clk *pclk;
struct notifier_block clk_rate_nb;
/* Settings */
struct i2c_timings t;
/* Synchronization & notification */
spinlock_t lock;
wait_queue_head_t wait;
bool busy;
/* Current message */
struct i2c_msg *msg;
u8 addr;
unsigned int mode;
bool is_last_msg;
/* I2C state machine */
enum rk3x_i2c_state state;
unsigned int processed;
int error;
unsigned int suspended:1;
struct notifier_block i2c_restart_nb;
};
接着分析下probe函数:
static int rk3x_i2c_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *match;
struct rk3x_i2c *i2c;
struct resource *mem;
int ret = 0;
int bus_nr;
u32 value;
int irq;
unsigned long clk_rate;
/*为I2C控制器结构体申请内存*/
i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
if (!i2c)
return -ENOMEM;
/*匹配设备树中对应的of_device_id 获取私有数据*/
match = of_match_node(rk3x_i2c_match, np);
i2c->soc_data = (struct rk3x_i2c_soc_data *)match->data;
/* use common interface to get I2C timing properties */
i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
/*填充适配器信息*/
strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &rk3x_i2c_algorithm;/*适配器的通信方法*/
i2c->adap.retries = 3;
i2c->adap.dev.of_node = np;
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
i2c->dev = &pdev->dev;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);/*初始化一个i2c等待队列头*/
i2c->i2c_restart_nb.notifier_call = rk3x_i2c_restart_notify;
i2c->i2c_restart_nb.priority = 128;
ret = register_i2c_restart_handler(&i2c->i2c_restart_nb);
if (ret) {
dev_err(&pdev->dev, "failed to setup i2c restart handler.\n");
return ret;
}
/*从设备树中获取 I2C1 控制器寄存器物理基地址*/
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
/*获取到寄存器基地址以后使用 devm_ioremap_resource 函数对其进行内存映射,得到可以在 Linux 内核中使用的虚拟地址。*/
i2c->regs = devm_ioremap_resource(&pdev->dev, mem);
if (IS_ERR(i2c->regs))
return PTR_ERR(i2c->regs);
/* 根据设备树设置适配器的ID */
bus_nr = of_alias_get_id(np, "i2c");
/*
* Switch to new interface if the SoC also offers the old one.
* The control bit is located in the GRF register space.
*/
/*I2C引脚配置在GRF控制器中,所以也需要把GRF控制器地址映射出来*/
if (i2c->soc_data->grf_offset >= 0) {
struct regmap *grf;
grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
if (IS_ERR(grf)) {
dev_err(&pdev->dev,
"rk3x-i2c needs 'rockchip,grf' property\n");
return PTR_ERR(grf);
}
if (bus_nr < 0) {
dev_err(&pdev->dev, "rk3x-i2c needs i2cX alias");
return -EINVAL;
}
/* 27+i: write mask, 11+i: value */
value = BIT(27 + bus_nr) | BIT(11 + bus_nr);
ret = regmap_write(grf, i2c->soc_data->grf_offset, value);
if (ret != 0) {
dev_err(i2c->dev, "Could not write to GRF: %d\n", ret);
return ret;
}
}
/* IRQ setup */
/*获取i2c中断号*/
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "cannot find rk3x IRQ\n");
return irq;
}
/*申请中断*/
ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
0, dev_name(&pdev->dev), i2c);
if (ret < 0) {
dev_err(&pdev->dev, "cannot request IRQ\n");
return ret;
}
platform_set_drvdata(pdev, i2c);
if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
/* Only one clock to use for bus clock and peripheral clock */
i2c->clk = devm_clk_get(&pdev->dev, NULL);
i2c->pclk = i2c->clk;
} else {
i2c->clk = devm_clk_get(&pdev->dev, "i2c");
i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
}
if (IS_ERR(i2c->clk)) {
ret = PTR_ERR(i2c->clk);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Can't get bus clk: %d\n", ret);
return ret;
}
if (IS_ERR(i2c->pclk)) {
ret = PTR_ERR(i2c->pclk);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "Can't get periph clk: %d\n", ret);
return ret;
}
/*配置时钟*/
ret = clk_prepare(i2c->clk);
if (ret < 0) {
dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
return ret;
}
ret = clk_prepare(i2c->pclk);
if (ret < 0) {
dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
goto err_clk;
}
i2c->clk_rate_nb.notifier_call = rk3x_i2c_clk_notifier_cb;
ret = clk_notifier_register(i2c->clk, &i2c->clk_rate_nb);
if (ret != 0) {
dev_err(&pdev->dev, "Unable to register clock notifier\n");
goto err_pclk;
}
clk_rate = clk_get_rate(i2c->clk);
rk3x_i2c_adapt_div(i2c, clk_rate);
/*向内核注册该适配器*/
ret = i2c_add_adapter(&i2c->adap);
if (ret < 0) {
dev_err(&pdev->dev, "Could not register adapter\n");
goto err_clk_notifier;
}
dev_info(&pdev->dev, "Initialized RK3xxx I2C bus at %p\n", i2c->regs);
return 0;
err_clk_notifier:
clk_notifier_unregister(i2c->clk, &i2c->clk_rate_nb);
err_pclk:
clk_unprepare(i2c->pclk);
err_clk:
clk_unprepare(i2c->clk);
return ret;
}
static const struct i2c_algorithm rk3x_i2c_algorithm = {
.master_xfer = rk3x_i2c_xfer,
.functionality = rk3x_i2c_func,
};
/*functionality用于返回此I2C适配器支持什么样的通信协议*/
static u32 rk3x_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}
static int rk3x_i2c_xfer(struct i2c_adapter *adap,
struct i2c_msg *msgs, int num)
{
/*这里就是I2C通信基本的那一套流程:起始信号、应答、读写、结束信号*/
}
master_xfer()函数的实现有很多种方式,但多数驱动以中断方式来完成这个流程,比如发生硬件操作时把自己调度出去,当硬件操作完成,一般是某个标志位置位或者是发生中断,再唤醒进程继续操作。
rk3x_i2c_xfer函数中使用的rk3x_i2c_start、rk3x_i2c_stop等函数用于完成适配器底层的硬件操作,与I2C适配器和CPU的具体硬件相关,需要由工程师根据芯片的数据手册来实现。
static void rk3x_i2c_handle_write(struct rk3x_i2c *i2c, unsigned int ipd);
static void rk3x_i2c_handle_read(struct rk3x_i2c *i2c, unsigned int ipd);
static void rk3x_i2c_handle_stop(struct rk3x_i2c *i2c, unsigned int ipd);
static void rk3x_i2c_handle_start(struct rk3x_i2c *i2c, unsigned int ipd);
源代码位置:kernel/drivers/misc/eeprom/at24.c
at24c02的驱动为例:
一般情况下soc都会有多个i2c接口,所以需要根据实际情况在对应的i2c节点下添加AT24C02设备节点
在rk3288-firefly.dts中增加如下节点信息:
&i2c1
{
status = "okay";
clock-frequency = <100000>;
at24c02: at24c02@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
注意:我的at24c02的实际设备地址是0xA0,设备树中需要右移一位。
使用新的设备树启动内核后,在用户空间:/sys/bus/i2c/devices目录下应该可以看到名为1-0050的设备。(1:表示i2c1 50是从机地址 )
#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 "rk3288_i2c"
#define DEVICE_SIZE 256 /*24c02 为256字节*/
typedef struct
{
struct device_node *node;//设备树节点
struct cdev cdev;//定义一个cdev结构体
struct class *class;//创建一个at24c02类
struct device *device;//创建一个at24c02设备 该设备是需要挂在at24c02类下面的
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;/*at24c02 每页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;
}
printk("read %zu@%d --> %d (%ld)\n",count, offset, status, jiffies);
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;
}
printk("write %zu@%d --> %zd (%ld)\n",count, offset, status, jiffies);
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 at24c02 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 = devm_kzalloc(&dev->client->dev,count,GFP_KERNEL);
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("at24c02 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("at24c02 write error\n");
kfree(buffer);
return ret;
}
}
mutex_unlock(&dev->lock);
/*释放缓冲区内存*/
kfree(buffer);
// devm_kfree(&dev->client->dev,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("at24c02 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("at24c02 probe!\n");
/*获取设备节点*/
at24cxx_dev.node = of_find_node_by_path("/i2c@ff140000/at24c02@50");
if(at24cxx_dev.node == NULL)
{
printk("find node by path fialed!\r\n");
return -1;
}
/*读取AT24C02设备节点的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("at24c02 remove!\n");
/*删除at24c02类*/
cdev_del(&at24cxx_dev.cdev);
/*释放at24c02设备号*/
unregister_chrdev_region(at24cxx_dev.dev_id, 1);
/*注销at24c02设备*/
device_destroy(at24cxx_dev.class, at24cxx_dev.dev_id);
/*注销at24c02类*/
class_destroy(at24cxx_dev.class);
return 0;
}
static const struct of_device_id at24cxx_of_match[] = {
{.compatible = "atmel,24c02"},
{},
};
static const struct i2c_device_id at24c02_id[] = {
{ "xxxx", 0 },
{},
};
static struct i2c_driver at24cxx_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "atmel,24c02",
.of_match_table = at24cxx_of_match,
},
.probe = at24cxx_probe,
.remove = at24cxx_remove,
.id_table = at24c02_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_i2c_driver(at24cxx_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("AT24C02 driver");
MODULE_AUTHOR("xzx2020");
虽然我们使用的是设备树方式匹配,也就是通过at24cxx_driver结构体的driver成员的of_match_table表匹配。但是at24cxx_driver结构体的id_table成员不能为空,否则无法匹配。(暂时不知什么原因)
测试程序:
#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>
#define buf_size 20
void print_array(const char *title,char *buf,int count)
{
int i = 0;
printf(title);
for(i=0;i<count;i++)
{
printf(" %d",buf[i]);
}
printf("\n");
}
int main(int argc, char *argv[])
{
int fd;
int i;
int ret;
int count = 20;
char buf[buf_size]={0};
char offset = 0;
/*判断传入的参数是否合法*/
if(argc != 2)
{
printf("Usage:error\n");
return -1;
}
/*解析传入的参数*/
offset =atoi(argv[1]);
printf("offset = %d\n",offset);
/*打开设备文件*/
fd = open("/dev/rk3288_i2c",O_RDWR);
if(fd < 0)
{
printf("open rk3288_i2c fail fd=%d\n",fd);
close(fd);
return fd;
}
/*缓存数组赋值*/
for(i=0;i<buf_size;i++)
{
buf[i] = i;
}
/*写入数据*/
lseek(fd,offset,SEEK_SET);
ret = write(fd,buf,buf_size);
if(ret < 0)
{
printf("write to at24c02 error\n");
close(fd);
return ret;
}
/*打印数据*/
print_array("write to at24c02:",buf,count);
/*清空缓冲区*/
memset(buf,0,buf_size);
/*读取数据*/
ret = lseek(fd,offset,SEEK_SET);
printf("lseek = %d\n",ret);
ret = read(fd,buf,count);
if(ret < 0)
{
printf("read from at24c02 error\n");
close(fd);
return ret;
}
/*打印数据*/
print_array("read from at24c02:",buf,count);
close(fd);
return 0;
}