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

linux驱动 专栏收录该内容
23 篇文章 5 订阅

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目录下内有如下文件:
 

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体系架构图如下:
   
    主要分为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不自动检测类型,如下图:
   

    所以对于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()函数中有如下代码:
   

    s3c24xx_i2c_algorithm定义如下:

   

    可见适配器并没有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 <buserror@gmail.com> */
	.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。

    所以在调用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时钟芯片通过杜邦线连接到开发板):

4.3 相关寄存器

    ds3231其中的一组时钟和日历相关寄存器如下图所示:

通过芯片手册可得以下信息:  
    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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/device.h>

#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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
/* ./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 (读取当前时间)
   

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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

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得到如下:
   
    安装驱动如下:
   
    可以看到调用了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,如下:
   
    然后重新编译内核。

5.3.2 编写I2C设备驱动

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

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/device.h>

#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得到如下:
   
    安装驱动如下:
   
    可以看到先调用了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。测试方法与效果也一样。

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

  • 3
    点赞
  • 0
    评论
  • 17
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

作者: 李岩 荣盘祥 丛书名: 普通高校本科计算机专业特色教材精选 出版社:清华大学出版社 ISBN:7302097259 内容简介回到顶部↑  本书特色:   1.新颖性:本书以当前最廉价、接口最丰富、占有率最高的ARM内核的处理器芯片——S3C44BOX为基础进行讲   解,并将嵌入式系统理论与当前最流行且最适用于嵌入式应用的Linux操作系统进行整和。   2.知识的系统性:本书从教学和应用出发,将操作系统(Linux)、微机原理(ARM处理器)、汇编/C语言程   序设计、计算机网络等知识进行系统化。   3.易学性和实用性相结合:本书在阐述嵌入式系统的基本理论、主要内容和实际开发应用方法的同时,特别注   重理论和实践相结合,既具有面向教学又具有面向开发与应用的特点。   4.理论与案例相结合:本书在阐述理论知识的同时,给出了简单明了的实验程序和综合示例。    本书着重介绍了目前流行的S3C44B0X微处理器及应用较为广泛的μCLinux嵌入式操作系统。书中作者结合多年的研究和教学经验,介绍了基于S3C44B0X芯片的嵌入式系统的原理、程序设计方法以及实用性程序的开发。    本书共分为12章。第1~第3章介绍了嵌入式系统的基本概念,常用的ARM/Thumb指令系统及编译工具GNUGCC的使用方法;第4~第5章介绍了S3C44BOX芯片的体系结构,结合MICETEK(上海祥佑数码,WWW.micetek.com.cn)EV44BOII开发板介绍了S3C44BOX的接口电路设计方法及编程;第6~第10章介绍了主流的μCLinux操作系统的进程管理、文件系统管理、设备管理、以及用户接口的设计过程,其中包括了开发工具Hitool forμCLinux的使用方法介绍及简单驱动程序的编写;第11~第12章介绍了应用程序开发实例和μCLinux在其他平台上的移植过程。    本书主要从嵌入式理论、方法和应用实践的角度出发,以ARM处理器S3C44BOX为基础,展现了嵌入式系统的基本理论的主要内容和实际应用开发方法。做到学术性、新颖性、可读性和使用性相结合。本书可作为有关嵌入式系统教学的本科生或研究生的教材使用,也可供嵌人式爱好者、从业人员和高等院校师生专业进修和教学之用。    目录回到顶部↑第1章 嵌入式系统导论 1.1 概述 1.1.1 什么是嵌入式系统 1.1.2 嵌入式系统的特点及分类 1.2 嵌入式微处理器和嵌入式操作系统 1。2.1 嵌入式微处理器 1.2.2 嵌入式操作系统 1.3 嵌入式系统设计过程 1.3.1 需求分析 1.3.2 规格说明 1.3.3 体系结构设计 1.3.4 设计硬件构件和软件构件 1.3.5 系统集成 1.3.6 系统测试 小结 习题 第2章 arm/thumb微处理器结构及指令系统 2.1 arm微处理器概述 2.1.1 arm处理器系列 2.1.2 risc体系结构 .2.1.3 arm和thumb状态 2.1.4 寄存器 2.1.5 arm指令集概述 2.1.6 thumb指令集概述 2.2 arm微处理器体系结构 2.2.1 数据类型 2.2.2 arm微处理器的工作状态 2.2.3 arm体系结构的存储器格式 2.2.4 处理器模式 2.2.5 寄存器组织 2.2.6 异常 2.3 arm/thumb指令系统 2.3.1 arm处理器寻址方式 2.3.2 arm指令集介绍 2.3.3 thumb指令集介绍 小结 习题 第3章 程序设计基础 3.1 gnugcc简介 3.1.1 gcc组成 3.1.2 gcc编译程序的基本过程 3.2 c/c++交叉编译器arm-elf-gee 3.2.1 概述 3.2.2 命令使用 3.3 交叉连接器arm-elf-id 3.3.1 概述 3.3.2 命令使用 3.3.3 linkemds链接命令文件 3.4 工程管理器make 3.4.1 概述 3.4.2 命令使用 3.4.3 编写--个makefile 3.5 交叉汇编器arm-elf-as 3.5.1 概述 3.5.2 命令使用 3.6 汇编语言编程 3.6.1 汇编语言 3.6.2 宏语句与条件汇编 3.6.3 模块化程序设计 3.6.4 内存模式 3.6.5 strongarm & arm7 3.7 简单程序设计 3.7.1 顺序程序设计 3.7.2 分支程序设计 3.7.3 循环程序设计 3.7.4 子程序设计 3.8 混合语言编程 3.8.1 如何在c语言内嵌汇编语言 3.8.2 c与汇编相互调用 小结
©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值