二十三、Linux驱动之IIC驱动(基于linux4.12内核)

1. 基本概念

本文默认读者掌握裸机下的I2C操作,该部分只做简单介绍, 主要内容是对linux-4.12系统下I2C驱动的分析。(上一篇二十一、Linux驱动之IIC驱动(基于linux2.6.22.6内核)对linux-2.6.22.6内核的I2C进行了分析,新内核的I2C有了很大的变化,但是也有部分类似,为了保证完整性,我会全部从头分析。linux-4.12的移植和对应之前驱动的移植以后会进行更新)

    I2C是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据,是一个多主机的半双工通信方式,每个挂接在总线上的器件都有个唯一的地址。位速在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可待3.4Mbit/s
    I2C总线仅仅使用SCL、 SDA这两根信号线就实现了设备之间的数据交互, 极大地简化了对硬件资源和PCB板布线空间的占用。 因此, I2C总线非常广泛地应用在EEPROM、 实时钟、 小型LCD等设备与CPU的接口中。

2. I2C结构

2.1 文件结构

    在linux-4.12内核的driver/i2c目录下内有如下文件:
  二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第1张图片

2.1.1 algos文件夹

    里面保存I2C的通信方面的算法(algorithms)

2.1.2 busses文件夹

    里面保存I2C总线驱动相关的文件,比如i2c-omap.c、 i2c-versatile.c、 i2c-s3c2410.c等。

2.1.3 muxes文件夹

    里面保存I2C切换芯片相关驱动,本节不使用。

2.1.4 i2c-core.c文件

    完成I2C总线、设备、驱动模型,对用户提供sys文件系统访问支持;为I2C内部adpter等提供注册接口。

2.1.5 i2c-dev.c文件

    实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配为一个设备。通过适配器访问设备是的主设备号都为89,次设备号为0~255。应用程序通过“i2c-%d”(i2c-0、i2c-1、i2c-2...)文件名并使用文件操作结构open()、write()、read()、ioctl()close()等来访问这个设备。
    i2c-dev.c并没有针对特定的设备而设计,只是提供了通用read()等接口,应用程序可以借用这些接口访问适配器上的I2C设别的存储空间或寄存器,并控制I2C设别的工作方式。

2.2 I2C体系架构

    I2C体系架构图如下:
   二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第2张图片
    主要分为3个部分,I2C核心、I2C总线驱动、I2C设备驱动

2.2.1 I2C核心

    是Linux内核用来维护和管理的I2C的核心部分,其中维护了两个静态的List,分别记录系统中的I2C driver结构和I2C adapter结构。I2C core提供接口函数,允许一个I2C adatper、I2C driverI2C client初始化时在I2C core中进行注册,以及退出时进行注销等操作函数。同时还提供了I2C总线读写访问的一般接口。

2.2.2 I2C总线驱动

    I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapterI2C适配器的算法结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。

2.2.3 I2C设备驱动

    I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含了数据结构i2c_driveri2c_client,我们需要根据具体设备实现其中的成员函数,可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。

2.2.4 各结构之间的关系

1. i2c_adapter与i2c_algorithm

    i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithmi2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。

2. i2c_driver和i2c_client

    i2c_driver对应一套驱动方法,i2c_client对应真实的i2c物理设备device,每个i2c设备都需要一个i2c_client来描述i2c_driveri2c_client的关系是一对多。一个i2c_driver上可以支持多个同等类型的i2c_client

3. i2c_adapter和i2c_client

    i2c_adapteri2c_client的关系与i2c硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个i2c设备,所以i2c_adapter中包含依附于它的i2c_client的链表。

2.2.5 为什么构建这么复杂的I2C体系架构?

    在单片机中使用I2C只需要简单地配置几个I2C寄存器,构建包含开始位、停止位、读写周期、ACK等简单概念函数即可,为什么在Linux上需要构建这么复杂的I2C体系架构呢?为了通用性及为了符合Linux内核驱动模式。
    对于linux系统来说,支持各式各样的CPU类型,并且还想要满足各种I2C芯片硬件特性,就必须将一些稳定性好的部分抽离出来,这样就抽象出了i2c_client(描述具体的I2C设备)i2c_driver(描述操作I2C设备方法的接口)i2c_adpter(描述CPU如何控制I2C总线)、i2c_algorithm(I2C通信算法)等。前两个就是抽象出的I2C设备驱动,后两个为I2C总线驱动。
    在一个SoC上可能有多条I2C总线,一条总线对应一个I2C总线驱动(I2C适配器),每一条总线上又可以接多个I2C设备。假如一个SoC上有3个I2C适配器,外接3个I2C设备,想要3个适配器分别都能驱动3个I2C设备,我们只需写3个适配器代码,3个I2C设备代码即可。

3. 分析内核(linux-4.12)

    下面开始深入内核分析I2C驱动。

3.1 重要数据结构

    内核的I2C驱动围绕着以下几个数据结构进行盘根交错的展开。

3.1.1 i2c_adapter(I2C适配器结构)

    i2c_adapter是用来描述一个I2C适配器,在SoC中的指的就是内部外设I2C控制器,当向I2C核心层注册一个I2C适配器时就需要提供这样的一个结构体变量。

struct i2c_adapter {
	struct module *owner;    //所有者

        /*
        class表示该I2C bus支持哪些类型的I2C从设备,只有匹配的I2C从设备才能和适配器绑定。
        具体的类型包括:
        1. I2C_CLASS_HWMON,硬件监控类,如lm_sensors等;
        2. I2C_CLASS_DDC,DDC是数字显示通道(Digital Display Channel)的意思, 通常用于显示设备信息的获取;
        3. I2C_CLASS_SPD,存储类的模组;
        4. I2C_CLASS_DEPRECATED,不支持自动探测。
        */
	unsigned int class; 
	const struct i2c_algorithm *algo;    //该适配器与从设备的通信算法
	void *algo_data;

	/* data fields that are valid for all devices	*/
	const struct i2c_lock_operations *lock_ops;
	struct rt_mutex bus_lock;
	struct rt_mutex mux_lock;

	int timeout;    //超时时间
	int retries;    //重试次数
	struct device dev;    //该适配器设备对应的device

	int nr;    //该适配器的ID,会体现在sysfs中(/sys/bus/i2c/devices/i2c-n中的‘n’)
	char name[48];    //适配器的名字
	struct completion dev_released;    //用来挂接与适配器匹配成功的从设备i2c_client的一个链表头

	struct mutex userspace_clients_lock;    
	struct list_head userspace_clients;    //clients链表

	struct i2c_bus_recovery_info *bus_recovery_info;
	const struct i2c_adapter_quirks *quirks;

	struct irq_domain *host_notify_domain;
};

3.1.2 i2c_algorithm(I2C算法结构)

    i2c_algorithm结构体代表的是适配器的通信算法,在构建i2c_adapter结构体变量的时候会去填充这个元素。

struct i2c_algorithm {
        /*
        I2C协议有关的数据传输接口,输入参数是struct i2c_msg类型(可参考2.3小节的介绍)
        的数组(大小由num指定)。返回值是成功传输的msg的个数,如有错误返回负值。
        */
	int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
			   int num);

        /*SMBUS有关的数据传输接口,如果为NULL,I2C core会尝试使用master_xfer模拟。*/
	int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
			   unsigned short flags, char read_write,
			   u8 command, int size, union i2c_smbus_data *data);

	/* 
        该I2C adapter支持的功能:
        1. I2C_FUNC_I2C,支持传统的I2C功能;
        2. I2C_FUNC_10BIT_ADDR,支持10bit地址;
        3. I2C_FUNC_PROTOCOL_MANGLING,支持非标准的协议行为(具体请参考2.3小节的介绍);
        4. I2C_FUNC_NOSTART,支持不需要发送START信号的I2C传输(具体请参考2.3小节的介绍);
        5. I2C_FUNC_SMBUS_xxx,SMBUS相关的功能,不再详细介绍。
        */
	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.1.3 i2c_client(I2C次设备结构)

    i2c_client描述一个I2C次设备。

struct i2c_client {
	unsigned short flags;		// 标志
	unsigned short addr;		// 该I2C设备的7位设备地址

	char name[I2C_NAME_SIZE];       // 设备名字
	struct i2c_adapter *adapter;	// 该I2C设备支持哪个适配器
	struct device dev;		// 设备结构体
	int irq;			//设备所使用的结构体
	struct list_head detected;      //链表头
#if IS_ENABLED(CONFIG_I2C_SLAVE)
	i2c_slave_cb_t slave_cb;	/* callback for slave mode	*/
#endif
};

3.1.4 i2c_driver(I2C设备驱动结构)

    i2c_driver描述一个I2C设备驱动。

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;    //匹配适配器函数指针,不使用

	/* 标准驱动模型接口 */
	int (*probe)(struct i2c_client *, const struct i2c_device_id *);
	int (*remove)(struct i2c_client *);

	/* 新驱动模型接口 */
	int (*probe_new)(struct i2c_client *);

	/* driver model interfaces that don't relate to enumeration  */
	void (*shutdown)(struct i2c_client *);

        /* 闹钟回调函数 */
	void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,
		      unsigned int data);

        /* 类似于ioctl的命令接口函数 */
	int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

	struct device_driver driver;    //该i2c设备驱动所对应的device_driver
	const struct i2c_device_id *id_table;    //存放该i2c设备驱动id名,用于与i2c_client->name比较匹配

	/* 检测回调函数 */
	int (*detect)(struct i2c_client *, struct i2c_board_info *);
	const unsigned short *address_list;
	struct list_head clients;

	bool disable_i2c_core_irq_mapping;
};

3.1.5 i2c_msg(I2C数据包)

    i2c_msg描述一个I2C数据包。

struct i2c_msg {
	__u16 addr;	//I2C从机的设备地址
	__u16 flags;    //当flags=0表示写, flags= I2C_M_RD表示读
#define I2C_M_RD		0x0001	/* read data, from slave to master */
					/* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN		0x0010	/* this is a ten bit chip address */
#define I2C_M_RECV_LEN		0x0400	/* length will be first received byte */
#define I2C_M_NO_RD_ACK		0x0800	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK	0x1000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR	0x2000	/* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART		0x4000	/* if I2C_FUNC_NOSTART */
#define I2C_M_STOP		0x8000	/* if I2C_FUNC_PROTOCOL_MANGLING */
	__u16 len;	//传输的数据长度,等于buf数组里的字节数
	__u8 *buf;	//存放数据的数组
};

3.2 分析I2C总线驱动

    内核是最好的学习工具。想要写一个linux驱动,最好的方法就是分析内核自带的驱动程序。对于I2C总线驱动我们可以分析内核源码drivers/i2c/busses/i2c-s3c2410.c文件。

3.2.1 入口函数

static const struct platform_device_id s3c24xx_driver_ids[] = {
	{
		.name		= "s3c2410-i2c",
		.driver_data	= 0,
	}, {
		.name		= "s3c2440-i2c",
		.driver_data	= QUIRK_S3C2440,
	}, {
		.name		= "s3c2440-hdmiphy-i2c",
		.driver_data	= QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
	}, { },
};

... ...

static struct platform_driver s3c24xx_i2c_driver = {
	.probe		= s3c24xx_i2c_probe,
	.remove		= s3c24xx_i2c_remove,
	.id_table	= s3c24xx_driver_ids,
	.driver		= {
		.name	= "s3c-i2c",
		.pm	= S3C24XX_DEV_PM_OPS,
		.of_match_table = of_match_ptr(s3c24xx_i2c_match),
	},
};

static int __init i2c_adap_s3c_init(void)
{
	return platform_driver_register(&s3c24xx_i2c_driver);    //以platform模型注册platform_driver
}

    首先在platform总线上注册一个platform_driver结构platform总线上有与id_table->name相同(“s3c2440-i2c”)platform_device被注册或已经注册时调用s3c2440_i2c_driver->probe函数。也就是s3c24xx_i2c_probe()函数。

3.2.2 s3c24xx_i2c_probe()函数

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
        struct s3c24xx_i2c *i2c;
	struct s3c2410_platform_i2c *pdata = NULL;   
        ... ...

        pdata = dev_get_platdata(&pdev->dev);    //得到资源
        ... ...
        
        /* 分配相关结构体内存 */
        i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
	if (!i2c)
		return -ENOMEM;

	i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
	if (!i2c->pdata)
		return -ENOMEM;
        ... ...
        
        /* 填充适配器i2c_adapter结构体 */
        strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));    //填充适配器名字
	i2c->adap.owner = THIS_MODULE;    //所有者    
	i2c->adap.algo = &s3c24xx_i2c_algorithm;    //填充算法
	i2c->adap.retries = 2;    //重试次数
	i2c->adap.class = I2C_CLASS_DEPRECATED;    //支持的设备类型,I2C_CLASS_DEPRECATED表示不支持自动检测
	i2c->tx_setup = 50;

	init_waitqueue_head(&i2c->wait);        //初始化等待队列头

        ...
        i2c->clk = devm_clk_get(&pdev->dev, "i2c");    //获得时钟

        /* 获得资源并映射 */
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    
        i2c->regs = devm_ioremap_resource(&pdev->dev, res);

        /*设置i2c_adapter适配器结构体, 将i2c结构体设为adap的私有数据成员*/
        i2c->adap.algo_data = i2c;
	i2c->adap.dev.parent = &pdev->dev;

        /*初始化I2C控制器*/
        ret = clk_prepare_enable(i2c->clk);    //使能时钟
	if (ret) {
		dev_err(&pdev->dev, "I2C clock enable failed\n");
		return ret;
	}

	ret = s3c24xx_i2c_init(i2c);    //初始化2440相关寄存器
	clk_disable(i2c->clk);    //使能时钟

        /*申请中断*/
        ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,0, dev_name(&pdev->dev), i2c);

        ... ...
        /*设置i2c_adapter适配器*/
        i2c->adap.nr = i2c->pdata->bus_num;    //设置适配器ID,如果私有数据没有设置默认为0
	i2c->adap.dev.of_node = pdev->dev.of_node;

        ... ...
        /*注册i2c_adapter适配器*/
        ret = i2c_add_numbered_adapter(&i2c->adap);
        ... ...
}

    该函数主要作用如下:
      1. 分配设置i2c_adapter适配器相关结构
      2. 硬件相关设置
      3. 设置等待队列
      4. 申请中断
      5. 注册i2c_adapter适配器

    最后调用i2c_add_numbered_adapter注册i2c_adapter适配器。

3.2.3 注册i2c_adapter适配器

    看看该函数是如何实现的:

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
	if (adap->nr == -1) /* adap->nr=0,不满足条件 */
		return i2c_add_adapter(adap);

	return __i2c_add_numbered_adapter(adap);
}

    注册i2c_adapter适配器有两个函数,i2c_add_adapter会自动分配adapter IDi2c_add_numbered_adapter则可以指定ID。
   
在内核启动时调用(mach-smdk2440.c里)smdk2440_machine_init()函数,里面又调用s3c_i2c0_set_platdata()函数。函数代码如下:

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
	struct s3c2410_platform_i2c *npd;

	if (!pd) {
		pd = &default_i2c_data;
		pd->bus_num = 0;
	}

	npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
			       &s3c_device_i2c0);

	if (!npd->cfg_gpio)
		npd->cfg_gpio = s3c_i2c0_cfg_gpio;
}

    在s3c_i2c0_set_platdata中设置pd->bus_num = 0而在s3c24xx_i2c_probe()函数中有如下代码:
   
    可得adap->nr=0,所以i2c_add_numbered_adapter()函数实际调用的是__i2c_add_numbered_adapter函数(i2c_add_adapter自动分配ID后也是调用这个函数)__i2c_add_numbered_adapter函数又调用了i2c_register_adapter()函数,函数如下:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
        ... ...
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);    //设置适配器名字
	adap->dev.bus = &i2c_bus_type;    //设置适配器成员dev的总线为i2c_bus_type
	adap->dev.type = &i2c_adapter_type;    //设置适配器成员dev的类型为适配器类型
	res = device_register(&adap->dev);    //注册适配器成员dev设备

        ... ...
        
        /* 对dts上所描述的i2c_client设备进行实例化,并创建相应的sys文件:sys/bus/i2c/devices/xxx */
        of_i2c_register_devices(adap);
	i2c_acpi_register_devices(adap);
	i2c_acpi_install_space_handler(adap);

        /* 内核启动时(注册适配器之前),如果调用i2c_register_board_info()函数注册
        过i2c_board_info结构(描述i2c_client的结构),就调用i2c_scan_static_board_info
        对这些i2c_board_info结构里的i2c_client进行实例化 */
        if (adap->nr < __i2c_first_dynamic_bus_num)
	i2c_scan_static_board_info(adap);
        
        ... ...
        /* 遍历i2c_bus_type匹配i2c_driver与i2c_adapter */
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
        ... ...
}

    该函数主要作用如下3大点:

3.2.3.1 设置注册适配器i2c_adapter

    1. 设置适配器名字
    2. 设置适配器成员dev的总线为i2c_bus_type
    3. 设置适配器成员dev的类型为适配器类型
    4. 注册设备(适配器成员dev,以后可以通过这个dev找到该适配器)

3.2.3.2 实例化i2c_client设备(该部分在I2C设备驱动部分详细讲解)

    1. 如果使用了设备树,对dts上所描述的i2c_client设备进行实例化,并创建相应的sys文件:sys/bus/i2c/devices/xxx。
    2.
内核启动时(注册适配器之前),如果调用i2c_register_board_info()函数注册过i2c_board_info结构(描述i2c_client的结构),就调用i2c_scan_static_board_info()函数对这些i2c_board_info结构里的i2c_client进行实例化。

3.2.3.3 遍历i2c_bus_type匹配i2c_driver与i2c_adapter

    匹配过程实际是使用__process_new_adapter()函数来匹配i2c_bus_type上的每一个i2c_drivers,该函数最终又是调用i2c_detect()函数:

static int __process_new_adapter(struct device_driver *d, void *data)
{
	return i2c_do_add_adapter(to_i2c_driver(d), data);
}
... ...
static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap)
{
	i2c_detect(adap, driver);
        ... ...
}

    i2c_detect()函数部分代码如下:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);    //得到适配器ID = adap->nr

	address_list = driver->address_list;    //从i2c_driver得到I2C设备地址列表
        ... ...

	/* 如果适配器dapter->class定义为不自动检测类型,函数返回 */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
                ... ...
		return 0;
	}

	/* 如果i2c_driver支持的设备类型与适配器支持的类型不一样,函数返回 */
	if (!(adapter->class & driver->class))
		return 0;

	/* 分配设置i2c_client */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

        /* 取出每一个address_list里的设备地址进行检测 */
	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev,
			"found normal entry for adapter %d, addr 0x%02x\n",
			adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		err = i2c_detect_address(temp_client, driver);    //检测该设备地址
		if (unlikely(err))
			break;
	}

	kfree(temp_client);    //释放i2c_client 
	return err;
}

    该函数主要作用如下:
      1. i2c_driver得到I2C设备地址列表address_list
      2. 判断适配器i2c_adapteri2c_driver支持的设备类型class
      3. 取出每一个address_list里的设备地址进行检测
(调用i2c_detect_address()函数)

    分析作用2:在前面s3c24xx_i2c_probe()函数中定义了适配器i2c_adapterI2C_CLASS_DEPRECATED不自动检测类型,如下图:
    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第3张图片

    所以对于i2c-s3c2410.c这个内核(这里只针对linux-4.12,比如linux-3.4.2 这里的class设置的是I2C_CLASS_HWMON | I2C_CLASS_SPD)提供的总线驱动来说,在 i2c_detect()函数中什么也没做直接返回了。但是对于driver/i2c/busses内核文件内的其他总线驱动,i2c_adapter适配器的class成员设置的是支持自动检测类型(如设为I2C_CLASS_HWMON),如果同时满足adapter->classdriver->class类型相同,就可以继续往下执行检测设备地址。
    分析作用3:循环取出i2c_driver->address_list里存放的设备地址进行检测,即调用i2c_detect_address()函数:

static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
	struct i2c_board_info info;
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* 确保7位I2C设备地址在0x08~0x77之间 */
	err = i2c_check_7bit_addr_validity_strict(addr);
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}
        ... ...

	/* 确保总线上真实挂接有该设备地址的I2C芯片,通过i2c_smbus_xfer发送设备地址,
        如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回 */
	if (!i2c_default_probe(adapter, addr))
		return 0;

	/* 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片,如通过读写芯片特定的寄存器 */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);

        ... ...

        /*创建设备*/
	client = i2c_new_device(adapter, &info);
        ... ...
	return 0;
}

    该函数主要作用如下:
      1. 确保传入的7位I2C设备地址在0x08~0x77之间(I2C芯片通常是7位设备地址,范围在0x08~0x77之间)
      2. 确保总线上真实挂接有该设备地址的I2C芯片(在i2c_default_probe()函数中通过i2c_smbus_xfer发送设备地址,如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回)
      3. 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片(如通过读写I2C芯片特定的寄存器)
      4. 调用i2c_new_device创建设备(创建I2C设备驱动的i2c_client)
    下面先来看看i2c_smbus_xfer()函数是如何检测设备地址是否真实存在的。

3.2.4 i2c_smbus_xfer()函数

    函数部分代码如下:

s32 i2c_smbus_xfer(struct i2c_adapter *adapter, u16 addr, unsigned short flags,
		   char read_write, u8 command, int protocol,
		   union i2c_smbus_data *data)
{
        ... ...  

	flags &= I2C_M_TEN | I2C_CLIENT_PEC | I2C_CLIENT_SCCB;

	if (adapter->algo->smbus_xfer) {    //如果适配器指定了algo->smbus_xfer成员
        ... ...
	}

	res = i2c_smbus_xfer_emulated(adapter, addr, flags, read_write,
				      command, protocol, data);    //使用smbus协议发送I2C消息
        ... ...
}

    该函数首先判断i2c_adapter适配器成员algo->smbus_xfer函数,可以回顾一下i2c-s3c2410.c中的i2c_adapter适配器是如何定义的,s3c24xx_i2c_probe()函数中有如下代码:
    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第4张图片

    s3c24xx_i2c_algorithm定义如下:

    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第5张图片

    可见适配器并没有algo->smbus_xfer函数,所以i2c_smbus_xfer()函数实际是使用i2c_smbus_xfer_emulated()函数进行发送数据的,i2c_smbus_xfer_emulated()函数部分代码如下:

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter *adapter, u16 addr,
				   unsigned short flags,
				   char read_write, u8 command, int size,
				   union i2c_smbus_data *data)
{
	unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];    //属于 msg[0]的buf成员
	unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];    //属于 msg[1]的buf成员
	int num = read_write == I2C_SMBUS_READ ? 2 : 1;    //如果为读命令,就等于2,表示要执行两次数据传输
	int i;
	u8 partial_pec = 0;
	int status;
	struct i2c_msg msg[2] = {
		{
			.addr = addr,
			.flags = flags,
			.len = 1,
			.buf = msgbuf0,
		}, {
			.addr = addr,
			.flags = flags | I2C_M_RD,
			.len = 0,
			.buf = msgbuf1,
		},
	};

	msgbuf0[0] = command;    //IIC设备地址最低位为读写命令

        switch (size) {
        ... ...

        /*读操作需要两个i2c_msg,写操作需要一个i2c_msg*/
	case I2C_SMBUS_BYTE_DATA:
		if (read_write == I2C_SMBUS_READ)    //如果是读字节
			msg[1].len = 1;
		else {
			msg[0].len = 2;
			msgbuf0[1] = data->byte;
		}
		break;
        ... ...

        status = i2c_transfer(adapter, msg, num);    //将i2c_msg结构体的内容发送给I2C设备
    	if (status < 0)    //返回值不等于0表示没有收到ACK
		return status;
	}

    可见该函数是通过构造i2c_msg结构体通过i2c_transfer()函数发送给I2C设备,如果I2C设备返回ACK表示设备存在。i2c_transfer()函数实际又调用了__i2c_transfer()函数,部分代码如下:

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
        ... ...

        orig_jiffies = jiffies;
	for (ret = 0, try = 0; try <= adap->retries; try++) {
		ret = adap->algo->master_xfer(adap, msgs, num);
		if (ret != -EAGAIN)
			break;
		if (time_after(jiffies, orig_jiffies + adap->timeout))
			break;
	}
        ... ...
}

    最终是调用到了I2C总线驱动里注册的adapter适配器的算法函数master_xfer发送i2c_msg。master_xfer()函数调用过程:
      1. 传输数据时,调用s3c24xx_i2c_algorithm结构体中的数据传输函数s3c24xx_i2c_xfer()。
     
2. s3c24xx_i2c_xfer()中会调用s3c24xx_i2c_doxfer()进行数据的传输。
     
3. s3c24xx_i2c_doxfer()中向总线发送IIC设备地址和开始信号S后,便会调用wati_event_timeout()函数进入等待状态。
     
4. 将数据准备好发送时,将产生中断,并调用实现注册的中断处理函数s3c24xx_i2c_irq()。
     
5. s3c24xx_i2c_irq()调用下一个字节传输函数i2c_s3c_irq_nextbyte()来传输数据。
     
6. 当数据传输完成后,会调用 s3c24xx_i2c_stop()。
     
7. 最后调用wake_up()唤醒等待队列,完成数据的传输过程。
    其中第5点调用i2s_s3c_irq_nextbyte()函数中有如下代码:

case STATE_START:
	if (iicstat & S3C2410_IICSTAT_LASTBIT &&
	    !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {
		
                /* 如果没有收到ACK */
		dev_dbg(i2c->dev, "ack was not received\n");
		s3c24xx_i2c_stop(i2c, -ENXIO);
		goto out_ack;
	}

    当使用适配器发送数据没有收到ACK会有如下调用:
        s3c24xx_i2c_stop(i2c, -ENXIO);
            s3c24xx_i2c_master_complete(i2c, ret);   
// ret=-ENXIO
                    if (ret)
                        i2c->msg_idx = ret;   
// i2c->msg_idx=-ENXIO
    i2c_smbus_xfer()又通过以下调用得到返回值-ENXIO
              i2c_smbus_xfer()
                    i2c_transfer()
                          s3c24xx_i2c_xfer()
                              s3c24xx_i2c_doxfer
                                  ret = i2c->msg_idx;   
// ret=-ENXIO
                                      return ret;
    回顾3.2.3.3中讲的i2c_detect_address()函数内容:
      1. 确保传入的7位I2C设备地址在0x08~0x77之间(I2C芯片通常是7位设备地址,范围在0x08~0x77之间)
      2. 确保总线上真实挂接有该设备地址的I2C芯片(在i2c_default_probe()函数中通过i2c_smbus_xfer发送设备地址,如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回)
      3. 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片(如通过读写I2C芯片特定的寄存器)
      4. 调用i2c_new_device创建设备(创建I2C设备驱动的i2c_client)
   
i2c_detect_address()函数中检验设备地址i2c_default_probe()函数使用如下:

static int i2c_default_probe(struct i2c_adapter *adap, unsigned short addr)
{
        int err;
        ... ...
        err = i2c_smbus_xfer(... ...);
        ... ...
        return err >= 0;
}
... ...
static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
        ... ...
        if (!i2c_default_probe(adapter, addr))
                return 0;
        
	/* 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片,如通过读写芯片特定的寄存器 */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);

        ... ...

        /*创建设备*/
	client = i2c_new_device(adapter, &info);
        ... ...
	return 0;
}

    可见,当使用i2c_smbus_xfer()函数(最终是调用到了I2C总线驱动里注册的adapter适配器的算法函数master_xfer)发送数据(设备地址)时:
    1. 如果没有收到I2C设备的ACK, i2c_smbus_xfer()返回-ENXIO,所以i2c_default_probe()函数返回0,i2c_detect_address()函数也就会返回0。
   
2. 如果收到ACK表示I2C总线上确实有该设备地址的芯片存在,就会调用i2c_driver->detect检测函数再次检测该设备地址的芯片具体是什么芯片(如通过读写I2C芯片特定的寄存器)。接着便调用i2c_new_device()创建设备(i2c_client),该函数是I2C设备驱动中很重要的组成部分,放到后面在仔细分析。

3.2.5 总结

    到目前为止,i2c-s3c2410.c文件为我们实现了对2440I2C控制器的设置、I2C通信协议相关代码等,组成了一个适配器i2c_adapter(I2C总线驱动),使用这个适配器内核就知道如何与挂在I2C总线上的I2C设备通信了下面讲解I2C设备驱动部分。

3.3 分析I2C设备驱动

    其实在以上讲解分析I2C总线驱动部分已经涉及到I2C设备驱动的使用了,实例化创建i2c_client、使用i2c_client、i2c_driver等。I2C总线驱动与I2C设备驱动相互盘根交错,并没有明显的界线。如果说I2C总线驱动的核心是向内核注册适配器(i2c_adapter),那么I2C设备驱动的核心就是实例化I2C设备(i2c_client)与注册设备驱动(i2c_driver)
   
Linux内核文档(/Documentation/i2c/instantiating-devices)中,介绍了实例化I2C设备(注册i2c_client)的4种方法,实际上这4种方法最终都是通过调用i2c_new_device()函数来实例化I2C设备的,所以先来仔细分析一下i2c_new_device()函数。

3.3.1 i2c_new_device()函数

    实例化I2C设备核心就是调用i2c_new_device()函数,该函数部分代码如下:

struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	struct i2c_client	*client;
	int			status;

	client = kzalloc(sizeof *client, GFP_KERNEL);    //分配i2c_client结构体内存
        ... ...

        /*设置i2c_client结构体*/
	client->adapter = adap;    //设置该从设备的适配器
        ... ...

	client->flags = info->flags;    //设置该从设备的标志
	client->addr = info->addr;    //设置该从设备的设备地址

	client->irq = info->irq;    //中断号
        ... ...

	strlcpy(client->name, info->type, sizeof(client->name));    //设置该从设备的名字
        ... ...

	client->dev.parent = &client->adapter->dev;    //设置设备父类为适配器的dev
	client->dev.bus = &i2c_bus_type;    //设置设备总线类型
	client->dev.type = &i2c_client_type;    //设置设备类型
	client->dev.of_node = info->of_node;    //设备节点
	client->dev.fwnode = info->fwnode;

	i2c_dev_set_name(adap, client);

        /* 向设备添加属性 */
	if (info->properties) {
		status = device_add_properties(&client->dev, info->properties);
		if (status) {
			dev_err(&adap->dev,
				"Failed to add properties to client %s: %d\n",
				client->name, status);
			goto out_err;
		}
	}

        /* 注册设备 */
	status = device_register(&client->dev);
        ... ...

	return client;
        ... ...
}

    该函数传入两个参数,struct i2c_adapter结构和struct i2c_board_info结构,前者即是I2C总线驱动中的适配器,后者就是一个I2C设备信息表,结构原型如下(include/linux/i2c.h)

struct i2c_board_info {
	char		type[I2C_NAME_SIZE];    //描述I2C设备类型名
	unsigned short	flags;    
	unsigned short	addr;    //描述I2C设备地址
	void		*platform_data;    //平台数据
	struct dev_archdata	*archdata;
	struct device_node *of_node;    //设备节点
	struct fwnode_handle *fwnode;
	const struct property_entry *properties;
	const struct resource *resources;
	unsigned int	num_resources;
	int		irq;
};

/*设置i2c_board_info结构体type与addr成员的宏 */
#define I2C_BOARD_INFO(dev_type, dev_addr) \
	.type = dev_type, .addr = (dev_addr)

    我们重点关心typeaddr成员,type描述一个I2C设备类型名,addr描述I2C设备地址。在i2c_new_device()函数中会将传入的i2c_board_info结构体成员拷贝给i2c_client结构,现在再来总结一下i2c_new_device()函数内容:
      1. 分配i2c_client结构体
      2. 设置i2c_client结构体
        2.1 设置适配器
        2.2 设置flag
        2.3 设置设备地址
(i2c_board_info->addr)
        2.4 设置irq
        2.4 设置设备名字
(i2c_board_info->type)
      3. 设置i2c_client描述的device设备(通过这个device可以找到i2c_client)

      4. 使用device_register()函数注册device设备
    最后是调用device_register注册这个device结构,由总线设备驱动模型可以猜到,必定有地方调用driver_register注册一个device_driver结构。内核通过调用i2c_add_driver()函数注册设备驱动(i2c_driver)里,就会注册与i2c_driver相关联的device_driver结构。
    对应的释放设备函数原型为:

void i2c_unregister_device(struct i2c_client *client);

3.3.2 i2c_add_driver()函数

    在include/linux/i2c.h中有如下宏定义:

//include/linux/i2c.h
#define i2c_add_driver(driver) \
	i2c_register_driver(THIS_MODULE, driver)

    所以i2c_add_driver()函数实际调用了i2c_register_driver()函数,该函数代码如下:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;
        ... ...

	/* 设置i2c_driver的device_driver成员 */
	driver->driver.owner = owner;    //设置device_driver成员的所有者
	driver->driver.bus = &i2c_bus_type;    //设置device_driver成员的总线类型
	INIT_LIST_HEAD(&driver->clients);
    
        /* 注册device_driver结构体 */
	res = driver_register(&driver->driver);    
	if (res)
		return res;

	/* 遍历i2c_bus_type匹配i2c_driver与i2c_adapter */
	i2c_for_each_dev(driver, __process_new_driver);

	return 0;
}

    该函数作用如下:
      1. 设置i2c_driver的device_driver成员
      2. 使用driver_register()函数注册device_driver
      3. 遍历i2c_bus_type匹配i2c_driver与i2c_adapter

3.3.2.1 分析作用2

    由总线设备驱动模型可以知道,当向内核注册device_driver驱动或者注册device设备,都会调用到device_driverdevice的总线结构体里的.match匹配函数匹配设备链表与驱动链表,由于他们的总线类型都设为i2c_bus_type,所以使用i2c_new_device()函数实例化I2C设备(i2c_client)或者使用i2c_add_driver()函数注册设备驱动(i2c_driver)时,都会调用到i2c_bus_type结构的.match匹配函数,i2c_bus_type结构定义如下:

struct bus_type i2c_bus_type = {
	.name		= "i2c",
	.match		= i2c_device_match,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

    其中i2c_device_match()函数代码如下:


const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
						const struct i2c_client *client)
{
	if (!(id && client))
		return NULL;

	while (id->name[0]) {
		if (strcmp(client->name, id->name) == 0)    //通过比较.name成员进行匹配
			return id;
		id++;
	}
	return NULL;
}
... ...
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
	struct i2c_client	*client = i2c_verify_client(dev);    //得到i2c_client结构体
	struct i2c_driver	*driver;


	/* 使用设备树匹配i2c_driver->of_match_table与i2c_client */
	if (i2c_of_match_device(drv->of_match_table, client))
		return 1;

	/* ACPI类型匹配 */
	if (acpi_driver_match_device(dev, drv))
		return 1;

	driver = to_i2c_driver(drv);    //得到i2c_driver结构体

	/* 匹配i2c_driver->id_table与i2c_client */
	if (i2c_match_id(driver->id_table, client))
		return 1;

	return 0;
}

    可见i2c_bus_type->match匹配函数实际是通过判断i2c_driver->id_table->namei2c_client->name是否相同,(这里思考一个问题,在调用i2c_new_device()函数注册i2c_client时,是通过传入的i2c_board_info->type成员赋值给i2c_client->name的,如果i2c_board_info不是由我们构建传入的,调用i2c_new_device()函数之前要在哪里指定这个i2c_board_info->type呢?)相同则匹配成功,调用i2c_bus_type->probe函数,该函数部分代码如下:

static int i2c_device_probe(struct device *dev)
{
	struct i2c_client	*client = i2c_verify_client(dev);    //得到i2c_client结构体
	struct i2c_driver	*driver;
	int status;
        
        ... ...
        driver = to_i2c_driver(dev->driver);    //得到i2c_driver结构体
        ... ...

        /* i2c_driver没有id_table成员且设备树匹配失败则函数返回 */
	if (!driver->id_table &&
	    !i2c_of_match_device(dev->driver->of_match_table, client))
		return -ENODEV;

        ... ...

        if (driver->probe_new)    //如果i2c_driver结构体有probe_new成员则调用
		status = driver->probe_new(client);
	else if (driver->probe)    //如果i2c_driver结构体有probe成员则调用
		status = driver->probe(client,
				       i2c_match_id(driver->id_table, client));
	else
		status = -EINVAL;
        ... ...
        return 0;
        ... ...
}

    i2c_bus_type->probe函数最终调用的是i2c_driver结构体的probe_new成员或者probe成员。probe函数里面做的事情由我们自己决定比如注册、构建设备节点等。

3.3.2.2 分析作用3

    i2c_register_driver()函数的作用3遍历i2c_bus_type匹配i2c_driver与i2c_adapter就是调用下面代码:
  

    __process_new_driver()函数内容如下:

static int __process_new_driver(struct device *dev, void *data)
{
	if (dev->type != &i2c_adapter_type)
		return 0;
	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

    只调用了i2c_do_add_adapter()函数,是不是很熟悉呢?没错,在3.2.3中注册i2c_adapter适配器时最终也调用了该函数,
作用就是遍历i2c_bus_type匹配i2c_driveri2c_adapter的,所以调用i2c_add_driver()函数会做与3.2.3.3节完全一样的工作,这里就不再重复了。

3.3.3 总结I2C设备驱动

    Linux内核中构建了许多总线,但并不都是真实的,比如platform平台总线就是虚拟的,平台总线中也有设备链表,驱动链表。针对I2C总线,也有一个I2C总线的结构即i2c_bus_type结构,此结构里面也有设备链表和也有驱动链表。
    设备链表里存放i2c_client的结构体,这些结构体是调用i2c_new_device()函数时加入的,不但要加入这些结构体,还会在i2c_bus_type结构的驱动链表中一个一个地比较drv里的i2c_driver来判断是否有匹配的,如果有将调用i2c_driver里面的probe函数,匹配函数由总线提供。
    驱动链表里存放i2c_driver结构体,这些结构体是调用i2c_add_driver()时加入的,不但要加入这些结构体,还会在i2c_bus_type结构的设备链表中一个一个地比较dev里的i2c_client来判断是否有匹配的,如果匹配将调用i2c_driver里面的probe函数。
    上述的匹配函数就是i2c_bus_type结构里面的i2c_device_match函数。i2c_device_match函数通过i2c_driver->id_table->namei2c_client->name比较(使用设备树是比较compatible属性),如果相同,就表示此驱动drv里存放的i2c_driver能支持这个设备dev里存放的i2c_client
    总的说来,I2C设备驱动基于bus-dev-drv模型的构建过程如下:
      1. 左边注册一个设备,设备里存有i2c_client(调用i2c_new_device()函数)
      2. 右边注册一个驱动,驱动里存有i2c_driver(调用i2c_add_driver()函数)
      3. 比较i2c_driver->id_table->namei2c_client->name(使用设备树是比较compatible属性),如果相同,则调用i2c_driver的probe函数。
      4. probe函数里面做的事情由用户决定
(比如注册、构建设备节点等)

3.3.4 实例化I2C设备

    上面介绍了实例化I2C设备的底层概念,接下来分析对于编写驱动我们应该做什么。总共有四种方法(如果算上设备树总共有5种方法,这里就不介绍设备树方式了),这4种方法最终都是通过调用i2c_new_device()函数来实例化I2C设备的。

3.3.4.1 实例化I2C设备方法一(需指定适配器,指定设备地址不检测是否存在)

    该方式核心是向内核静态注册i2c_board_info结构。
   回顾3.2.3 注册i2c_adapter适配器中,内核启动后会装载i2c-s3c2410.c(默认编译进内核)该驱动,也就是会调用到i2c_add_numbered_adapter()函数然后调用i2c_register_adapter()函数,该函数部分代码如下:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
        ... ...
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);    //设置适配器名字
	adap->dev.bus = &i2c_bus_type;    //设置适配器成员dev的总线为i2c_bus_type
	adap->dev.type = &i2c_adapter_type;    //设置适配器成员dev的类型为适配器类型
	res = device_register(&adap->dev);    //注册适配器成员dev设备

        ... ...
        
        /* 对dts上所描述的i2c_client设备进行实例化,并创建相应的sys文件:sys/bus/i2c/devices/xxx */
        of_i2c_register_devices(adap);
	i2c_acpi_register_devices(adap);
	i2c_acpi_install_space_handler(adap);

        /* 内核启动时(注册适配器之前),如果调用i2c_register_board_info()函数注册
        过i2c_board_info结构(描述i2c_client的结构),就调用i2c_scan_static_board_info
        对这些i2c_board_info结构里的i2c_client进行实例化 */
        if (adap->nr < __i2c_first_dynamic_bus_num)
	i2c_scan_static_board_info(adap);
        
        ... ...
        /* 遍历i2c_bus_type匹配i2c_driver与i2c_adapter */
        bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
        ... ...
}

    通过判断比较adap->nr __i2c_first_dynamic_bus_num决定是否执行i2c_scan_static_board_info()函数。假设执行了该函数,看看该函数做了什么工作:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
	struct i2c_devinfo	*devinfo;

	down_read(&__i2c_board_lock);

        /* 循环取出__i2c_board_list链表里的devinfo->board_info传入i2c_new_device()函数 */
	list_for_each_entry(devinfo, &__i2c_board_list, list) {
		if (devinfo->busnum == adapter->nr
				&& !i2c_new_device(adapter,
						&devinfo->board_info))
			dev_err(&adapter->dev,
				"Can't create device at 0x%02x\n",
				devinfo->board_info.addr);
	}
	up_read(&__i2c_board_lock);
}

    只做了一件事:循环取出__i2c_board_list链表里的devinfo->i2c_board_info传入i2c_new_device()函数。也就是将__i2c_board_list链表里保存的I2C设备信息实例化了。
    下面分析看看什么情况下会执行i2c_scan_static_board_info()函数。从3.2.3 注册i2c_adapter适配器中我们得到adap->nr=0,那么__i2c_first_dynamic_bus_num为多少呢?搜索内核发现,在driver/i2c/i2c_boardinfo.c文件中有如下内容:

int __i2c_first_dynamic_bus_num;    //定义__i2c_first_dynamic_bus_num,默认初始化为0
EXPORT_SYMBOL_GPL(__i2c_first_dynamic_bus_num);

... ...
int i2c_register_board_info(int busnum, struct i2c_board_info const *info, unsigned len)
{
	int status;
        ... ...
	if (busnum >= __i2c_first_dynamic_bus_num)
		__i2c_first_dynamic_bus_num = busnum + 1;

        /* 为每一个I2C设备信息分配内存,添加到__i2c_board_list链表里 */
	for (status = 0; len; len--, info++) {
		struct i2c_devinfo	*devinfo;

		devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
		if (!devinfo) {
			pr_debug("i2c-core: can't register boardinfo!\n");
			status = -ENOMEM;
			break;
		}

		devinfo->busnum = busnum;
		devinfo->board_info = *info;

                ... ...

		list_add_tail(&devinfo->list, &__i2c_board_list);    //添加到__i2c_board_list链表
	}
        ... ...

	return status;
}

    在driver/i2c/i2c_boardinfo.c文件中定义__i2c_first_dynamic_bus_num未初始化则默认为0,该变量只在i2c_register_board_info()注册单板I2C信息函数里执行修改操作(还在i2c_init()函数里修改该变量,但是没有调用该函数)。
    对于2440的单板文件mach-smdk2440.c里并没有使用i2c_register_board_info()注册单板I2C信息函数,所以__i2c_first_dynamic_bus_num一直为0,也就是说在i2c_register_adapter()函数里不会执行i2c_scan_static_board_info()扫描单板I2C信息函数。我们参考mach-mini2440.c单板文件如何使用i2c_register_board_info()函数的:

static struct i2c_board_info mini2440_i2c_devs[] __initdata = {
	{
		I2C_BOARD_INFO("24c08", 0x50),    //"24c08"表示设备名,0x50表示设备地址
		.platform_data = &at24c08,
	},
};
... ...
static void __init mini2440_init(void)
{

        ... ...
        /* 设置platform_device I2C相关信息 */
	s3c_i2c0_set_platdata(NULL);

        /* 注册单板I2C相关信息
           参数1:I2C总线号,0
           参数2:i2c_board_info结构体数组指针
           参数3:i2c_board_info结构体数组的成员个数
        */
	i2c_register_board_info(0, mini2440_i2c_devs,
				ARRAY_SIZE(mini2440_i2c_devs));
        ... ...

}
MACHINE_START(MINI2440, "MINI2440")
	/* Maintainer: Michel Pollet  */
	.atag_offset	= 0x100,
	.map_io		= mini2440_map_io,
	.init_machine	= mini2440_init,
	.init_irq	= s3c2440_init_irq,
	.init_time	= mini2440_init_time,
MACHINE_END

    当内核启动时就会调用mini2440_init()函数,里面调用了i2c_register_board_info()注册单板I2C信息函数(注意:该函数要在注册适配器之前调用),参数1为I2C总线号0(适配器0),参数2为定义的i2c_board_info结构体数组指针,参数3为i2c_board_info结构体数组成员个数,i2c_board_info结构体在3.3.1 i2c_new_device()函数的讲解中介绍了,这里不再重复。
    这样调用i2c_register_board_info()函数如下图,导致__i2c_first_dynamic_bus_num=1。
二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第6张图片
    所以在调用i2c_register_adapter()函数时会调用到i2c_scan_static_board_info()函数。针对JZ2440开发板(单板相关文件对应mach-smdk2440.c)仿照mach-mini2440.c修改内核源码即可实现实例化I2C设备。(具体实现放在后面的5.1 方法一

3.3.4.2 实例化I2C设备方法二(需指定适配器,指定设备地址(检测与不检测两种函数))

    该方式核心是向内核动态注册i2c_board_info结构。
   
与实例化I2C设备方法一类似,该方法也是通过构建i2c_board_info结构体,然后直接调用i2c_new_device()函数,对于该函数第一个参数需要的i2c_adapter结构体,可以通过i2c_get_adapter()函数(传入参数0表示想要获得适配器0)得到。与方法一的区别在于可以方便insmod,不需要修改源码,也就是可以在注册适配器之后再实例化I2C设备。
   还可以通过调用i2c_new_probed_device()函数,该函数部分代码如下:

struct i2c_client *i2c_new_probed_device(struct i2c_adapter *adap,
		      struct i2c_board_info *info,
		      unsigned short const *addr_list,
		      int (*probe)(struct i2c_adapter *, unsigned short addr))
{
	int i;

	if (!probe)
		probe = i2c_default_probe;    //如果参数4没有指定检测函数,使用默认检测函数检测设备地址是否真实存在

        /* 循环判断addr_list上的设备地址是否真实存在,存在则打破循环 */
	for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
		/* Check address validity */
		if (i2c_check_7bit_addr_validity_strict(addr_list[i]) < 0) {
			dev_warn(&adap->dev, "Invalid 7-bit address 0x%02x\n",
				 addr_list[i]);
			continue;
		}

		/* Check address availability (7 bit, no need to encode flags) */
		if (i2c_check_addr_busy(adap, addr_list[i])) {
			dev_dbg(&adap->dev,
				"Address 0x%02x already in use, not probing\n",
				addr_list[i]);
			continue;
		}

		/* Test address responsiveness */
		if (probe(adap, addr_list[i]))
			break;
	}

	if (addr_list[i] == I2C_CLIENT_END) {
		dev_dbg(&adap->dev, "Probing failed, no device found\n");
		return NULL;
	}

	info->addr = addr_list[i];    //将该设备地址存入i2c_board_info
	return i2c_new_device(adap, info);    //创建设备
}

    该函数主要内容如下:
      1. 判断参数4是否指定检测函数,没有则使用默认检测函数检测设备地址是否真实存在
      2. 循环判断addr_list上的设备地址是否真实存在,存在则打破循环
      3. 调用i2c_new_device创建设备

    最终还是调用了i2c_new_device()函数,只不过先检测了设备地址是否真实存在(调用i2c_default_probe()函数检验设备地址是否有ACK(具体实现放在后面的5.2 方法二

3.3.4.3 实例化I2C设备方法三(不需要指定适配器,需要指定设备地址并检测是否存在)

    该方式核心是使用i2c_add_driver()函数注册设备驱动时会自动匹配适配器,自动实例化I2C设备。
    回顾3.3.2 i2c_add_driver()函数讲的,当调用i2c_add_driver()函数最终会调用到i2c_detect()函数,函数部分代码如下:

static int i2c_detect(struct i2c_adapter *adapter, struct i2c_driver *driver)
{
	const unsigned short *address_list;
	struct i2c_client *temp_client;
	int i, err = 0;
	int adap_id = i2c_adapter_id(adapter);    //得到适配器ID = adap->nr

	address_list = driver->address_list;    //从i2c_driver得到I2C设备地址列表
        ... ...

	/* 如果适配器dapter->class定义为不自动检测类型,函数返回 */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
                ... ...
		return 0;
	}

	/* 如果i2c_driver支持的设备类型与适配器支持的类型不一样,函数返回 */
	if (!(adapter->class & driver->class))
		return 0;

	/* 分配设置i2c_client */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter;

        /* 取出每一个address_list里的设备地址进行检测 */
	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		dev_dbg(&adapter->dev,
			"found normal entry for adapter %d, addr 0x%02x\n",
			adap_id, address_list[i]);
		temp_client->addr = address_list[i];
		err = i2c_detect_address(temp_client, driver);    //检测该设备地址
		if (unlikely(err))
			break;
	}

	kfree(temp_client);    //释放i2c_client 
	return err;
}

    首先对于mach-smdk2440.c这个适配器来说,之前代码分析了注册该适配器时会指定adapter->class = I2C_CLASS_DEPRECATED,我们可以通过修改该内容,让这个适配器支持自动检测。然后当调用i2c_add_driver()函数传入的i2c_driver结构体定义了class成员(驱动支持的类型),并且与某个(2440只有一个)适配器的class(适配器支持的类型)匹配,表示使用自动检测模式注册设备驱动,会继续调用i2c_detect_address()函数,该函数部分代码如下:

static int i2c_detect_address(struct i2c_client *temp_client, struct i2c_driver *driver)
{
	struct i2c_board_info info;
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* 确保7位I2C设备地址在0x08~0x77之间 */
	err = i2c_check_7bit_addr_validity_strict(addr);
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}
        ... ...

	/* 确保总线上真实挂接有该设备地址的I2C芯片,通过i2c_smbus_xfer发送设备地址,
        如果有ACK返回,表示确实有真实的I2C芯片接在该总线上,如果没有ACK,函数返回 */
	if (!i2c_default_probe(adapter, addr))
		return 0;

	/* 调用i2c_driver->detect检测函数再次检测芯片具体是什么芯片,如通过读写芯片特定的寄存器 */
	memset(&info, 0, sizeof(struct i2c_board_info));
	info.addr = addr;
	err = driver->detect(temp_client, &info);

        ... ...

        /*创建设备*/
	client = i2c_new_device(adapter, &info);
        ... ...
	return 0;
}

    最终也会调用i2c_new_device()函数注册设备(详细分析内容看3.2.3.3)。这里回答3.3.2.1 里提出的思考问题:调用i2c_new_device()函数注册i2c_client时,是通过传入的i2c_board_info->type成员赋值给i2c_client->name的,如果i2c_board_info不是由我们构建传入的,调用i2c_new_device()函数之前要在哪里指定这个i2c_board_info->type呢?可以看到调用i2c_new_device()函数之前会调用i2c_driver->detect检测函数,我们可以在该函数里赋值i2c_board_info->type成员。
    方法二调用i2c_add_driver()函数注册设备驱动时不指定i2c_driver结构体的class成员,所以这里直接返回,需要我们自己调用i2c_new_device()函数。
(具体实现放在后面的5.3 方法三

3.3.4.4 实例化I2C设备方法四(需要指定适配器,需要指定设备地址不检测是否存在)

    该方式核心是从用户空间直接实例化I2C设备。
    在用户空间执行例如
echo at24c08 0x50 > /sys/class/i2c-adapter/i2c-0/new_device”:
   
内核会自动创建一个使用适配器0的设备名为“at24c08”,设备地址为0x50I2C设备,相当于方法二中构建i2c_board_info结构体,然后直接调用i2c_new_device()函数。
    在用户空间执行例如echo 0x50 > /sys/class/i2c-adapter/i2c-0/delete_device”:
    内核会调用i2c_unregister_device()函数删除这个设备地址为0x50I2C设备。
(具体实现放在后面的5.4 方法四

4 分析硬件

    讲解具体实现I2C设备驱动之前先讲解一下硬件信息。由于使用的JZ2440开发板上没有接I2C设备,所以通过杜邦线外接一个DS3231时钟芯片。

4.1 DS3231介绍

    DS3231是低成本、高精度I2C实时时钟RTC,RTC保存秒、分、时、星期、日期、月和年信息。少于31天的月份,将自动调整月末的日期,包括闰年的修正。时钟的工作格式可以是24小时或带/AM/PM指示的12小时格式。提供两个可设置的日历闹钟和一个可设置的方波输出。地址与数据通过I2C双向总线串行传输。详细芯片资料参考:https://html.alldatasheet.com/html-pdf/112132/DALLAS/DS3231/2425/11/DS3231.html

4.2 原理图

    原理图如下(DS3231时钟芯片通过杜邦线连接到开发板):
二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第7张图片二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第8张图片

4.3 相关寄存器

    ds3231其中的一组时钟和日历相关寄存器如下图所示:
二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第9张图片

通过芯片手册可得以下信息:  
    1. 通过读写00h~06h这7个寄存器地址就能读取与设置时钟和日历了。
    2. 从设备地址为7位ds3231地址1101000。

5. 编写I2C设备驱动与测试

内核:linux-4.12
编译器:arm-linux-gcc-4.4.3
环境:ubuntu9.10

5.1 方法一

    使用该方法编写I2C设备驱动特点:
      1. 需要修改内核源码
      2. 需要指定使用哪个适配器(S3C2440只有一个)
      3. 手动构建i2c_board_info(指定设备名字,指定设备地址)
      4. 内核帮我们调用i2c_new_device()函数
      5. 不检测设备地址是否真实存在
   
下面开始修改内核源码并测试。

5.1.1 修改arch/arm/mach-s3c24xx/mach-smdk2440.c

    在mach-smdk2440.c中添加构建单板I2C设备信息数组,并在smdk2440_machine_init()函数中注册该单板I2C设备信息,完整修改代码如下:

/*构建单板I2C设备信息*/
static struct i2c_board_info smdk2440_i2c_devs[] __initdata = {
	{
		I2C_BOARD_INFO("ds3231", 0x68),
	},
};

static void __init smdk2440_machine_init(void)
{
	s3c24xx_fb_set_platdata(&smdk2440_fb_info);
	s3c_i2c0_set_platdata(NULL);

        /*
        注册单板I2C设备信息
         参数1:使用适配器0
         参数2:i2c_board_info结构体数组
         参数3:i2c_board_info结构体数组成员个数
        */
	i2c_register_board_info(0, smdk2440_i2c_devs, ARRAY_SIZE(smdk2440_i2c_devs));

	platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
	smdk_machine_init();
}

    重新编译内核。下载启动。

5.1.2 编写I2C设备驱动

    通过上面的修改,按照3.3.4.1 实例化I2C设备方法一的分析,当内核启动后将会调用到i2c_new_device()函数注册i2c_board_info结构体(注册了i2c_client),所以接下来我们只需编写注册i2c_driver部分即可,完整代码ds3231.c如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BCD2BIN(val)	(((val) & 0x0f) + ((val)>>4)*10)
#define BIN2BCD(val)	((((val)/10)<<4) + (val)%10)

#define DS3231_REG_SEC		0x00	//秒
#define DS3231_REG_MIN		0x01	//分
#define DS3231_REG_HOURS	0x02	//时
#define DS3231_REG_DAY		0x03	//星期
#define DS3231_REG_DATE		0x04	//日
#define DS3231_REG_MOUNTH	0x05	//月
#define DS3231_REG_YEAR		0x06	//年
 
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
 
int major;	//主设备号
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
 
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
	unsigned char time[7];
	int reg = DS3231_REG_YEAR;
	int i=sizeof(time);
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 实际调用了i2c_smbus_xfer函数发送数据
	 * 循环读取时钟与日历寄存器数据,读出来的数据是BCD码,
	 * 先转换成16进制并拷贝给应用程序
	 */
	for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
		int tmp;
		if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
			dev_warn(&ds3231_client->dev,
				 "can't read from ds3231 chip\n");
			return -EIO;
		}
		time[--i] = BCD2BIN(tmp) & 0xff;
	}
	
	return copy_to_user(buf, &time, sizeof(time));
}
 
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned char time[7];
	int reg;
	int i=0;
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 拷贝要写入的7个16进制时钟与日历值 */
	if(copy_from_user(time, buf, sizeof(time)))
		return -EINVAL;
 
	/* 实际调用了i2c_smbus_xfer函数发送数据
	 * 循环写入转换成BCD码的时钟与日历值
	 */
	for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
		if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
			dev_warn(&ds3231_client->dev,
				 "can't write to ds3231 chip\n");
			return -EINVAL;
		}
		i++;
	}
	return sizeof(time);
}
 
static struct file_operations ds3231_fops = {
    .owner    =   THIS_MODULE,
    .write    =   ds3231_write,     
    .read     =	  ds3231_read,	   
};
 
static int ds3231_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	int err;
	dev_t devid;
	ds3231_client = client;	

 	printk("ds3231 probe !\n");
	/* 创建字符设备 */	
	devid = MKDEV(major, 0);	//从主设备号major,次设备号0得到dev_t类型
	if (major) 
	{
		err=register_chrdev_region(devid, 1, "ds3231");	//注册字符设备
	} 
	else 
	{
		err=alloc_chrdev_region(&devid, 0, 1, "ds3231");	//注册字符设备
		major = MAJOR(devid);	//从dev_t类型得到主设备
	}
	if(err < 0)
		return err;
	
	cdev_init(&ds3231_cdev, &ds3231_fops);
	cdev_add(&ds3231_cdev, devid, 1);
	
	ds3231_class = class_create(THIS_MODULE, "ds3231");
	device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
 
	return err;
}
 
static int ds3231_remove(struct i2c_client *client)
{
	printk("ds3231_remove !\n");
	device_destroy(ds3231_class,MKDEV(major, 0));
	class_destroy(ds3231_class);
	cdev_del(&ds3231_cdev);
	unregister_chrdev_region(MKDEV(major, 0), 1);
		
	return 0;
}

static const struct i2c_device_id ds3231_id_table[] = {
	{ "ds3231", 0 },    //名字“ds3231”与i2c_board_info的.type一样
	{}
};
 
static struct i2c_driver ds3231_driver = {
	.driver	= {
		.name	= "ds3231_driver",
		.owner	= THIS_MODULE,
	},
	.probe 		= ds3231_probe,
	.remove		= ds3231_remove,
	.id_table	= ds3231_id_table,
};
 
static int ds3231_init(void)
{
	return i2c_add_driver(&ds3231_driver);
}
 
static void ds3231_exit(void)
{
	i2c_del_driver(&ds3231_driver);
}
 
module_init(ds3231_init);
module_exit(ds3231_exit);
 
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

    当装载该驱动时,由总线设备驱动模型会调用到该驱动的probe函数,我们在该函数里面创建字符设备,再编写一个测试程序ds3231_test.c,完整代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
 
/* ./ds3231_test r    //读时钟与日历格式
 * ./ds3231_test w Year Mounth Date Day Hour Min Sec    //设置时钟与日历格式
 */
 
void print_usage(char *file)
{
	printf("%s r\n", file);
	printf("%s w Year Mounth Date Day Hour Min Sec\n", file);
}
 
int main(int argc, char **argv)
{
	int fd;
	unsigned char buf[7];
	int i;
	
	if ((argc != 2) && (argc != 9))
	{
		print_usage(argv[0]);
		return -1;
	}
 
	fd = open("/dev/ds3231", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/ds3231\n");
		return -1;
	}
 
	if (strcmp(argv[1], "r") == 0)	//读取时钟与日历
	{
		read(fd, buf, sizeof(buf));
		printf("Now Time= Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
			 buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
	}
	else if (strcmp(argv[1], "w") == 0)	//写时钟与日历
	{
		for(i=0;i<7;i++)
		{
			buf[6-i] = strtoul(argv[i+2], NULL, 0);	//将字符转换成数值
		}
		printf("Set Time Year:%02d, Mounth:%02d, Date:%02d, Day:%d, Hour:%02d, Min:%02d, Sec:%02d\n", \
		buf[6], buf[5],buf[4], buf[3], buf[2],buf[1],buf[0]);
		write(fd, buf, sizeof(buf));
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

    Makefile代码如下:

KERN_DIR = /work/tools/linux-4.12
 
all:
	make -C $(KERN_DIR) M=`pwd` modules 
 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
 
obj-m	+= ds3231.o

5.1.3 测试

    1. 启动修改好的内核。
    2. 拷贝以上3个文件到网络文件系统编译并安装驱动,进入文件目录,执行如下命令:
      make
      arm-linux-gcc -o ds3231_test ds3231_test.c
   
ls得到如下:
   
    安装驱动如下:
   
    可以看到调用了ds3231.c驱动的probe函数,使用应用程序测试,执行如下命令:
   
    执行如下命令:
      ./ds3231_test w 18 12 31 1 23 59 55 (设置当前时间为18年12月31日星期1 23点59分55秒)
      ./ds3231_test r (读取当前时间)
    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第10张图片

5.2 方法二

    使用该方法编写I2C设备驱动特点:
      1. 需要指定使用哪个适配器(S3C2440只有一个)
      2. 手动构建i2c_board_info(指定设备名字,指定设备地址)

      3. 编写驱动调用i2c_new_device()函数(不检测设备地址是否真实存在)或者调用i2c_new_probed_device()函数(检测设备地址是否真实存在)
   
按照3.3.4.2 实例化I2C设备方法二的分析编写I2C设备驱动,在ds3231_dev.c里注册i2c_client,在ds3231_drv.c(与方法一的ds3231.c相同,不贴代码了)里注册i2c_driverds3231_dev.c的编写有两种,主要不同在使用i2c_new_device()函数(不检测设备地址是否真实存在)或者调用i2c_new_probed_device()函数。

5.2.1 编写I2C设备驱动(使用i2c_new_device

    使用i2c_new_device()函数的ds3231_dev.c代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct i2c_board_info ds3231_info = {	
	I2C_BOARD_INFO("ds3231", 0x68),
};
static struct i2c_client *ds3231_client;

static int ds3231_dev_init(void)
{
	struct i2c_adapter *i2c_adap;

	i2c_adap = i2c_get_adapter(0);	/*因为硬件挂接在I2C总线0上,所以参数是0*/

/*在该适配器下创建了一个新设备,设备信息在i2c_board_info结构体中,
以后就可以使用这个i2c_adapter	的操作函数对该设备发出I2C的信号了*/
	ds3231_client = i2c_new_device(i2c_adap, &ds3231_info);	

	i2c_put_adapter(i2c_adap);	/*使用完要释放*/
	return 0;
}
static void ds3231_dev_exit(void)
{
	i2c_unregister_device(ds3231_client);	/*卸载注册的设备*/
}
module_init(ds3231_dev_init);
module_exit(ds3231_dev_exit);

MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

5.2.2 编写I2C设备驱动(使用i2c_new_probed_device

    使用i2c_new_probed_device()函数的ds3231_dev.c代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static struct i2c_client *ds3231_client;

/*short addr_list[]中可以存放多个设备地址,
在 i2c_new_probed_device函数中依次进行匹配,
I2C_CLIENT_END	表示后面没有参数了*/
static const unsigned short addr_list[] = { 0x60, 0x68, I2C_CLIENT_END };

static int ds3231_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info ds3231_info;

	memset(&ds3231_info, 0, sizeof(struct i2c_board_info));	
	strlcpy(ds3231_info.type, "ds3231", I2C_NAME_SIZE);

	i2c_adap = i2c_get_adapter(0);
	ds3231_client = i2c_new_probed_device(i2c_adap, &ds3231_info, addr_list, NULL);
	i2c_put_adapter(i2c_adap);

	if (ds3231_client)
		return 0;
	else
		return -ENODEV;
}
static void ds3231_dev_exit(void)
{
	i2c_unregister_device(ds3231_client);
}

module_init(ds3231_dev_init);
module_exit(ds3231_dev_exit);

MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

    测试程序ds3231_test.c与方法一的一样,ds3231_drv.c与方法一的ds3231.c相同。Makefile代码如下:

KERN_DIR = /work/tools/linux-4.12
 
all:
	make -C $(KERN_DIR) M=`pwd` modules 
 
clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
 
obj-m	+= ds3231_dev.o
obj-m	+= ds3231_drv.o

5.2.3 测试

    注意如果使用过方法一修改了mach-smdk2440.c文件要修改回去,否则装载ds3231_dev.c时提示不能重复装载相同设备地址的驱动。
    拷贝以上4个文件到网络文件系统编译并安装驱动,进入文件目录,执行如下命令:
      make
      arm-linux-gcc -o ds3231_test ds3231_test.c
   
ls得到如下:
    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第11张图片
    安装驱动如下:
   
    可以看到调用了ds3231_drv.c驱动的probe函数,后面的使用应用程序测试与方法一一样。

5.3 方法三

    使用该方法编写I2C设备驱动特点:
      1. 修改源码(由于该版本内核不支持i2c-s3c2410.c这个适配器自动检测,修改让其支持)
      2. 不需要指定使用哪个适配器
      3. 不需要构建i2c_board_info,但通过赋值给i2c_board_info->type成员传递设备名字,构建一个address_list传递地址
      4. 内核帮我们调用i2c_new_device()函数
      5. 检测设备地址是否真实存在

5.3.1 修改driver/i2c/busses/i2c-s3c2410.c

    按照3.3.4.2 实例化I2C设备方法二的分析,修改适配器文件driver/i2c/busses/i2c-s3c2410.c文件,由于该版本内核不支持这个适配器自动检测I2C_CLASS_DEPRECATED,修改让其支持改为I2C_CLASS_HWMON,如下:
    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第12张图片
    然后重新编译内核。

5.3.2 编写I2C设备驱动

    按照3.3.4.2 实例化I2C设备方法二的分析编写I2C设备驱动,我们只需注册i2c_driver,完整代码ds3231.c如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BCD2BIN(val)	(((val) & 0x0f) + ((val)>>4)*10)
#define BIN2BCD(val)	((((val)/10)<<4) + (val)%10)

#define DS3231_REG_SEC		0x00	//秒
#define DS3231_REG_MIN		0x01	//分
#define DS3231_REG_HOURS	0x02	//时
#define DS3231_REG_DAY		0x03	//星期
#define DS3231_REG_DATE		0x04	//日
#define DS3231_REG_MOUNTH	0x05	//月
#define DS3231_REG_YEAR		0x06	//年
 
static struct i2c_driver ds3231_driver;
static struct i2c_client *ds3231_client;
 
int major;	//主设备号
static struct cdev ds3231_cdev;
static struct class *ds3231_class;
 
static ssize_t ds3231_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
	unsigned char time[7];
	int reg = DS3231_REG_YEAR;
	int i=sizeof(time);
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 实际调用了i2c_smbus_xfer函数发送数据
	 * 循环读取时钟与日历寄存器数据,读出来的数据是BCD码,
	 * 先转换成16进制并拷贝给应用程序
	 */
	for (reg = DS3231_REG_YEAR; reg >= DS3231_REG_SEC; reg--){
		int tmp;
		if ((tmp = i2c_smbus_read_byte_data(ds3231_client, reg)) < 0) {
			dev_warn(&ds3231_client->dev,
				 "can't read from ds3231 chip\n");
			return -EIO;
		}
		time[--i] = BCD2BIN(tmp) & 0xff;
	}
	
	return copy_to_user(buf, &time, sizeof(time));
}
 
static ssize_t ds3231_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	unsigned char time[7];
	int reg;
	int i=0;
	
	if (size != sizeof(time))
		return -EINVAL;
 
	/* 拷贝要写入的7个16进制时钟与日历值 */
	if(copy_from_user(time, buf, sizeof(time)))
		return -EINVAL;
 
	/* 实际调用了i2c_smbus_xfer函数发送数据
	 * 循环写入转换成BCD码的时钟与日历值
	 */
	for (reg = DS3231_REG_SEC; reg <= DS3231_REG_YEAR; reg++) {
		if (i2c_smbus_write_byte_data(ds3231_client, reg, BIN2BCD(time[i]))< 0) {
			dev_warn(&ds3231_client->dev,
				 "can't write to ds3231 chip\n");
			return -EINVAL;
		}
		i++;
	}
	return sizeof(time);
}
 
static struct file_operations ds3231_fops = {
    .owner  =   THIS_MODULE,
    .write  =   ds3231_write,     
    .read   =	ds3231_read,	   
};
 
static int ds3231_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	int err;
	dev_t devid;
	ds3231_client = client;	

 	printk("ds3231 probe !\n");
	/* 创建字符设备 */	
	devid = MKDEV(major, 0);	//从主设备号major,次设备号0得到dev_t类型
	if (major) 
	{
		err=register_chrdev_region(devid, 1, "ds3231");	//注册字符设备
	} 
	else 
	{
		err=alloc_chrdev_region(&devid, 0, 1, "ds3231");	//注册字符设备
		major = MAJOR(devid);	//从dev_t类型得到主设备
	}
	if(err < 0)
		return err;
	
	cdev_init(&ds3231_cdev, &ds3231_fops);
	cdev_add(&ds3231_cdev, devid, 1);
	
	ds3231_class = class_create(THIS_MODULE, "ds3231");
	device_create(ds3231_class, NULL, MKDEV(major, 0), NULL, "ds3231"); /* /dev/ds3231 */
 
	return err;
}
 
static int ds3231_remove(struct i2c_client *client)
{
	printk("ds3231_remove !\n");
	device_destroy(ds3231_class,MKDEV(major, 0));
	class_destroy(ds3231_class);
	cdev_del(&ds3231_cdev);
	unregister_chrdev_region(MKDEV(major, 0), 1);
		
	return 0;
}

static const struct i2c_device_id ds3231_id_table[] = {
	{ "ds3231", 0 },	//设备名字
	{}
};

static int ds3231_detect(struct i2c_client *client,
		       struct i2c_board_info *info)
{
	/* 能运行到这里, 表示该addr的设备是存在的
	 * 但是有些设备单凭地址无法分辨(A芯片的地址是0x68, B芯片的地址也是0x68)
	 * 还需要进一步读写I2C设备来分辨是哪款芯片
	 * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type
	 */
	
	printk("ds3231_detect : addr = 0x%x\n", client->addr);
	/* 在这里可以进一步判断是哪一款芯片 */
	
	strlcpy(info->type, "ds3231", I2C_NAME_SIZE);    //与i2c_device_id里的设备名字一样
	return 0;
}

static const unsigned short addr_list[] = { 0x60, 0x68, I2C_CLIENT_END };

 static struct i2c_driver ds3231_driver = {
	.class  = I2C_CLASS_HWMON, /* 表示该驱动是什么类型,匹配支持相同的类型适配器 */
	.driver	= {
		.name	= "ds3231_driver",
		.owner	= THIS_MODULE,
	},
	.probe		= ds3231_probe,
	.remove		= ds3231_remove,
	.id_table	= ds3231_id_table,
	.detect     = ds3231_detect,  /* 用这个函数来检测设备确实存在 */
	.address_list	= addr_list,   /* 这些设备的地址 */
};
 
static int ds3231_init(void)
{
	return i2c_add_driver(&ds3231_driver);
}
 
static void ds3231_exit(void)
{
	i2c_del_driver(&ds3231_driver);
}
 
module_init(ds3231_init);
module_exit(ds3231_exit);
 
MODULE_AUTHOR("LVZHENHAI");
MODULE_LICENSE("GPL");

    测试程序ds3231_test.c与方法一一样,Makefile也一样。

5.3.3 测试

    1. 启动修改好的内核。
    2. 拷贝以上3个文件到网络文件系统编译并安装驱动,进入文件目录,执行如下命令:
      make
      arm-linux-gcc -o ds3231_test ds3231_test.c
   
ls得到如下:
    二十三、Linux驱动之IIC驱动(基于linux4.12内核)_第13张图片
    安装驱动如下:
   
    可以看到先调用了ds3231_drv.c驱动的detect函数,然后执行probe函数,后面的使用应用程序测试与方法一一样。

5.4 方法四

    使用该方法编写I2C设备驱动特点:
      1. 直接在用户空间创建或删除I2C设备(通过命令指定设备名字,指定设备地址
     
2. 不检测设备地址是否真实存在(注册i2c_driver时去匹配上面的设备名字,匹配成功就会执行驱动的probe函数)
    启动内核在开发板上执行如下命令:
      ls /sys/class/i2c-adapter/
   

    可以看到只有一个i2c-0,这就是2440唯一一个适配器。下面直接通过命令创建设备或删除设备。

5.4.1 创建设备

    执行如下命令:
      echo ds3231 0x68 > /sys/class/i2c-adapter/i2c-0/new_device
   

    内核会自动创建一个使用适配器0的设备名为“ds3231”,设备地址为0x68I2C设备,相当于方法二中构建i2c_board_info结构体,然后内核帮我们调用i2c_new_device()函数。

5.4.2 删除设备

    执行如下命令:
      echo 0x68 > /sys/class/i2c-adapter/i2c-0/delete_device
   
    内核会调用i2c_unregister_device()函数删除这个设备地址为0x68I2C设备。

5.4.3 测试

    使用5.4.1 创建设备后,再使用方法一的设备驱动程序ds3231.c、测试程序ds3231_test.cMakefile。测试方法与效果也一样。

还有一种方法是使用设备树方式,这里就先不介绍了。

你可能感兴趣的:(linux驱动)