【硬件1】platform/i2c总线

news2024/10/7 10:21:27

文章目录

  • 1.platform总线:相对于USB/PCI/I2C/SPI等物理总线来说,platform总线是一种虚拟的总线,实际并不存在,总线的工作就是就是完成总线下的设备和驱动之间的匹配。也就是在左手中找到与右手相匹配的设备驱动,并完成他们之间的匹配
    • 1.1 of_device_id + of_match_table设备树采用的匹配方式:设备树中的每个设备节点的compatible属性会和 of_match_table表中的所有成员比较,查看是否有相同的条目,有的话就表示设备和此驱动匹配,设备和驱动匹配成功后probe函数会执行
    • 1.2 id_table匹配:id_table不存在的话就直接比较驱动和设备name字段
  • 2.I2C协议:总线空闲的时SCL和SDA处于高电平。 I2C总线标准模式下速度可达100Kb/S,快速模式下可达400Kb/S
    • 2.1 I2C读时序(i2cget):sda线上ack和data是slave发,其他都是master发
    • 2.2 I2C写时序(i2cset):主机通过I2C总线与从机之间进行通信只有写和读,sda线上只有ack是slave发,其他都是master发
  • 3.I2C驱动:两不同的IIC设备挂在同一个IIC控制器下,给这两个设备编写驱动程序部分代码重复,因此linux将IIC驱动分层
    • 3.1 I2C总线&适配器:每一组i2c总线对应一个i2c控制器即i2c_adapter,一个芯片可有多个控制器
    • 3.2 I2C驱动&设备:一个设备对应一个i2c_client,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client
  • 4.I2C设备添加:系统中对i2c_client的注册是在i2c_adapter的注册过程中完成
    • 4.1 用户空间注册:删除设备只能删除在用户空间创建的i2c设备
    • 4.2 静态注册:i2c_board_info结构体或dts来配置硬件信息,调用i2c_register_board_info函数来向设备链表来注册硬件设备,然后系统在注册i2c_adapter完成后,取出链表中的每一个设备,当设备的i2c的编号和i2c_adapter相同时,在调用device_register对此设备注册
      • 案例:i2c_new_device(不管i2c设备是否真的存在,都实例化i2c_client),i2c_new_probed_device(调用probe函数去探测i2c地址是否有回应,存在则实例化i2c_client)
    • 4.3 动态注册:前面都事先指定适配器(i2c设备挂到哪一条i2c总线上),动态要配合设备驱动,硬件信息写到设备驱动中


1.platform总线:相对于USB/PCI/I2C/SPI等物理总线来说,platform总线是一种虚拟的总线,实际并不存在,总线的工作就是就是完成总线下的设备和驱动之间的匹配。也就是在左手中找到与右手相匹配的设备驱动,并完成他们之间的匹配

// include/linux/device.h,  Linux系统内核使用bus_type结构体表示总线,  Linux系统中文件的体现形式是在/sys/bus下有spi文件夹里有device和driver文件夹
struct bus_type {
	const char		*name;								/* 总线名字 */
	const char		*dev_name;
	struct device		*dev_root;
	struct device_attribute	*dev_attrs;					/* use dev_groups instead */
	const struct attribute_group **bus_groups;			/* 总线属性 */
	const struct attribute_group **dev_groups;			/* 设备属性 */
	const struct attribute_group **drv_groups;			/* 驱动属性 */
	int (*match)(struct device *dev, struct device_driver *drv);
	int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
	int (*probe)(struct device *dev);
	int (*remove)(struct device *dev);
	void (*shutdown)(struct device *dev);
	int (*online)(struct device *dev);
	int (*offline)(struct device *dev);
	int (*suspend)(struct device *dev, pm_message_t state);
	int (*resume)(struct device *dev);
	const struct dev_pm_ops *pm;
	const struct iommu_ops *iommu_ops;
	struct subsys_private *p;
	struct lock_class_key lock_key;
};

// drivers/base/platform.c
struct bus_type platform_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);
	/* When driver_override is set, only bind to the matching driver */
	if (pdev->driver_override)
		return !strcmp(pdev->driver_override, drv->name);

	// 如下4种设备和驱动匹配方式:
	/* 1. Attempt an OF style match first */
	if (of_driver_match_device(dev, drv))
		return 1;
		
	/* 2. Then try ACPI style match */
	// 从device中找到acpi_device设备后就可匹配了,of_driver_match_device没有匹配到,则用这种
	if (acpi_driver_match_device(dev, drv))  
		return 1;
		
	/* 3. Then try to match against the id table */
	if (pdrv->id_table)
		return platform_match_id(pdrv->id_table, pdev) != NULL;
		
	/* 4. fall-back to driver name match */
	return (strcmp(pdev->name, drv->name) == 0);
}

platform总线管理下的platform_device和platform_driver(任何一种Linux设备驱动模型下的总线都由这两个部分组成)。

// include/linux/platform_device.h
struct platform_device {   
	const char	*name;
	int		id;
	bool		id_auto;
	struct device	dev;
	u32		num_resources;
	struct resource	*resource;
	const struct platform_device_id	*id_entry;
	char *driver_override; /* Driver name to force a match */
	/* MFD cell pointer */
	struct mfd_cell *mfd_cell;
	/* arch specific additions */
	struct pdev_archdata	archdata;
};

struct platform_driver { 
	int (*probe)(struct platform_device *); /// 当驱动与设备匹配成功以后 probe 函数就会执行
	int (*remove)(struct platform_device *);
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;   device_driver基类下面定义
	const struct platform_device_id *id_table;  // id_table是个表(也就是数组),每个元素的类型为 platform_device_id,下面定义
	bool prevent_deferred_probe;
};

//struct platform_device_id {
//	char name[PLATFORM_NAME_SIZE];
//	kernel_ulong_t driver_data;
//};

1.1 of_device_id + of_match_table设备树采用的匹配方式:设备树中的每个设备节点的compatible属性会和 of_match_table表中的所有成员比较,查看是否有相同的条目,有的话就表示设备和此驱动匹配,设备和驱动匹配成功后probe函数会执行

// include/linux/of_device.h
static inline int of_driver_match_device(struct device *dev,   // platform_match函数中调用
					 const struct device_driver *drv)  device_driver下面定义
{
	return of_match_device(drv->of_match_table, dev) != NULL; 
}

struct device_driver { //基类,platform_driver继承了这个基类
	const char		*name;
	struct bus_type		*bus;
	struct module		*owner;
	const char		*mod_name;	/* used for built-in modules */
	bool suppress_bind_attrs;	/* disables bind/unbind via sysfs */
	const struct of_device_id	*of_match_table;   of_device_id下面定义	
	const struct acpi_device_id	*acpi_match_table;
	int (*probe) (struct device *dev);
	int (*remove) (struct device *dev);
	void (*shutdown) (struct device *dev);
	int (*suspend) (struct device *dev, pm_message_t state);
	int (*resume) (struct device *dev);
	const struct attribute_group **groups;
	const struct dev_pm_ops *pm;
	struct driver_private *p;
};

//struct of_device_id {  //include/linux/mod_devicetable.h
//	 char name[32];
//	 char type[32];
//	 char compatible[128];
//	 const void *data;
//};

在这里插入图片描述
of_device_id不是i2c_device_id,dts中匹配如下:/sys/bus/i2c/devices/3-0050下产生eeprom节点,psu用24c02。
在这里插入图片描述

1.2 id_table匹配:id_table不存在的话就直接比较驱动和设备name字段

在这里插入图片描述

2.I2C协议:总线空闲的时SCL和SDA处于高电平。 I2C总线标准模式下速度可达100Kb/S,快速模式下可达400Kb/S

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.1 I2C读时序(i2cget):sda线上ack和data是slave发,其他都是master发

在这里插入图片描述
1.主机发送起始信号。
2.主机发送要读取的I2C从设备地址。#######
3.读写控制位,因为是向I2C从设备发送数据,因此是写信号。
4.从机发送的ACK应答信号。
5.重新发送START信号。
6.主机发送要读取的寄存器地址。 #######
7.从机发送的 ACK 应答信号。
8.重新发送START信号。
9.重新发送要读取的I2C从设备地址。########比写时序多出的
10.读写控制位,这里是读信号,表示接下来是从I2C从设备里面读取数据。
11.从机发送的ACK应答信号。
12.从I2C器件里面读取到的数据。#######
13.主机发出NO ACK信号,表示读取完成,不需要从机再发送ACK信号了。
14.主机发出STOP信号,停止I2C通信。
逻辑分析仪一个接SDA,一个接SCL,一个接地。
在这里插入图片描述

2.2 I2C写时序(i2cset):主机通过I2C总线与从机之间进行通信只有写和读,sda线上只有ack是slave发,其他都是master发

在这里插入图片描述
1.开始信号。
2.发送I2C设备地址,每个I2C器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个I2C器件。这是一个8位的数据,其中高7位是设备地址,最后1位(设备地址不用管)是读写位。 ####### i2cget等用的7位,不用偏移
3.I2C器件地址后面跟着一个读写位,为0表示写操作,为1表示读操作。
4.从机发送的ACK应答信号。
5.重新发送开始信号。
6.发送要写写入数据的寄存器地址(也叫command)。 ########
7.从机发送的ACK应答信号
8.发送要写入寄存器的数据。 #######多字节读写在这多发数据
9.从机发送的ACK应答信号。
10.停止信号。

3.I2C驱动:两不同的IIC设备挂在同一个IIC控制器下,给这两个设备编写驱动程序部分代码重复,因此linux将IIC驱动分层

3.1 I2C总线&适配器:每一组i2c总线对应一个i2c控制器即i2c_adapter,一个芯片可有多个控制器

在这里插入图片描述

// 设备数据结构i2c_client中的一个对象指向i2c_adapter,这样就可以通过其中的方法(i2c_adapter内部的i2c_algorithm对象)以及i2c物理控制器来和一个i2c总线的物理设备进行交互。
struct i2c_adapter {
    struct module *owner;//所有者
    unsigned int class;                  /*适配器支持的从设备类型 */
    const struct i2c_algorithm *algo; /* 适配器的通信方法,也就是下面的i2c_algorithm */
    void *algo_data;                    //algo私有数据
    int timeout;                        /* 超时时间 */
    int retries;                       //重传次数,在通信方法中使用
    struct device dev;                 /* 表明这是一个设备,挂载在I2C总线上 */
    int nr;                            //适配器(总线)的编号
    char name[48];                     //适配器名称
    struct list_head userspace_clients; //IIC设备的client依附链表头,只有在用户层echo name addr > /sys/bus/i2c/devices/i2c-x/new_device 时候创建的client才会依附在此链表//省略部分无关成员
};

struct i2c_algorithm {
    int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,   int num);    //I2C传输函数指针,i2c_transfer函数的底层调用
    int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr,   unsigned short flags, char read_write,   u8 command, int size, union i2c_smbus_data *data);    //smbus传输函数指针,SMBUS协议发送函数,i2c_smbus_xxx函数的底层调用
    u32 (*functionality) (struct i2c_adapter *);    /* 这个IIC适配器支持什么样的功能,比如支持SMBUS字节发送或读取操作,标志位为I2C_FUNC_SMBUS_BYTE_DATA */
};


// i2c总线(/sys/bus/i2c)注册I2C驱动,将I2C驱动添加到I2C总线的驱动链表中:  遍历I2C总线上的设备链表,根据i2c_device_match函数进行匹配,如果匹配调用i2c_device_probe函数,i2c_device_probe函数会调用I2C驱动的probe函数
struct bus_type i2c_bus_type = { // I2C总线结构体在linux-aspeed/drivers/i2c/i2c-core-base.c
	.name		= "i2c",  //总线的名称
	.match		= i2c_device_match,   //
	.probe		= i2c_device_probe,   //
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
};

3.2 I2C驱动&设备:一个设备对应一个i2c_client,每检测到一个I2C设备就会给这个I2C设备分配一个i2c_client

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 *);    //当前移除设备或驱动时执行的移除函数,一般是释放资源
    int (*probe_new)(struct i2c_client *);    ///未来匹配成功后的执行函数
    void (*shutdown)(struct i2c_client *);    //关闭设备
    void (*alert)(struct i2c_client *, enum i2c_alert_protocol protocol,unsigned int data);
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
    struct device_driver driver;    /device_driver基类,如果使用设备树的话,需要设置device_driver的of_match_table 成员变量,也就是驱动的兼容(compatible)属性。
    const struct i2c_device_id *id_table;  /id_table是传统未使用设备树的设备匹配ID表
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;    //该驱动所支持的所有(次设备)的地址数组,用于注册驱动时自动去匹配
    struct list_head clients;    //用来挂接与该i2c_driver匹配成功的i2c_client (次设备)的一个链表头
    bool disable_i2c_core_irq_mapping;
};


struct i2c_client {  // 一般通过i2c_new_device()创建
    unsigned short flags;          // I2C_CLIENT_TEN表示设备使用10bit从地址,I2C_CLIENT_PEC表示设备使用SMBus检错
    unsigned short addr;           /* IIC设备基地址,7bit。这里说一下为什么是7位,因为最后1位0表示写,1表示读,通过对这个7bit地址移位处理即可。addr<<1 & 0x0即写,addr<<1 | 0x01即读, 对应上面的I2C协议 */
    char name[I2C_NAME_SIZE];      // iic设备名字,用于匹配iic驱动
    struct i2c_adapter *adapter;   /* iic设备是挂在哪个适配器(总线)上 */
    struct device dev;             /* 表明这是一个设备,挂在I2C总线上 */
    int irq;                       /* 中断号,用于申请中断,一般是在自己实现的iic驱动的probe函数中使用*/
    struct list_head detected;     //是i2c_adapter结构体或i2c_driver结构体中链表的节点
};

struct i2c_board_info { // 基本与i2c_client对应
    char   type[I2C_NAME_SIZE];  //设备名,最长20个字符,最终安装到client的name上
    unsigned short    flags;     //最终安装到client.flags
    unsigned short    addr;      //设备从地址slave address,最终安装到client.addr上
    void        *platform_data;  //设备数据,最终存储到i2c_client.dev.platform_data上
    struct dev_archdata    *archdata;
    struct device_node *of_node; //OpenFirmware设备节点指针
    struct acpi_dev_node acpi_node;
    int        irq;              //设备采用的中断号,最终存储到i2c_client.irq上
};

4.I2C设备添加:系统中对i2c_client的注册是在i2c_adapter的注册过程中完成

4.1 用户空间注册:删除设备只能删除在用户空间创建的i2c设备

root@bmc:/sys/bus/i2c/devices/i2c-0# echo 0xd > delete_device //删除bus0上0x0d设备,里面0-000d文件夹被删除
root@bmc:/sys/bus/i2c/devices/i2c-0# rmmod basecpld  //驱动,这行lsmod查看不到,如果这行比上行先执行,0-000d文件夹还在,但里面节点没了

root@bmc:/sys/bus/i2c/devices/i2c-0# insmod ~/basecpld.ko    //驱动
root@bmc:/sys/bus/i2c/devices/i2c-0# echo basecpld 0xd > new_device  //设备,将ko放到对应路径(/lib/modules/4.1.51/extra/basecpld.ko),这行执行后,不用上行insmod

root@bmc:/sys/bus/i2c/drivers/lm75# ls   // lm75是驱动名即ko名, a.ko则a就是驱动名 , lsmod查看
37-004a  54-0049  55-0048  7-0048   7-0049   7-004b   bind     module   uevent   unbind
root@bmc:/sys/bus/i2c/drivers# ls  // 和上面/sys/bus/i2c/devices/i2c-0 区分设备
ads7828           fcbcpld           lm75              ocmcpld           rtc-ds3232
at24              fpga_driver       maccpld           pca954x           rtc-pcf85063
basecpld          i2c-slave-mqueue  max1363           psu_driver        tmp401
dummy             ina3221           net_brcm          pxe1410           xdpe12284

在这里插入图片描述

4.2 静态注册:i2c_board_info结构体或dts来配置硬件信息,调用i2c_register_board_info函数来向设备链表来注册硬件设备,然后系统在注册i2c_adapter完成后,取出链表中的每一个设备,当设备的i2c的编号和i2c_adapter相同时,在调用device_register对此设备注册

如下是没有dts:arm架构board-xxx-yyy.c(架构板级)文件或x86架构下xxx-yyy-init-zzz.c(初始化)文件。

static struct i2c_board_info my_tmp75_info = {
    I2C_BOARD_INFO("my_tmp75", 0x48),  // 设备名,设备地址
};

// busnum:哪一条总线,也就是选择哪一个i2c控制器(adapter)
// info:i2c设备信息,就是上面的结构体
// n:info中有几个设备
// 如下函数在linux-aspeed/drivers/i2c/i2c-boardinfo.c
i2c_register_board_info(int busnum, struct i2c_board_info const * info, unsigned n)
{
    devinfo->busnum = busnum; /* 组装i2c总线 */
    devinfo->board_info = *info; /* 绑定设备信息 */
    list_add_tail(&devinfo->list, &__i2c_board_list); /* 将上面定义的i2c设备信息添加进__i2c_board_list链表中 */
}

如下出现了dts之后,为了去耦合,将这些的板级信息全部都定义在设备树(arch/arm/boots/dts),linux在启动uboot的时会自动展开dts上的硬件信息,自动调用i2c_register_board_info函数将设备注册进设备链表中,如下第2组总线地址0x01c…(i2c总线相对于芯片的cpu而言也是设备)。
在这里插入图片描述
如上两种方式将硬件设备添加到设备链表之后,如下内核是怎么向i2c总线动态注册这些设备的:

// 如下都在linux-aspeed/drivers/i2c/i2c-core-base.c,i2c_add_numbered_adapter调用i2c_register_adapter
// 注册i2c控制器(adapter)时,将会去查找这个__i2c_board_list链表,并实例化i2c设备添加到i2c总线上
static int i2c_register_adapter(struct i2c_adapter *adap)
{
	...
	dev_set_name(&adap->dev, "i2c-%d", adap->nr); 设置adap->dev.kobj.name为i2c-x ,它将出现在sysfs中
	adap->dev.bus = &i2c_bus_type;  //此设备依附的总线类型是i2c总线类型;
	adap->dev.type = &i2c_adapter_type; //此设备的设备类型是i2c_adapter;
	res = device_register(&adap->dev); //注册i2c_adapter设备;
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);  // 下面定义
	...
}

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    struct i2c_devinfo        *devinfo;
    down_read(&__i2c_board_lock);
    list_for_each_entry(devinfo, &__i2c_board_list, list) {  // __i2c_board_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);
    }  // i2c_new_device调用i2c_new_client_device
    up_read(&__i2c_board_lock);
}

struct i2c_client *
i2c_new_client_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
	...
	client->adapter = adap; //要创建的i2c_client依附到当前的i2c_adapter控制器上
	client->addr = info->addr;  //设置设备的地址,从__i2c_board_list链表取得的信息,传参struct i2c_board_info const *info
	client->init_irq = info->irq; //中断编号默认为0,在启动设备中通过gpio_to_irq会对irq重新赋值
	strlcpy(client->name, info->type, sizeof(client->name)); //对i2c_client的名字赋值,靠这名字和驱动的id_table进行匹配
	status = device_register(&client->dev);
	...
}
EXPORT_SYMBOL_GPL(i2c_new_client_device);

案例:i2c_new_device(不管i2c设备是否真的存在,都实例化i2c_client),i2c_new_probed_device(调用probe函数去探测i2c地址是否有回应,存在则实例化i2c_client)

// 如下使用 i2c_new_device 注册设备:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
 
static struct i2c_board_info my_tmp75_info = {
    I2C_BOARD_INFO("my_tmp75", 0x48),
};
static struct i2c_client *my_tmp75_client;

static int my_tmp75_init(void)
{
    struct i2c_adapter *i2c_adapt;
    int ret = 0;
    i2c_adapt = i2c_get_adapter(6);
    if (i2c_adapt == NULL)
    {
        printk("get adapter fail!\n");
        ret = -ENODEV;
    }
    /
    my_tmp75_client = i2c_new_device(i2c_adapt, &my_tmp75_info);  // i2c_board_info 
    if (my_tmp75_client == NULL)
    {
        printk("i2c new fail!\n");
        ret = -ENODEV;
    } 
    i2c_put_adapter(i2c_adapt);
    return ret;
}
 
static void my_tmp75_exit(void)
{
    i2c_unregister_device(my_tmp75_client);
}
module_init(my_tmp75_init); 
module_exit(my_tmp75_exit); 
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("aaa");
MODULE_DESCRIPTION("This my i2c device for tmp75");
// 如下使用 i2c_new_probed_device 注册设备:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>

static struct i2c_client *my_tmp75_client;
static const unsigned short addr_list[] = { 0x46, 0x48, I2C_CLIENT_END };//必须以I2C_CLIENT_END宏结尾
 
static int my_i2c_dev_init(void)
{
    struct i2c_adapter *i2c_adap;
    struct i2c_board_info my_i2c_dev_info;     
    memset(&my_i2c_dev_info, 0, sizeof(struct i2c_board_info));   
    strlcpy(my_i2c_dev_info.type, "my_tmp75", I2C_NAME_SIZE); 
    i2c_adap = i2c_get_adapter(0);
    
    my_tmp75_client = i2c_new_probed_device(i2c_adap, &my_i2c_dev_info, addr_list, NULL);//只会匹配0x48地址
    i2c_put_adapter(i2c_adap); 
    if (my_tmp75_client)
        return 0;
    else
        return -ENODEV;
}

static void my_i2c_dev_exit(void)
{
    i2c_unregister_device(my_tmp75_client);
} 
module_init(my_i2c_dev_init);
module_exit(my_i2c_dev_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("aaa");
MODULE_DESCRIPTION("This my i2c device for tmp75");
// 驱动中probe函数
int my_driver_probe(void)
{
    struct i2c_adapter *adapter;
	struct i2c_board_info info = { };
    struct i2c_client *client;
    int ret;
    unsigned short normal_i2c[] = { 0xd, I2C_CLIENT_END };
    struct list_head *pos;
    const char *name = "ocmcpld";
    struct i2c_client **client_list;

    strlcpy(info.type, name, sizeof(info.type));
    info.addr = 0x0d;

    adapter = i2c_get_adapter(1); // 获取i2c adapter
    if (!adapter){
        printk(KERN_CRIT "Failed i2c_get_adapter\n");
        return -1;
    }

    client = i2c_new_device(adapter, &info); // 创建i2c设备
    if (!client) {
        printk(KERN_CRIT "Failed to create i2c client\n");
        return -1;
    }

    client = i2c_new_probed_device(adapter, &info, &normal_i2c, NULL); // 注掉 info.addr = 0x0d;
    if (!client) {
        printk(KERN_CRIT "Failed to find i2c client\n");
        return -1;
    }

    // client = i2c_find_by_name("ocmcpld");
    // if (!client) {
    //     printk(KERN_CRIT "Failed to find i2c client\n");
    //     return -1;
    // }

    // list_for_each(pos, adapter->client_list) {
    //     client = list_entry(pos, struct i2c_client, list);
    //     if (client->addr == 0xd && strcmp(client->name, "ocmcpld") == 0) {
    //         printk(KERN_INFO "client find\n");  //找到指定设备的i2c_client结构体
    //         break;
    //     }
    // }

    ret = i2c_smbus_read_byte_data(client, 0x00);
    if (ret < 0) {
        printk(KERN_CRIT "Read failed\n");
        return -1;
    }
    printk(KERN_INFO "ocmcpld  ret:0x%x\n",ret);
    return 0;
}

4.3 动态注册:前面都事先指定适配器(i2c设备挂到哪一条i2c总线上),动态要配合设备驱动,硬件信息写到设备驱动中

insmod .ko时会执行module_init中xxx_init入口函数会调用如下:
在这里插入图片描述

// linux-aspeed/drivers/i2c/i2c-core-base.c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
	int res;
	/* Can't register until after driver model init */
	if (WARN_ON(!is_registered))
		return -EAGAIN;
	/* add the driver to the list of i2c drivers in the driver core */
	driver->driver.owner = owner;
	driver->driver.bus = &i2c_bus_type;  //设定这个driver依附的总线
	INIT_LIST_HEAD(&driver->clients);

	/* When registration returns, the driver core
	 * will have called probe() for all matching-but-unbound devices.
	 */
	res = driver_register(&driver->driver); //这函数会调用其他函数对driver和device进行匹配,若匹配成功,i2c_register_driver返回res,结束执行;如不成功,则会往下执行i2c_for_each_dev函数(这函数接下来调用的其他函数会动态注册i2c设备)
	if (res)
		return res;
	pr_debug("driver [%s] registered\n", driver->driver.name);

	/* Walk the adapters that are already present */
	i2c_for_each_dev(driver, __process_new_driver);   //注意传参
	return 0;
}  // 至此驱动注册就完成了,注意不是设备


// int i2c_for_each_dev(void *data, int (*fn)(struct device *dev, void *data))
// {
// 	int res;
// 	mutex_lock(&core_lock);
// 	res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn); //注意最后两个传参:driver, __process_new_driver
// 	mutex_unlock(&core_lock);
// 	return res;
// }

// // 只如下这一个函数在linux-aspeed/drivers/base/bus.c中
// int bus_for_each_dev(struct bus_type *bus, struct device *start,
// 		     void *data, int (*fn)(struct device *, void *))
// {
// 	struct klist_iter i;
// 	struct device *dev;
// 	int error = 0;
// 	if (!bus || !bus->p)
// 		return -EINVAL;
// 	klist_iter_init_node(&bus->p->klist_devices, &i,
// 			     (start ? &start->p->knode_bus : NULL));  //遍历链表上的所有设备
// 	while (!error && (dev = next_device(&i))) 
// 		error = fn(dev, data); //通过next_device函数将链表上的设备dev取出然后传递给fn即__process_new_driver函数,data就是driver
// 	klist_iter_exit(&i);
// 	return error;
// }

// __process_new_driver拿到driver和device做了什么?
static int __process_new_driver(struct device *dev, void *data)
{
	if (dev->type != &i2c_adapter_type)  //判断拿到的设备是否是i2c总线设备即是不是i2c_adapter
		return 0;
	return i2c_do_add_adapter(data, to_i2c_adapter(dev));
}

static int i2c_do_add_adapter(struct i2c_driver *driver,
			      struct i2c_adapter *adap)
{
	i2c_detect(adap, driver);
	return 0;
}

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); //获取i2c_adapter设备的i2c总线号

	address_list = driver->address_list; //获取设备驱动程序中定义的address_list 
	if (!driver->detect || !address_list) //判断驱动程序中是定义了detect 函数和address_list,其中一个没定义,则失败返回0;
		return 0;

	/* Warn that the adapter lost class based instantiation */
	if (adapter->class == I2C_CLASS_DEPRECATED) {
		dev_dbg(&adapter->dev,
			"This adapter dropped support for I2C classes and won't auto-detect %s devices anymore. "
			"If you need it, check 'Documentation/i2c/instantiating-devices.rst' for alternatives.\n",
			driver->driver.name);
		return 0;
	}

    // adapter->class = 1;   // 或 = I2C_CLASS_HWMON;
	/* Stop here if the classes do not match */
	if (!(adapter->class & driver->class)) { //这里在判断驱动程序中定义的class和adapter中定义的class是否相同,不相同则失败返回0;&:有0为0
		printk("000, %d, %d\n", adapter->class , driver->class);
		return 0; } // 注意加括号

	/* Set up a temporary client to help detect callback */
	temp_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	if (!temp_client)
		return -ENOMEM;
	temp_client->adapter = adapter; //这里定义了一个i2c_client,下面的程序会对这个进行赋值,然后再注册

	for (i = 0; address_list[i] != I2C_CLIENT_END; i += 1) {
		printk("111, %d, %d, %d\n", i , address_list[i], temp_client->adapter->nr); //nr为bus号
		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);
	return err;
}

static int i2c_detect_address(struct i2c_client *temp_client,
			      struct i2c_driver *driver)
{
	struct i2c_board_info info;  //这里定义了结构体i2c_board_info,看了静态注册设备的话,对这个结构体很熟悉了,没有使用dts的时候必须要用i2c_board_info 结构体存放硬件信息,动态注册也一样,只是定义这个结构体的位置不同而已。
	struct i2c_adapter *adapter = temp_client->adapter;
	int addr = temp_client->addr;
	int err;

	/* Make sure the address is valid */
	err = i2c_check_7bit_addr_validity_strict(addr); // 检测i2c地址是否7位有效
	if (err) {
		dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n",
			 addr);
		return err;
	}

	/* Skip if already in use (7 bit, no need to encode flags) */
	if (i2c_check_addr_busy(adapter, addr))  // 跳过已经使用的i2c设备
		return 0;

	/* Make sure there is something at this address */
	if (!i2c_default_probe(adapter, addr))  // 检查这个地址是否有回应
		return 0;

	/* Finally call the custom detection function */
	memset(&info, 0, sizeof(struct i2c_board_info));  
	info.addr = addr;  // 这里把在i2c_detect函数中检测到的i2c地址赋值给了info这个结构体,并将temp_client和info一起传入了驱动程序中定义的detect函数,
	err = driver->detect(temp_client, &info);  // 驱动程序中detect函数
    if (err) {
        return err == -ENODEV ? 0 : err;
    } 
    if (info.type[0] == '\0')
    {
    }
    else
    {
        struct i2c_client *client;
        client = i2c_new_device(adapter, &info); // 驱动中的detect函数必须要填写i2c_board_info结构体中name,i2c_new_device才能实例化i2c设备
        if (client)
        	printk("222, creating %s at 0x%02x\n", info.type, info.addr);
            list_add_tail(&client->detected, &driver->clients);  // 只有这种方式添加的i2c设备才会挂在驱动的链表上
    }
}
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/platform_device.h>
 
static int __devinit my_i2c_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    return 0;
}
 
static int __devexit my_i2c_drv_remove(struct i2c_client *client)
{
    return 0;
}
 
static const struct i2c_device_id my_dev_id_table[] = {
    { "my_i2c_dev", 0 },
    {}
};//这里的名字很重要,驱动第一种匹配设备的方式要用到
 
static int my_i2c_drv_detect(struct i2c_client *client, struct i2c_board_info *info)
{
    /* 能运行到这里, 表示该addr的设备是存在的
     * 但是有些设备单凭地址无法分辨(A芯片的地址是0x50, B芯片的地址也是0x50)
     * 还需要进一步读写I2C设备来分辨是哪款芯片,自己写方法
     * detect就是用来进一步分辨这个芯片是哪一款,并且设置info->type,也就是设备名字
     */
    printk("my_i2c_drv_detect: addr = 0x%x\n", client->addr);
 
    /* 进一步判断是哪一款 */
    strlcpy(info->type, "my_i2c_dev", I2C_NAME_SIZE);
    return 0;
}
 
static const unsigned short addr_list[] = { 0x46, 0x48, I2C_CLIENT_END };//必须使用I2C_CLIENT_END宏结尾
 
/* 分配/设置i2c_driver */
static struct i2c_driver my_i2c_driver = {
    .class  = I2C_CLASS_HWMON, /* 表示去哪些适配器上找设备,不是对应类将不会调用匹配, 适配器初始化时,也会初始化class,驱动在探测设备时,会将适配器的class和i2c_driver结构体的class比较 */
    .driver        = {
        .name        = "my_i2c_dev",
        .owner        = THIS_MODULE,
    },
    .probe                = my_i2c_drv_probe, // 当I2C设备和I2C驱动匹配时,调用该函数
    .remove        = __devexit_p(my_i2c_drv_remove), // 当I2C设备移除时,调用该函数
    .id_table        = my_dev_id_table,
    .detect     = my_i2c_drv_detect,  /* 用这个函数来检测address_list里设备确实存在 ,填充设备名字,调用i2c_new_device注册i2c_client,如果i2c_board_info.type和i2c_driver.id_table.name一致调用probe函数*/
    .address_list        = addr_list,   /* 这些设备的地址 */
};
 
static int my_i2c_drv_init(void)
{
    i2c_add_driver(&my_i2c_driver); 
    return 0;
}
 
static void my_i2c_drv_exit(void)
{
    i2c_del_driver(&my_i2cc_driver);
}

module_init(my_i2c_drv_init);
module_exit(my_i2c_drv_exit);
MODULE_LICENSE("GPL");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/679653.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Java+Springboot的智能图书馆座位管理系统设计和实现

博主介绍&#xff1a;擅长Java、微信小程序、Python、Android等&#xff0c;专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案例…

MySQL实战解析底层---为什么只查一行的语句,也执行这么慢

目录 前言 第一类&#xff1a;查询长时间不返回 第二类&#xff1a;查询慢 前言 一般情况下&#xff0c;如果说查询性能优化&#xff0c;首先会想到一些复杂的语句&#xff0c;想到查询需要返回大量的数据但有些情况下&#xff0c;“查一行”&#xff0c;也会执行得特别慢这…

第11讲:BootService 核心实现解析,Agent 的“地基”原来是这样的

之前介绍了 ServiceManager 加载并初始化 BootService 实现的核心逻辑。下图展示了 BootService 接口的所有实现类&#xff0c;本课时将深入分析这些 BootService 实现类的具体逻辑&#xff1a; 网络连接管理 在前面的介绍中提到 SkyWalking Agent 会定期将收集到的 JVM 监控和…

基于Java+Swing实现雷电小游戏

基于JavaSwing实现雷电小游戏 一、系统介绍二、功能展示三、其他系统四、获取源码 一、系统介绍 基于java的雷电游戏基本功能包括&#xff1a;敌方飞机随机飞行、我方飞机手动控制飞行&#xff0c;射击比拼&#xff0c;游戏闯关等。本系统结构如下&#xff1a; &#xff08;1&…

Java中线程的创建与使用、Thread类的常用方法

1、什么是进程与线程 1.1 含义 1.1.1 进程 进程是指正在运行的程序的实例。在操作系统中&#xff0c;一个进程代表了一个正在执行的程序&#xff0c;它包括了程序的代码、数据以及程序执行时所需要的系统资源。 最直观的就是我们任务管理器&#xff1a; 任务管理器中的每一…

Centos7安装和配置Mysql5.7

第一步&#xff1a;获取mysql YUM源 进入mysql官网获取RPM包下载地址&#xff0c;下面就是mysql5.7的Yum仓库的rpm包&#xff1a; mysql5.7链接地址&#xff1a; https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm 第二步&#xff1a;下载和安装mysql…

卷福的十年同学会

1.一通电话 某个上班日的午休时间里&#xff0c;小卷正趴在办公桌玩着手机准备睡一会&#xff0c;“叮咚”&#xff0c;一条微信消息弹出来&#xff0c;是大学的班群消息。 “五一期间大家来学校聚一下吧&#xff0c;今年是我们成为同学的十年了&#xff0c;大家提前报名哦&a…

Qt设置软件启动动画(支持图片和视频俩种方式)

目录 软件启动动画效果静态背景动态背景 程序启动动画QSplashScreen启动时加载静态图片启动时加载视频动画将启动动画置于所有窗口顶层 软件启动动画效果 先来看效果。下面录制了加载图片和gif动图的俩种效果。 静态背景 动态背景 这里我加载了一个gif的动图&#xff0c;你也…

AMBA AHB的burst termination

前言 在AMBA AHB协议中&#xff0c;AHB master可以用burst传输连续取多笔数据。AHB定义了4、8和16拍的burst传输、未定义长度的burst传输和单次传输。Burst传输中支持incrementing和wrapping。 Incrementing burst用于访问顺序的memory地址&#xff0c;burst中每个拍的地址都…

数据压缩的常用手段以及方法

0. 简介 之前我们在《经典文献阅读之–R-PCC(基于距离图像的点云压缩方法)》中提到了&#xff0c;我们可以通过一些算法层面来完成数据的压缩&#xff0c;而其实更简单或者说更直接的方法就是使用half这种形式来完成数据压缩。 1. half和float Half是用16位表示浮点数的一种…

什么是 FL Studio?2023年最新版 FL Studio21.0.3.3517中文版图文安装教程

什么是 FL Studio&#xff1f; FL Studio 是一个数字音频工作站 (DAW)。该软件借助各种编辑工具、插件和效果&#xff0c;让您可以录制、混音和掌握高度复杂的音乐作品。FL Studio 还允许您注册和编辑 MIDI 文件&#xff0c;您可以在众多可用乐器之一上演奏这些文件。FL Studi…

树莓派 python3.9降级为python3.7

今天烧录了一个官方烧录器中的最新的镜像&#xff0c;打开之后python的版本是3.9的&#xff0c;之前做的一些东西都是基于python3.7的&#xff0c;再重新架构十分麻烦&#xff0c;于是干脆就把python3.9进行降级&#xff0c;降为python3.7. 这个镜像不像之前的一些镜像&#x…

通用商城项目(上)

通用型产品&#xff08;电商&#xff09;发布解决方案落地实现&#xff08;基于分布式微服务技术栈&#xff1a; SpringBootSpring CloudSpring Cloud Alibaba VueElementUl MyBatis-Plus MySQL Git Maven Linux Nginx Docker 前后端分离&#xff09; 项目技术栈和前置技术 项…

【软件设计师暴击考点】操作系统知识高频考点暴击系列【一】

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

Web网页制作-知识点(1)——HTML5介绍、HTML5的DOCTYPE声明、HTML基本骨架、标题标签、段落 换行、水平线图片图片路径、超链接

目录 HTML5介绍 HTML5的DOCTYPE声明 HTML基本骨架 标题标签 段落、换行、水平线 图片 图片路径* 超链接 HTML5介绍 HTML5是用来描述网页的一种语言&#xff0c;被称为超文本标记语言。用HTML5编写的文件&#xff0c;后缀以.html结尾 HTML是一种标记语言&#xff0c;标…

自动化神器AutoIt,告别重复劳动

概要 计算机已经进入大众家庭多年&#xff0c;它给我们带来了便利&#xff0c;却也带来了枯燥、重复、机械的重复工作。今天&#xff0c;我要和大家分享一款自动化工具AutoIt&#xff0c;它能够帮助你告别这些烦恼&#xff0c;并提高工作效率。 AutoIt 是一款完全免费的Windows…

leetcode82. 删除排序链表中的重复元素 II(java)

删除排序链表中的重复元素 leetcode82. 删除排序链表中的重复元素 II题目描述一次遍历代码演示 链表专题 leetcode82. 删除排序链表中的重复元素 II 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/remove-duplicates-fr…

1.1 编写一个简单的C++程序

博主介绍&#xff1a;爱打游戏的计算机专业学生 博主主页&#xff1a;夏驰和徐策 所属专栏&#xff1a;夏驰和徐策带你从零开始学C 1.1.0 这段话告诉我们什么&#xff1f; 这段话解释了一个C程序中的main函数的基本结构和功能。 它告诉我们以下几点&#xff1a; 1. C程序的…

Debian11 编译bluez

之前的几篇文章写过如果编译x86和dv300 版本的bluez&#xff0c;不过那都是在 Centos7 上编译的。然而当我从taobao 上买了一个蓝牙适配器后发现无法使用&#xff08;淘宝客服说不支持Centos&#xff0c;只支持ubuntu 和 debian&#xff09;。再者 Centos 现在也停止支持服务了…

头歌网页设计与制作实训答案

我这里已经看不见原题目了&#xff0c;只粘贴了有Begin和End部分的代码&#xff0c;如果题目符合但答案不符合的的&#xff0c;欢迎在评论区找我。如果有帮助&#xff0c;请赞一个。注意看目录里有没有你需要的。 目录 一、HTML——基础 1.初识HTML: 简单的Hello World网页制…