前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >firefly-rk3288开发板Linux驱动——AT24C02 E2PROM驱动

firefly-rk3288开发板Linux驱动——AT24C02 E2PROM驱动

作者头像
知否知否应是绿肥红瘦
发布2025-02-19 21:31:55
发布2025-02-19 21:31:55
7100
代码可运行
举报
文章被收录于专栏:Linux知识Linux知识
运行总次数:0
代码可运行

一、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通信函数、探测设备、检测设备地址函数等。

代码语言:javascript
代码运行次数:0
复制
//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
代码语言:javascript
代码运行次数:0
复制
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
代码语言:javascript
代码运行次数:0
复制
//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
};

3.I2C设备驱动

I2C设备驱动就是真实物理设备的驱动程序,这些设备通常都是挂在I2C适配器下面的,用户程序不能直接访问设备,必须通过I2C适配器来访问,而访问用到的接口函数都是由I2C核心层提供。

I2C设备驱动有两个重要的数据结构:i2c_client和i2c_driver。

代码语言:javascript
代码运行次数:0
复制
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。

代码语言:javascript
代码运行次数:0
复制
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总线定义如下:

代码语言:javascript
代码运行次数:0
复制
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 这个函数,此函数内容如下::

代码语言:javascript
代码运行次数:0
复制
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消息)为单位。

代码语言:javascript
代码运行次数:0
复制
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状态)

代码语言:javascript
代码运行次数:0
复制
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函数:

代码语言:javascript
代码运行次数:0
复制
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;
}
代码语言:javascript
代码运行次数:0
复制
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的具体硬件相关,需要由工程师根据芯片的数据手册来实现。

代码语言:javascript
代码运行次数:0
复制
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中增加如下节点信息:

代码语言:javascript
代码运行次数:0
复制
&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.驱动编写
代码语言:javascript
代码运行次数:0
复制
#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成员不能为空,否则无法匹配。(暂时不知什么原因)

测试程序:

代码语言:javascript
代码运行次数:0
复制
#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;   
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、Linux I2C设备体系
    • 1.I2C核心
    • 2.I2C总线驱动
      • a.I2C适配器数据结构i2c_adapter
      • b.I2C适配器Algorithm数据结构i2c_algorithm
    • 3.I2C设备驱动
    • 4.I2C设备和驱动匹配过程
  • 二、Linux I2C 4个重要数据结构之间的联系
    • 1.i2c_adapter和i2c_algorithm
    • 2.i2c_driver和i2c_client
    • 3.i2c_adapter和i2c_client
  • 三、I2C适配器驱动
  • 四、Linux I2C设备驱动
    • 1.Linux内核自带AT24xx EEPROM的设备驱动
    • 2.自定义AT24xx EEPROM的设备驱动
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档