linux 深入理解I2C内核驱动

系列文章

I.MX6ULL 手册查找使用方法 实战点亮LED(寄存器版)
I.MX6ULL 手册查找使用方法 实战点亮LED(固件库版本)
linux 字符设备驱动实战
linux LED设备驱动文件
linux 设备树(.dts)实战解析
linux 使用设备树点亮LED 实战
linux 驱动中并发与竞争
linux 内核定时器
linux 内核中断理解
linux 驱动阻塞和非阻塞
linux 内核异步通知
linux platform驱动框架
linux 内核自带的LED灯驱动
linux misc设备驱动
linux input子系统

前言

I2C 是很常用的串行通信接口,用于连接各种外设、传感器等器件。本文理解I2C 内核的实现和开发人员需要开发的工作,不涉及I2C的物理特性,时序等信息。

内核将 I2C 驱动分为两部分:

I2C 总线驱动,I2C 总线驱动就是 SOC 的 I2C 控制器驱动,也叫做 I2C 适配器驱动;
I2C 设备驱动,I2C 设备驱动就是针对具体的 I2C 设备而编写的驱动。


1、I2C 总线驱动

I2C 总线驱动重点是 I2C 适配器驱动,两个重要的数据结构:i2c_adapter 和 i2c_algorithm。

i2c_adapter

/*
 * i2c_adapter is the structure used to identify a physical i2c bus along
 * with the access algorithms necessary to access it.
 */
struct i2c_adapter {
        struct module *owner;
        unsigned int class;               /* classes to allow probing for */
        const struct i2c_algorithm *algo; /* the algorithm to access the bus */
        void *algo_data;
        struct device dev;              /* the adapter device */
        char name[48];\
        ...
};

i2c_algorithm

/**
 * struct i2c_algorithm - represent I2C transfer method
 * @master_xfer: Issue a set of i2c transactions to the given I2C adapter
 *   defined by the msgs array, with num messages available to transfer via
 *   the adapter specified by adap.
 */
struct i2c_algorithm {

        int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
                           int num);
        int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,
                           unsigned short flags, char read_write,
                           u8 command, int size, union i2c_smbus_data *data);

        /* To determine what the adapter supports */
        u32 (*functionality) (struct i2c_adapter *);
};

i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。
i2c_algorithm中关键函数master_xfer产生用于收发函数,以i2c_msg为单位(传输地址、方向、缓冲区、长度等信息)

master_xfer 就是 I2C 适配器的传输函数; smbus_xfer 就是 SMBUS 总线的传输函数。

通过 i2c_add_numbered_adapter或 i2c_add_adapter 这两个函数向系统注册设置好的 i2c_adapter,这两个函数的原型如下:

int i2c_add_adapter(struct i2c_adapter *adapter)
int i2c_add_numbered_adapter(struct i2c_adapter *adap)

删除 I2C 适配器

void i2c_del_adapter(struct i2c_adapter * adap)

2、I2C 设备驱动

I2C 设备驱动重点关注两个数据结构:i2c_client 和 i2c_driver。

i2c_client 就是描述设备信息,与设备树对应 一个设备对应一个 i2c_client

struct i2c_client {
        unsigned short flags;           /* div., see below              */
        unsigned short addr;            /*芯片地址,7位,存在低7位   */                                        
        char name[I2C_NAME_SIZE];
        struct i2c_adapter *adapter;    /* 对应的适配器 */
        struct device dev;              /*设备结构体  */
        int irq;                        /* 中断 */
        struct list_head detected;
};

i2c_driver 描述驱动内容(编程具体实现) 编写 I2C 设备驱动重点要实现的内容

struct i2c_driver {
        unsigned int class;
        int (*attach_adapter)(struct i2c_adapter *) __deprecated;
        /* Standard driver model interfaces */
        int (*probe)(struct i2c_client *, const struct i2c_device_id *);
        int (*remove)(struct i2c_client *);
        void (*shutdown)(struct i2c_client *);

        struct device_driver driver;
        const struct i2c_device_id *id_table;
        ...
};

i2c_driver:对应于一套驱动方法,提供probe(),remove(),suspend()等函数。
i2c_client:对应于真实的物理设备,通常用i2c_board_info填充,或者使用设备树描述。

i2c_driver与i2c_client关系是一对多,一个i2c_driver同时支持多个同类型的i2c_client。
i2c_client依附于i2c_adpater。一个i2c_adpater,可以被多个i2c_client依附。

向Linux 内核注册i2c_driver ,注册函数为 int i2c_register_driver

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)

i2c_add_driver 也常常用于注册 i2c_driver

#define i2c_add_driver(driver) \
					i2c_register_driver(THIS_MODULE, driver)

注销:

void i2c_del_driver(struct i2c_driver *driver)

3、I2C 核心

I2C 设备和驱动的匹配过程是由 I2C 核心来完成的,数据结构为 i2c_bus_type,定义在 drivers/i2c/i2c-core.c 文件:

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匹配过程,三种匹配方式

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
        struct i2c_client       *client = i2c_verify_client(dev);
        struct i2c_driver       *driver;

        /* Attempt an OF style match */
        if (of_driver_match_device(dev, drv))
                return 1;

        /* Then ACPI style match */
        if (acpi_driver_match_device(dev, drv))
                return 1;

        driver = to_i2c_driver(drv);
        /* match on an id table if there is one */
        if (driver->id_table)
                return i2c_match_id(driver->id_table, client) != NULL;

        return 0;
}

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

i2c_bus_type 在何时使用?首先看一下i2c框架做了什么


4、I2C框架,系统帮做了什么?

总线注册到系统 涉及到的结构体 i2c_adapter,i2c_algorithm

在 imx6ull.dtsi 文件中找到 I.MX6U 的 I2C1 控制器节点。

 i2c1: i2c@021a0000 {
	#address-cells = <1>;
	#size-cells = <0>;
	compatible = "fsl,imx6ul-i2c", "fsl,imx21-i2c";
	reg = <0x021a0000 0x4000>;
	interrupts = <GIC_SPI 36 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&clks IMX6UL_CLK_I2C1>;
	status = "disabled";
}

在内核源文件夹下,搜索属性值“imx6ul-i2c”,grep -r “imx6ul-i2c” ./ ,在驱动文件drivers/i2c/busses/i2c-imx.c中找到

static const struct of_device_id i2c_imx_dt_ids[] = {
        { .compatible = "fsl,imx1-i2c", .data = &imx1_i2c_hwdata, },
        { .compatible = "fsl,imx21-i2c", .data = &imx21_i2c_hwdata, },
        { .compatible = "fsl,vf610-i2c", .data = &vf610_i2c_hwdata, },
        { /* sentinel */ }
};
...
static struct platform_driver i2c_imx_driver = {
        .probe = i2c_imx_probe,
        .remove = i2c_imx_remove,
        .driver = {
                .name = DRIVER_NAME,
                .owner = THIS_MODULE,
                .of_match_table = i2c_imx_dt_ids,
                .pm = IMX_I2C_PM,
        },
        .id_table       = imx_i2c_devtype,
};

static int __init i2c_adap_imx_init(void)
{
        return platform_driver_register(&i2c_imx_driver);
}

1)I2C 适配器驱动是个标准的 platform 驱动
2)“fsl,imx21-i2c”属性值,设备树中 i2c1 节点的 compatible 属性值一致
3)platform_driver_register 将驱动注册到I2C总线上,如何执行match函数,下面分析:

#define platform_driver_register(drv) \
        __platform_driver_register(drv, THIS_MODULE)

//查看 __platform_driver_register函数
int __platform_driver_register(struct platform_driver *drv,
                                struct module *owner)
{
        drv->driver.owner = owner;
        drv->driver.bus = &platform_bus_type;//重要:绑定总线
	    ... 
        return driver_register(&drv->driver);
}
//接着查看driver_register函数
int driver_register(struct device_driver *drv)
{
        other = driver_find(drv->name, drv->bus);
        ret = bus_add_driver(drv);
        ret = driver_add_groups(drv, drv->groups);
		... 
}

int bus_add_driver(struct device_driver *drv)
{
        bus = bus_get(drv->bus);
        ...
        if (drv->bus->p->drivers_autoprobe) {
                error = driver_attach(drv);
        }
        module_add_driver(drv->owner, drv);
        error = driver_create_file(drv, &driver_attr_uevent);
}

int driver_attach(struct device_driver *drv)
{
        return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
#__driver_attach 函数为参数
static int __driver_attach(struct device *dev, void *data)
{
        if (!driver_match_device(drv, dev))
                return 0;
		...
        if (!dev->driver)
                driver_probe_device(drv, dev);
}

static inline int driver_match_device(struct device_driver *drv, struct device *dev)
{
        return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
最终调用总线match函数
drivers/base/platform.c中有总线bus类型
struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,
};
drv->bus->match是哪个函数呢?drv->driver.bus = &platform_bus_type
drv->bus->match即platform_bus_type.match

struct bus_type platform_bus_type = {
        .name           = "platform",
        .dev_groups     = platform_dev_groups,
        .match          = platform_match,
        .uevent         = platform_uevent,
        .pm             = &platform_dev_pm_ops,
};

static int platform_match(struct device *dev, struct device_driver *drv)
{
        struct platform_device *pdev = to_platform_device(dev);
        struct platform_driver *pdrv = to_platform_driver(drv);
        
        if (pdev->driver_override)
                return !strcmp(pdev->driver_override, drv->name);
        //设备树匹配函数        
        if (of_driver_match_device(dev, drv))
                return 1;
                
        if (acpi_driver_match_device(dev, drv))
                return 1;
        if (pdrv->id_table)
                return platform_match_id(pdrv->id_table, pdev) != NULL;
        return (strcmp(pdev->name, drv->name) == 0);
}

总结:设备与platform_driver 的绑定

驱动注册的时候platform_driver_register()->driver_register()->bus_add_driver()->driver_attach()->bus_for_each_dev()
对每个挂在虚拟的platform bus的设备作match操作;
如果相符就调用platform_drv_probe()->driver->probe(),如果probe成功则绑定该设备到该驱动.

匹配match后,执行probe函数,执行的流程如下

int driver_probe_device(struct device_driver *drv, struct device *dev)
{
        if (!device_is_registered(dev))
                return -ENODEV;
        pm_runtime_barrier(dev);
        ret = really_probe(dev, drv);
}

static int really_probe(struct device *dev, struct device_driver *drv)
{
	 if (dev->bus->probe) {
                ret = dev->bus->probe(dev);
        } else if (drv->probe) {
                ret = drv->probe(dev);
        }
}

drv->probe则 i2c_imx_probe函数执行。

i2c_adapter 在什么时候注册的?在i2c_imx_probe函数中

static int i2c_imx_probe(struct platform_device *pdev)
{
		//i2c_imx  为adapter结构体
        i2c_imx = devm_kzalloc(&pdev->dev, sizeof(*i2c_imx), GFP_KERNEL);
		...
        /* Setup i2c_imx driver structure 对结构体赋值 */
        strlcpy(i2c_imx->adapter.name, pdev->name, sizeof(i2c_imx->adapter.name));
        i2c_imx->adapter.owner          = THIS_MODULE;
        i2c_imx->adapter.algo           = &i2c_imx_algo;
        i2c_imx->adapter.dev.parent     = &pdev->dev;
        i2c_imx->adapter.nr             = pdev->id;
        i2c_imx->adapter.dev.of_node    = pdev->dev.of_node;
        i2c_imx->base                   = base;
        
        ret = i2c_add_numbered_adapter(&i2c_imx->adapter);
}

总结:probe函数中完成工作

  1. 初始化 i2c_adapter,设置 i2c_algorithm 为 i2c_imx_algo,最后向 Linux 内核注册i2c_adapter。
  2. 初始化 I2C1 控制器的相关寄存器。i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer,i2c_imx_algo 结构体定

以上分析的函数中带有driver,以下分析的带有device

i2c_adapter做了什么? 内核的i2c总线上注册adapter device和client device
i2c_add_numbered_adapter(&i2c_imx->adapter)

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
        if (adap->nr == -1) /* -1 means dynamically assign bus id */
                return i2c_add_adapter(adap);
        return __i2c_add_numbered_adapter(adap);
}

int i2c_add_adapter(struct i2c_adapter *adapter)
{
        if (dev->of_node) {
                id = of_alias_get_id(dev->of_node, "i2c");
                if (id >= 0) {
                        adapter->nr = id;
                        return __i2c_add_numbered_adapter(adapter);
                }
        }
		...
        return i2c_register_adapter(adapter);
}

static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
        return i2c_register_adapter(adap);
}

#注意类型adap->dev.type = &i2c_adapter_type; 
#device_register()将adapter注册到i2c总线上
static int i2c_register_adapter(struct i2c_adapter *adap)
{
        dev_set_name(&adap->dev, "i2c-%d", adap->nr);
        adap->dev.bus = &i2c_bus_type;
        adap->dev.type = &i2c_adapter_type;
        res = device_register(&adap->dev)
}


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

        down_read(&__i2c_board_lock);
        #从__i2c_board_list链表上获取一个devinfo信息结构体,i2c_register_board_info()添加到i2c设备链表上的
        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);
}

 #注释 client->dev.type = &i2c_client_type  client添加到总线上
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
        ...
        client->dev.parent = &client->adapter->dev;
        client->dev.bus = &i2c_bus_type;
        #注释 client->dev.type = &i2c_client_type  client添加到总线上
        client->dev.type = &i2c_client_type;
        client->dev.of_node = info->of_node;
        client->dev.fwnode = info->fwnode;

}
//带有device
int device_register(struct device *dev)
{
        device_initialize(dev);
        return device_add(dev);
}
int device_add(struct device *dev)
{
        dev = get_device(dev);
		bus_probe_device(dev);//开始寻找设备所对应的驱动
 }
//为设备找到一个驱动  
void bus_probe_device(struct device *dev)  
{  
    struct bus_type *bus = dev->bus;//获得设备的隶属的总线,该值在设备初始化时设置  
    if (bus->p->drivers_autoprobe) {  
        ret = device_attach(dev);
    }  
}  

int device_attach(struct device *dev)  
{  
    if (dev->driver) {//如果设备已经有驱动  
    } else {//设备没有驱动  
        pm_runtime_get_noresume(dev);  
        ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);-------遍历总线上的driver链表-------  
        pm_runtime_put_sync(dev);  
    }  
}  
//__device_attach 函数作为参数
static int __device_attach(struct device_driver *drv, void *data)  
{  
    if (!driver_match_device(drv, dev))//设备和驱动是否匹配函数,
        return 0;  
    return driver_probe_device(drv, dev);//设备和驱动匹配成功,调用probe函数
}- 
int driver_probe_device(struct device_driver *drv, struct device *dev)  
{  
    ret = really_probe(dev, drv);//继续调用really_probe函数
}  

static int really_probe(struct device *dev, struct device_driver *drv)  
{  
    dev->driver = drv;//匹配好后,将驱动信息记录到设备内部  
    if (dev->bus->probe) {//如果总线存在probe函数,则调用总线的probe函数  
        ret = dev->bus->probe(dev);  

    } else if (drv->probe) {  
        ret = drv->probe(dev);//如果总线中没有probe函数,则调用驱动的probe函数  
    }  
}  

总结:
在上一个总结中,内核中的设备与驱动已经绑定了,执行probe函数,在此函数中,注册了adatper结构体,将 adapter注册到 i2c_bus_type。这个过程还需要再深入理解下

i2c_imx_algo 包含 I2C1 适配器与 I2C 设备的通信函数 master_xfer

static struct i2c_algorithm i2c_imx_algo = {
	.master_xfer = i2c_imx_xfer,
	.functionality = i2c_imx_func,
};
static int i2c_imx_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, int num)
{
        struct imx_i2c_struct *i2c_imx = i2c_get_adapdata(adapter);

        /* Start I2C transfer */
        result = i2c_imx_start(i2c_imx);
        if (result)
		{
                if (msgs[i].flags & I2C_M_RD)
                        result = i2c_imx_read(i2c_imx, &msgs[i], is_lastmsg);
                else {
                        if (i2c_imx->dma && msgs[i].len >= DMA_THRESHOLD)
                                result = i2c_imx_dma_write(i2c_imx, &msgs[i]);
                        else
                                result = i2c_imx_write(i2c_imx, &msgs[i]);
                }
                if (result)
                        goto fail0;
        }

fail0:
        /* Stop I2C transfer */
	i2c_imx_stop(i2c_imx);
}

总结:I2C时序读写信息

  1. 调用 i2c_imx_start 函数开启 I2C 通信
  2. 调用 i2c_imx_stop 函数停止 I2C 通信
  3. 如果是从 I2C 设备读数据的话就调用 i2c_imx_read 函数。
  4. 向 I2C 设备写数据,如果要用 DMA 的话就使用 i2c_imx_dma_write 函数来完成写数据。如果不使用 DMA 的话就使用 i2c_imx_write 函数完成写数据

那么i2c_imx_xfer何时被调用?
在驱动向adapter发生信息的时候,i2c_transfer 函数被调用的时候

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
 	if (adap->algo->master_xfer) {
 	}
}

5、I2C框架,开发人员需要做什么?

5.1、I2C数据通信方式

核心函数:i2c_transfer 函数。最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数。

int i2c_transfer(struct i2c_adapter *adap,
						struct i2c_msg *msgs,
						int num)

adap: 所使用的 I2C 适配器, i2c_client 会保存其对应的 i2c_adapter。
msgs: I2C 要发送的一个或多个消息。
num: 消息数量,也就是 msgs 的数量

i2c_msg 结构体

struct i2c_msg {
	__u16 addr; /* 从机地址 */
	__u16 flags; /* 标志 */
	#define I2C_M_TEN 0x0010
	#define I2C_M_RD 0x0001
	#define I2C_M_STOP 0x8000
	#define I2C_M_NOSTART 0x4000
	#define I2C_M_REV_DIR_ADDR 0x2000
	#define I2C_M_IGNORE_NAK 0x1000
	#define I2C_M_NO_RD_ACK 0x0800
	#define I2C_M_RECV_LEN 0x0400
	__u16 len; /* 消息(本 msg)长度 */
	__u8 *buf; /* 消息数据 */
 };

1)读取数据示例

static int xxx_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;		/* I2C 器件地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;		/* I2C 器件地址 */
	msg[1].flags = I2C_M_RD;		/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

因为 I2C 读取数据的时候要先发送要读取的寄存器地址,然后再读取数据,所以需要准备两个 i2c_msg。一个用于发送寄存器地址,一个用于读取寄存器值。

msg[0],将 flags 设置为 0,表示写数据
msg[1],将 flags 设置为 I2C_M_RD,表示读取数据

2)写数据示例

static int xxx_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/*I2C设备地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

I2C 写操作要比读操作简单一点,一个 i2c_msg 即可。
1) 数组 b 用于存放寄存器首地址和要发送的数据
2) len 为 len+1 加上一个字节的寄存器地址


5.2、I2C 设备驱动编写

以上的知识只有在做SOC开发的时候,才会去考虑。所使用的芯片,基本都已经帮我们做好了。驱动开发人员只需要编写 i2c_driver 与 i2c_client

原理图:
在这里插入图片描述

一、I2C 设备信息描述(i2c_client)

1、未使用设备树
在未使用设备树的时候需要在 BSP 里面使用 i2c_board_info 结构体来描述一个具体的 I2C 设备.

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

type 和 addr 这两个成员变量是必须要设置的,一个是 I2C 设备的名字,一个是 I2C 设备的
器件地址

static struct i2c_board_info mx27_3ds_i2c_camera = {
		I2C_BOARD_INFO("ov2640", 0x30),
};

I2C_BOARD_INFO 是一个宏,定义如下

#define I2C_BOARD_INFO(dev_type, dev_addr) \
            .type = dev_type, .addr = (dev_addr)

2、使用设备树

1、IO 修改或添加
设置管脚复用,打开 imx6ull-alientek-emmc.dts

pinctrl_i2c1: i2c1grp {
	fsl,pins = <
	MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
	MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
	>;
};

pinctrl_i2c1 就是 I2C1 的 IO 节点,这里将 UART4_TXD 和 UART4_RXD 这两个 IO 分别
复用为 I2C1_SCL 和 I2C1_SDA,电气属性都设置为 0x4001b8b0。

2、在 i2c1 节点追加 ap3216c 子节点

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";
	
	ap3216c@1e {
	compatible = "alientek,ap3216c";
	reg = <0x1e>;
	 };
};
  1. ap3216c 子节点,@后面的“1e”是 ap3216c 的器件地址
  2. 设置 compatible 值为“alientek,ap3216c”;
  3. reg 属性也是设置 ap3216c 器件地址的,因此 reg 设置为 0x1e
  4. I2C地址在手册中查找,手册中的摘抄如下:
    I2C Slave Address
    The slave addresses have 7 bits. A read/write bit should be appended to the slave address by the master device to properly communicate with the device. The slave address of this device is 0x1E

二、i2c_driver 驱动模板:

/* AP3216C操作函数 */
static const struct file_operations xxx_ops = {
        .owner = THIS_MODULE,
        .open = xxx_open,
        .read = xxx_read,
        .release = xxx_release,
};
//读取函数
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
}

static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	//注册ops读写功能
	
}
static const struct i2c_device_id xxx_id[] = {
        {"alientek,ap3216c", 0},
        {}
};

/* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
        { .compatible = "alientek,ap3216c" },
        { /* Sentinel */ }
};

/* i2c驱动结构体 */
static struct i2c_driver xxx_driver = {
        .probe = xxx_probe,
        .remove = xxx_remove,
        .driver = {
                        .owner = THIS_MODULE,
                        .name = "ap3216c",
                        .of_match_table = xxx_of_match,
                   },
        .id_table = ap3216c_id,
};

static int __init xxx_init(void)
{
        int ret = 0;

        ret = i2c_add_driver(&xxx_driver);
        return ret;
}

static void __exit xxx_exit(void)
{
        i2c_del_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

分析一下i2c_add_driver

i2c_add_driver实现关系很复杂,函数调用关系

 i2c_add_driver 
 		i2c_register_driver
 		    driver_register(&i2c_driver->driver),
				 bus_add_driver(struct device_driver *drv)
				 		driver_attach(struct device_driver *drv) 
				 			   bus_for_each_dev(drv->bus, NULL, drv, __driver_attach)
				 			        __driver_attach
				 			            driver_probe_device(structdevice_driver *drv, struct device *dev) 
				 			                 really_probe

重点看一下后面的两个函数

int driver_probe_device(structdevice_driver *drv, struct device *dev) 
{ 
	//step1:match
	if (drv->bus->match && !drv->bus->match(dev, drv)) 
	   goto done; 
	//step2:probe
	ret = really_probe(dev, drv); 
} 
//执行probe函数
static int really_probe(struct device *dev, struct device_driver *drv) 
{ 
//调用的驱动所属总线的probe函数:
    if (dev->bus->probe) { 
        ret = dev->bus->probe(dev); 
    }  
//调用的驱动中的probe函数: 
    else if (drv->probe) { 
        ret = drv->probe(dev); 
    } 
} 

2、根据上面的模板,实现AP3216C 驱动编写(driver)重点实现

实例如下
手册中的命令码:
在这里插入图片描述
头文件ap3216creg.h信息根据手册中的地址定义

#define AP3216C_ADDR    	0X1E	/* AP3216C地址  */

/* AP3316C寄存器 */
#define AP3216C_SYSTEMCONG	0x00	/* 配置寄存器       */
#define AP3216C_INTSTATUS	0X01	/* 中断状态寄存器   */
#define AP3216C_INTCLEAR	0X02	/* 中断清除寄存器   */
#define AP3216C_IRDATALOW	0x0A	/* IR数据低字节     */
#define AP3216C_IRDATAHIGH	0x0B	/* IR数据高字节     */
#define AP3216C_ALSDATALOW	0x0C	/* ALS数据低字节    */
#define AP3216C_ALSDATAHIGH	0X0D	/* ALS数据高字节    */
#define AP3216C_PSDATALOW	0X0E	/* PS数据低字节     */
#define AP3216C_PSDATAHIGH	0X0F	/* PS数据高字节     */
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/i2c.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "ap3216creg.h"

#define AP3216C_CNT	1
#define AP3216C_NAME	"ap3216c"

struct ap3216c_dev {
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	struct device_node	*nd; /* 设备节点 */
	int major;			/* 主设备号 */
	void *private_data;	/* 私有数据 */
	unsigned short ir, als, ps;		/* 三个光传感器数据 */
};

static struct ap3216c_dev ap3216cdev;

//读寄存器中的值,两个i2c_msg 读前先写地址flags = 0,读的时候flags = I2C_M_RD
static int ap3216c_read_regs(struct ap3216c_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* ap3216c地址 表示哪个I2C设备*/
	msg[0].flags = 0;					/* 发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* ap3216c地址 表示哪个I2C设备*/
	msg[1].flags = I2C_M_RD;			/* 读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c msg failed=%d\n",ret);
		ret = -EREMOTEIO;
	}
	return ret;
}

//写的时候比较简单,值要一个i2c_msg,数组b 寄存器地址+数据
static s32 ap3216c_write_regs(struct ap3216c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					
	memcpy(&b[1],buf,len);	
		
	msg.addr = client->addr;	/* ap3216c地址 表示哪个I2C设备*/
	msg.flags = 0;				

	msg.buf = b;				
	msg.len = len + 1;			/* 要写入的数据长度 + 1 */

	return i2c_transfer(client->adapter, &msg, 1);
}

//读取一个寄存器
static unsigned char ap3216c_read_reg(struct ap3216c_dev *dev, u8 reg)
{
	u8 data = 0;
	ap3216c_read_regs(dev, reg, &data, 1);
	return data;
}


 //写一个寄存器
static void ap3216c_write_reg(struct ap3216c_dev *dev, u8 reg, u8 data)
{
	u8 buf = 0;
	buf = data;
	ap3216c_write_regs(dev, reg, &buf, 1);
}

/*
 * @description	: 读取AP3216C的数据,读取原始数据,包括ALS,PS和IR, 注意!
 *				: 如果同时打开ALS,IR+PS的话两次数据读取的时间间隔要大于112.5ms

 */
void ap3216c_readdata(struct ap3216c_dev *dev)
{
	unsigned char i =0;
    unsigned char buf[6];
	
	/* 循环读取所有传感器数据 */
    for(i = 0; i < 6; i++)	
    {
        buf[i] = ap3216c_read_reg(dev, AP3216C_IRDATALOW + i);	//读取6次值,先地位后高位
    }

    if(buf[0] & 0X80) 	/* IR_OF位为1,则数据无效 */
		dev->ir = 0;					
	else 				/* 读取IR传感器的数据   buf[1]:高8位,buf[0]:低八位中取出其中的低两位		*/
		dev->ir = ((unsigned short)buf[1] << 2) | (buf[0] & 0X03); 			
	
	dev->als = ((unsigned short)buf[3] << 8) | buf[2];	/* 读取ALS传感器的数据 			 */  
	
    if(buf[4] & 0x40)	/* IR_OF位为1,则数据无效 			*/
		dev->ps = 0;    													
	else 				/* 读取PS传感器的数据    */
		dev->ps = ((unsigned short)(buf[5] & 0X3F) << 4) | (buf[4] & 0X0F); 
}

static int ap3216c_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &ap3216cdev;

	/* 初始化AP3216C */
    //AP3216C_SYSTEMCONG= 0x00 为配置功能,0x04为 软复位,0x03为激活功能
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0x04);		/* 软复位AP3216C 			*/
	mdelay(50);														/* AP3216C复位最少10ms 	*/
	ap3216c_write_reg(&ap3216cdev, AP3216C_SYSTEMCONG, 0X03);		/* 激活ALS、PS+IR 		*/
	return 0;
}


static ssize_t ap3216c_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
	short data[3];
	long err = 0;

	struct ap3216c_dev *dev = (struct ap3216c_dev *)filp->private_data;
	
	ap3216c_readdata(dev);

	data[0] = dev->ir;
	data[1] = dev->als;
	data[2] = dev->ps;
	err = copy_to_user(buf, data, sizeof(data));//读出的数据,返回给用户
	return 0;
}


static int ap3216c_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* AP3216C操作函数 ,与应用层呼应*/
static const struct file_operations ap3216c_ops = {
	.owner = THIS_MODULE,
	.open = ap3216c_open,
	.read = ap3216c_read,
	.release = ap3216c_release,
};

 //驱动与设备匹配后执行probe
static int ap3216c_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	/* 1、构建设备号 */
	if (ap3216cdev.major) {
		ap3216cdev.devid = MKDEV(ap3216cdev.major, 0);
		register_chrdev_region(ap3216cdev.devid, AP3216C_CNT, AP3216C_NAME);
	} else {
		alloc_chrdev_region(&ap3216cdev.devid, 0, AP3216C_CNT, AP3216C_NAME);
		ap3216cdev.major = MAJOR(ap3216cdev.devid);
	}

	/* 2、注册设备 */
	cdev_init(&ap3216cdev.cdev, &ap3216c_ops);//ops 操作函数
	cdev_add(&ap3216cdev.cdev, ap3216cdev.devid, AP3216C_CNT);

	/* 3、创建类 */
	ap3216cdev.class = class_create(THIS_MODULE, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.class)) {
		return PTR_ERR(ap3216cdev.class);
	}

	/* 4、创建设备 */
	ap3216cdev.device = device_create(ap3216cdev.class, NULL, ap3216cdev.devid, NULL, AP3216C_NAME);
	if (IS_ERR(ap3216cdev.device)) {
		return PTR_ERR(ap3216cdev.device);
	}

	ap3216cdev.private_data = client;//i2c_client 结构体

	return 0;
}

//注销
static int ap3216c_remove(struct i2c_client *client)
{

	cdev_del(&ap3216cdev.cdev);
	unregister_chrdev_region(ap3216cdev.devid, AP3216C_CNT);

	device_destroy(ap3216cdev.class, ap3216cdev.devid);
	class_destroy(ap3216cdev.class);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct i2c_device_id ap3216c_id[] = {
	{"alientek,ap3216c", 0},  
	{}
};

//设备树匹配列表 ,compatible与设备树的一致
static const struct of_device_id ap3216c_of_match[] = {
	{ .compatible = "alientek,ap3216c" },
	{ /* Sentinel */ }
};

//i2c驱动结构体 
static struct i2c_driver ap3216c_driver = {
	.probe = ap3216c_probe,
	.remove = ap3216c_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "ap3216c",
		   	.of_match_table = ap3216c_of_match, //设备树匹配
		   },
	.id_table = ap3216c_id,
};
		   
//入口函数
static int __init ap3216c_init(void)
{
	int ret = 0;

    //添加i2c驱动
	ret = i2c_add_driver(&ap3216c_driver);
	return ret;
}

//出口函数
static void __exit ap3216c_exit(void)
{
	i2c_del_driver(&ap3216c_driver);
}

module_init(ap3216c_init);
module_exit(ap3216c_exit);
MODULE_LICENSE("GPL");

数据处理理解
在这里插入图片描述
在读取数据时,根据条件判断位,和高低八位,将数据组合在一起。
例如在读取ir数据时,data[0]最低两位有效,data[1]为数据高8位

所以ir= data[1] <<2 | data[0]&0x03 ;
同理 als = data[3]<<8 | data[2];
ps = data[5]<<4 | data[3]&0x0F;


3)应用程序编写:

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
	int fd;
	char *filename;
	unsigned short databuf[3];
	unsigned short ir, als, ps;
	int ret = 0;

	if (argc != 2) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if(fd < 0) {
		printf("can't open file %s\r\n", filename);
		return -1;
	}

	while (1) {
		ret = read(fd, databuf, sizeof(databuf));
		if(ret == 0) { 			/* 数据读取成功 */
			ir =  databuf[0]; 	/* ir传感器数据 */
			als = databuf[1]; 	/* als传感器数据 */
			ps =  databuf[2]; 	/* ps传感器数据 */
			printf("ir = %d, als = %d, ps = %d\r\n", ir, als, ps);
		}
		usleep(200000); /*100ms */
	}
	close(fd);	/* 关闭文件 */	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/WANGYONGZIXUE/article/details/116463790