一、Linux I2C设备体系

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个部分:

1.I2C核心

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)

2.I2C总线驱动

I2C总线驱动就是SOC中I2C控制器(在Linux中称为I2C适配器)驱动,其主要包括下面几个部分:

a.I2C适配器数据结构i2c_adapter

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;
};

b.I2C适配器Algorithm数据结构i2c_algorithm

//kernel/include/linux/i2c.h
struct i2c_algorithm {/* If an adapter algorithm can't do I2C-level access, set master_xferto NULL. If an adapter algorithm can do SMBus access, setsmbus_xfer. If set to NULL, the SMBus protocol is simulatedusing common I2C messages *//* master_xfer should return the number of messages successfullyprocessed, 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
};

3.I2C设备驱动

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设备驱动重点要处理的内容。

4.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设备和驱动匹配有三种方式:

  • of_driver_match_device 函数用于完成设备树设备和驱动匹配。比较 I2C 设备节点的 compatible 属性和 of_device_id 中的 compatible 属性是否相等,如果相当的话就表示 I2C设备和驱动匹配。

  • acpi_driver_match_device 函数用于 ACPI 形式的匹配

  • i2c_match_id 函数用于传统的、无设备树的 I2C 设备和驱动匹配过程。比较 I2C设备名字和 i2c_device_id 的 name 字段是否相等,相等的话就说明 I2C 设备和驱动匹配。

二、Linux I2C 4个重要数据结构之间的联系

i2c4个重要的数据结构就是上文提到的:i2c_adapter、i2c_algorithm、i2c_driver和i2c_client。

1.i2c_adapter和i2c_algorithm

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通信的地址,方向,缓存区等信息。

2.i2c_driver和i2c_client

i2c_driver是i2c设备驱动,i2c_client是真实的设备,每个设备都需要一个i2c_client描述。当I2C设备和驱动匹配后,驱动中的probe函数就会被执行。每个i2c_driver驱动可以对应多个i2c_client,也就是同一个驱动程序,可以驱动多个同类型的物理设备。

3.i2c_adapter和i2c_client

i2c_client是挂在i2c_adapter下面的,因为物理设备肯定是直接接在主机I2C接口上的。一个适配器可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adapter中包括依附于他的i2c_client的链表。

三、I2C适配器驱动

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);

四、Linux I2C设备驱动

1.Linux内核自带AT24xx EEPROM的设备驱动

源代码位置:kernel/drivers/misc/eeprom/at24.c

2.自定义AT24xx EEPROM的设备驱动

at24c02的驱动为例:

a.修改设备树

一般情况下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是从机地址 )

b.驱动编写

#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 20void 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;
}

firefly-rk3288开发板Linux驱动——AT24C02 E2PROM驱动相关推荐

  1. RK3288开发板PopMetal上的GPIO驱动实例

    2019独角兽企业重金招聘Python工程师标准>>> 楼主在这边给大家介绍下如何使用PopMetal的GPIO.先讲过程,再讲原理吧, 该驱动需要涉及到的知识点:1,DTS设备树的 ...

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

    firefly-rk3288j开发板–linux I2C实验之eeprom驱动 1 准备工作 开发板:aio-rk3288j SDK版本:rk3288_linux_release_20210304 下 ...

  3. firefly-rk3288开发板Linux驱动——LED驱动

    本文主要介绍三个部分的内容:一.准备SDK源码 二.如何操作GPIO 三.LED设备驱动的实现.由于firefly官方一直在对源码进行更新,所以本文只以我正在用的版本介绍.此外,官方提供的下载工具版本 ...

  4. firefly-rk3288j开发板--linux NFC实验之RC522驱动

    firefly-rk3288j开发板–linux NFC实验之RC522驱动 1 准备工作 开发板:aio-rk3288j SDK版本:rk3288_linux_release_20210304 下载 ...

  5. 讯为4412开发板Linux驱动学习笔记

    驱动理论专题一 Linux驱动程序的基本认识 有了内存管理单元,就有虚拟地址,物理地址. 驱动理论专题二 学会查看原理图 以LED2为示例 通过原理图查看到KP_COL0,赋予高电平则能点亮LED2, ...

  6. 【迅为iMX6Q】开发板 Linux 5.15.71 RTL8211E 以太网驱动适配

    相关参考 [迅为iMX6Q]开发板 u-boot 2022.04 SD卡 启动 [迅为iMX6Q]开发板 u-boot 2020.04 RTL8211E 以太网驱动适配 [迅为iMX6Q]开发板 Li ...

  7. OK6410开发板linux系统下的SPI驱动和测试

    OK6410下的SPI驱动是可以用的,但是飞凌把它作为其它用途了,我们修改一些代码才能在/dev目录下创建SPI的设备节点文件 Step1:打开arch/arm/mach_s3c64XX/mach_m ...

  8. NUC980开发板Linux系统EC20模块 移植 串口 PPP拨号

    NUC980开发板Linux系统EC20模块 移植 串口 PPP拨号 1. EC20模块连接 2. Linux内核配置 3. 交叉编译PPP 4. 拨号脚本 5. 进行拨号 1. EC20模块连接 在 ...

  9. 迅为IMX6ULL开发板Linux学习教程

    1800+页使用手册(持续更新)+入门视频教程+实战视频教程 关注VX公众号:迅为电子 ,  回复 :终结者,免费获取产品资料  让教程更细致,终结入门难! 所有教程由迅为原创,是迅为工作多年的工程师 ...

最新文章

  1. iOS应用代码注入防护
  2. 【drp 12】再识转发和重定向:SpringMVC无法跳转页面
  3. 引用js_js值和引用
  4. 打开指定大小的新窗体
  5. linux 让程序在后台执行
  6. 远程接入CBTS的应用
  7. ABB机器人Whlie循环指令
  8. g120xa正反转参数_原创分享 | 西门子G120变频器的参数备份
  9. CSS提高图片清晰度
  10. 王文京:纵横30年,阵阵桂花香
  11. 智联招聘 'python数据分析'职位分析第一篇
  12. c语言设置字体时调用对话框,第1课用C语言函数编写对话框之一直接实践
  13. 谷歌浏览器无法正常使用怎么办?Google浏览器无法正常使用的解决方法
  14. 手机照片局部放大镜_iphone手机这5个功能十分出色,满满的科技感,别再白白浪费...
  15. 去千户苗寨为什么说不要为了节约钱住普通客栈,而要住商务酒店
  16. H.264 中 SAD SATD及常见知识点
  17. Mysql数据库部分简单整理
  18. 关于使用ADS编译很卡的问题
  19. 虚拟化平台PVE(ProxmoxVirtual Environment)安装部署
  20. laravel框架 APP 支付宝支付

热门文章

  1. deepin 系统 微信登录提示版本过低解决方法
  2. 无线路由器常用的五种工作模式详细介绍
  3. Vue2.0源码解析 - 知其然知其所以然之Vue.use
  4. echart x轴自定义间距
  5. _WIN32_WINNT not defined
  6. 教你一键开发桌面应用程序
  7. Python开发Windows桌面应用程序(一)PyCharm+PyQt5开发环境搭建
  8. 苹果手机的高德地图的位置服务器,苹果手机高德地图家人地图在哪
  9. Python中计算两个数据点之间的欧式距离,一个点到数据集中其他点的距离之和
  10. rsa私钥 txt转化为pem格式