linux-I2c驱动

参考文档:

https://blog.csdn.net/wq690968346/article/details/50921039(i2c_client的四中方法)

https://blog.csdn.net/pengliang528/article/details/78262441

1:最新的I2c模型:设备,总线,驱动模型。

分层模型(1):核心层:提供统一的I2C接口操作函数,提供了总线驱动和设备驱动的注册,注销方法,i2c的通信方式,与具体适配器无关的代码以及探测设备

(2):适配器:基于板卡的I2c的硬件操作。基于某个芯片的硬件操作。

bus:I2C_bus_type提供了注册接口函数。

I2c总线层驱动(device):i2c_clinet  注册: i2c_new_device  结构体:i2c_adapter(i2c_board_info适配器数据结构体),i2c_algorithm和控制i2c适配器产生通信信号的函数。是对I2c硬件体系结构中适配器的实现,适配器可由CPU控制,或者直接集成在CPU内部,我们可以控制i2c适配器产生开始位,停止位,以及读写周期,以及以从设备方式读写,产生ACK。

驱动层(driver):i2c_driver   注册:i2c_add_driver() 结构体:i2c_driver   比较(i2c_device_id)

比较如果名称相同,则调用i2c_driver   的probe函数

第一层:i2c adapter的硬件驱动,探测,初始化i2c_adapter(申请i2c的io地址和中断号),驱动soc控制控制adapter产生的硬件信号以及I2c中断。

第二层:提供i2c_adapter的algorithm,用适配器xxx_xferf()填充i2c_algorithm的master_xfer函数指针,并把i2c_algorithm赋给algo指针,覆盖图中的抽象层,i2c核心层。

第三层:i2c_driver接口,用具体的i2c device设备的attach_adapter()、detach_adapter()方法赋值给i2c_driver的成员函数指针。实现设备device与总线(或者叫adapter)的挂接

第四层:具体device的驱动,i2c_driver只是实现设备与总线的挂接,所以要实现具体设备device的write()、read()、ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)

与之相关的c文件:i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。

所有的i2c设备都在sysfs文件系统中显示,存在于sys/bus/i2c目录下,以适配器地址和芯片地址形式列出。

i2c设备在内核原代码中的位置linux-3.10.y\drivers\i2c

目录下的功能的简单介绍:

i2c-core:i2c的核心功能以及/proc/bus/i2c接口

algos文件夹:实现一些i2c总线适配器的通信方式。有四个主要结构体

i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。I2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,每个i2c适配器分配一个设备,应用程序通过i2c-%d文件名并使用文件操作接口等来访问这个设备。应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c.
algos文件夹实现了一些I2C总线适配器的algorithm.实现一些i2c总线适配器的通信方式。有四个主要结构体i2c_adapter,i2c_algorithm,i2c_driver,i2c_client结构体

先看一些结构体:(从下到上)

i2c的主机操作驱动主要聚集在这一部分上。

  • struct i2c_algorithm {  //i2c传输
  •     int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);//I2C传输函数指针  
  •     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传输函数指针  
  •     u32 (*functionality) (struct i2c_adapter *);//返回适配器支持的功能  
  • };
  • struct i2c_adapter {   //i2c物理上的适配器,一个适配器上有多个i2c_client ,所以i2c_adapter中包含依附于它的i2c_clinet
  • 的链表。
  •  struct module *owner;//所属模块  
  •  unsigned int id;//algorithm的类型,定义于i2c-id.h,  
  •  unsigned int class;      
  •  const struct i2c_algorithm *algo; //总线通信方法结构体指针  
  •  void *algo_data;//algorithm数据  
  •  struct rt_mutex bus_lock;//控制并发访问的自旋锁  
  •  int timeout;     
  •  int retries;//重试次数  
  •  struct device dev; //适配器设备   
  •  int nr;  
  •  char name[48];//适配器名称  
  •  struct completion dev_released;//用于同步  
  •  struct list_head userspace_clients;//client链表头  
  • }; 
  • struct i2c_client {   //对应真是的i2c的物理设备,每个i2c设备都要有一个i2c-client来描述
  •  unsigned short flags;//标志    
  •  unsigned short addr; //低7位为芯片地址    
  •  char name[I2C_NAME_SIZE];//设备名称  
  •  struct i2c_adapter *adapter;//依附的i2c_adapter  
  •  struct i2c_driver *driver;//依附的i2c_driver   
  •  struct device dev;//设备结构体    
  •  int irq;//设备所使用的结构体    
  •  struct list_head detected;//链表头  
  •  };
  • struct i2c_driver {  //与i2c_client 是一对多,一个i2c_driver 可以支持多个同等类型的i2c_client 
  • unsigned int class;  
  • int (*attach_adapter)(struct i2c_adapter *);//依附i2c_adapter函数指针  
  • int (*detach_adapter)(struct i2c_adapter *);//脱离i2c_adapter函数指针  
  • int (*probe)(struct i2c_client *, const struct i2c_device_id *);  
  • int (*remove)(struct i2c_client *);  
  • void (*shutdown)(struct i2c_client *);  
  • int (*suspend)(struct i2c_client *, pm_message_t mesg);  
  • int (*resume)(struct i2c_client *);  
  • void (*alert)(struct i2c_client *, unsigned int data);  
  • int (*command)(struct i2c_client *client, unsigned int cmd, void*arg);//命令列表  
  • struct device_driver driver;  
  • const struct i2c_device_id *id_table;//该驱动所支持的设备ID表  
  • int (*detect)(struct i2c_client *, struct i2c_board_info *);  
  • const unsigned short *address_list;  
  • struct list_head clients;  
  • };
  • struct i2c_msg {
        __u16 addr;    /* slave address            */
        __u16 flags;
    #define I2C_M_TEN        0x0010    /* this is a ten bit chip address */
    #define I2C_M_RD        0x0001    /* read data, from slave to master */
    #define I2C_M_STOP        0x8000    /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NOSTART        0x4000    /* if I2C_FUNC_NOSTART */
    #define I2C_M_REV_DIR_ADDR    0x2000    /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_IGNORE_NAK    0x1000    /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_NO_RD_ACK        0x0800    /* if I2C_FUNC_PROTOCOL_MANGLING */
    #define I2C_M_RECV_LEN        0x0400    /* length will be first received byte */
    #define I2C_M_16BIT_REG        0x0002    /* indicate reg bit-width is 16bit */
    #define I2C_M_16BIT_DATA    0x0008    /* indicate data bit-width is 16bit */
        __u16 len;        /* msg length                */
        __u8 *buf;        /* pointer to msg data            */
    };

搞清楚以上四个结构体:i2c驱动差不多就能搞清楚。

i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方式,一个i2c适配器需要i2c_algorithm提供的通信函数来控制适配器产生特定的访问周期,(所以说i2c_algorithm很重要)。

i2c_algorithm到的关键函数是master_xfer()用于i2c访问周期的需要的信号,以i2c_msg为单位,i2c_msg结构体很重要,表明了i2c的传输方向,传输地址,缓冲区,缓冲长度等信息。

i2c_driver对应一套驱动方式,主要成员函数probe,remove,suspend,然后根据i2c——device_id的形式的id_table是该驱动所支持i2c设备的id表。

i2c_client对应于真是的物理设备,每个i2c 设备都需要一个i2c_clinet来描述,i2c_driver与i2c_clinet的关系是一对多,

i2c_clinet的信息通畅在bsp的板文件中通过i2c_board_info填充,

主要函数

i2c_transfer()只是用来找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正的驱动硬件流程

s32 i2c_smbus_read_byte_data(const struct i2c_client *client, u8 command)从i2cbus中读取字节。

  • //增加/删除i2c_adapter  
  • int i2c_add_adapter(struct i2c_adapter *adapter)  
  • int i2c_del_adapter(struct i2c_adapter *adap) 
  • //增加/删除i2c_driver  
  • int i2c_register_driver(struct module *owner, struct i2c_driver *driver)  
  • void i2c_del_driver(struct i2c_driver *driver)
  • //i2c_client依附/脱离  
  • int i2c_attach_client(struct i2c_client *client)  
  • //I2C传输,发送和接收  
  • int i2c_master_send(struct i2c_client *client,const char *buf ,int count)  //用于时序比较简单的send
  • int i2c_master_recv(struct i2c_client *client, char *buf ,int count)  //用于时序比较简单的写
  • int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 用于i2c 是额脾气和i2c设备之间的一组消息交互,该函数一次可以传输多个i2c_msg,本身不具备驱动硬件适配器以完成消息交互的能力,只是寻找到与i2c_adapter对应的i2c_algorithm,并使用master-xfer函数真正驱动硬件流程。

下面通过简单的实力分析一个i2c 设备:

i2c控制器本身连接在platform总线上platform_driver和platform_device匹配来执行,

一般我们会在platform_driver的probe中完成几个工作:初始化i2c适配器的硬件资源(申请io,中断号,时钟等)

通过i2c_add_adapter添加i2c_adapter的数据结构体。

i2c适配器驱动的注册过程

static int hi_i2c_probe(struct platform_device *pdev)
{
    int errorcode;
    struct hi_i2c *pinfo;
    struct i2c_adapter *adap;
    struct resource *mem;
    struct hi_platform_i2c *platform_info;

    platform_info =
        (struct hi_platform_i2c *)pdev->dev.platform_data;
    if (platform_info == NULL) {
        dev_err(&pdev->dev, "%s: Can't get platform_data!\n",
                __func__);
        errorcode = -EPERM;
        goto i2c_errorcode_na;
    }

    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (mem == NULL) {
        dev_err(&pdev->dev, "Get I2C mem resource failed!\n");
        errorcode = -ENXIO;
        goto i2c_errorcode_na;
    }

    pinfo = kzalloc(sizeof(struct hi_i2c), GFP_KERNEL);
    if (pinfo == NULL) {
        dev_err(&pdev->dev, "Out of memory!\n");
        errorcode = -ENOMEM;
        goto i2c_errorcode_na;
    }

    pinfo->regbase = (unsigned char __iomem *)IO_ADDRESS(mem->start);
    pinfo->mem = mem;
    pinfo->dev = &pdev->dev;
    pinfo->pdata = platform_info;
    pinfo->g_last_dev_addr = 0;

    hi_i2c_hw_init(pinfo);

    platform_set_drvdata(pdev, pinfo);

    adap = &pinfo->adap;
    i2c_set_adapdata(adap, pinfo);
    adap->owner = THIS_MODULE;
    adap->class = platform_info->i2c_class;
    strlcpy(adap->name, pdev->name, sizeof(adap->name));
    adap->algo = &hi_i2c_algo; //用来分配和注册i2c的传输函数
    adap->dev.parent = &pdev->dev;
    adap->nr = pdev->id;
    adap->retries = CONFIG_HI_I2C_RETRIES;
    errorcode = i2c_add_numbered_adapter(adap);
    if (errorcode) {
        dev_err(&pdev->dev,
                "%s: Adding I2C adapter failed!\n", __func__);
        goto i2c_errorcode_free_irq;
    }

    dev_notice(&pdev->dev,
            "Hisilicon [%s] probed!\n",
            dev_name(&pinfo->adap.dev));

    goto i2c_errorcode_na;

i2c_errorcode_free_irq:
    free_irq(pinfo->irq, pinfo);
    kfree(pinfo);

i2c_errorcode_na:
    return errorcode;
}

//接下来分析底层的i2c的硬件传输函数

static int hi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,        int num)
{
    struct hi_i2c *pinfo;
    int errorcode;

   pinfo = (struct hi_i2c *)i2c_get_adapdata(adap);

    pinfo->msgs = msgs;
    pinfo->msg_num = num;
    pinfo->msg_index = 0;

    if (msgs->flags & I2C_M_RD)
        errorcode = hi_i2c_read(pinfo);
    else
        errorcode = hi_i2c_write(pinfo);

    return errorcode;
}

static const struct i2c_algorithm hi_i2c_algo = {
    .master_xfer    = hi_i2c_xfer,
    .functionality  = hi_i2c_func,
};

writel() 往内存映射的 I/O 空间上写数据,wirtel()  I/O 上写入 32 位数据 (4字节)。

接下来的读写函数就是基于板卡硬件平台的i2c驱动操作。

i2c设备驱动:i2c设备驱动使用i2c_driver和i2c_client数据结构体并且填充i2c_driver,我们县定义一个初始化额i2c_driver;

static struct i2c_driver xxx_i2c_driver = {
    .driver = {
        .name = "xxx",
    },
    .probe    = xxx_probe,
    .remove   = xxx_remove,
    .id_table = xxx_id,
};

i2c设备驱动模块的加载:通过核心函数i2c_add_driverde api函数添加i2c_driver的工作

在i2c设备上读写数据的时序且数据通过i2c_msg数组进行组织,最后通过i2c_transfer函数完成

linux/drivers/i2c/busses/i2c-hisilicon.c下的基于hi3536设备的模块的加载(此文件夹下是i2c的总线驱动程序)

接下来我们分析一个设备驱动程序:

关于i2c的设备和驱动的匹配

设备驱动不依赖具体的cpu和i2c控制器我的硬件特性,如果某一电路板包含该外设,只需在板级文件中添加对应的i2c_board_info

在支持设备树的情况下,简单的在.dts文件中添加一个节点即可。

i2c_new_dummy->i2c_new_device:创建一个i2c设备,用来绑定一个驱动模块。

设备驱动编写

i2c_client:的信息一般是在BSP的板文件中通过i2c_board_info填充,例如定义i2c的设备id,地址信息,中断号等信息,每个i2c设备都需要一个i2c_client来描述,对应的是真实的物理设备,在i2c总线驱动i2c_bus_type的match()函数i2c_driver_match()中,会调用i2c_match_id()函数匹配板文件中的定义的iD和i2c_driver所支持的ID表。

常用的i2c_client设置

eg1:static struct i2c_board_info mini2440_i2c_devs[] __initdata = { { /* 遇到与”24c08一样的名称”的驱动就会与之绑定,0x50是I2C设备的地址 */ I2C_BOARD_INFO("24c08", 0x50), .platform_data = &at24c08, }, }; /* 这里的0代表:i2c-0总线 */ i2c_register_board_info(0, mini2440_i2c_devs, ARRAY_SIZE(mini2440_i2c_devs));
使用i2c_register_board_info去实例化必须知道我们使用的I2C设备是挂载到哪个总线上,并知道设备的地址。

在Linux启动的时候会将信息进行收集,i2c适配器会扫描已经静态注册的i2c_board_info,通过调用i2c_register_board_info函数将包含所有I2C设备的i2c_board_info信息的i2c_devinfo变量加入到__i2c_board_list链表中,并调用i2c_new_device为其实例化一个i2c_client。在驱动加载的时候遇到同名的i2c_board_info就会将i2c_client和driver绑定,并且执行driver的probe函数。

i2c_new_device用来创建i2c_client实例化。

方法二:/*@后面是设备的起始地址*/

&i2c-0@fe { /* i2c_client的name = "hall-i2c" */

compatible = "qcom, hall-i2c";

reg = <fe>; interrupts = <70>; /* 如果设置成disabled,在初始化的时候就不会被实例化,可以在linux内置文档查看更多 */

status = "disabled"; };

其中:i2c-0中的0是总线编号,reg是设备地址,interrupts是中断号。

在初始化的时候i2c总线会调用qup_i2c_probe(),接着调用of_i2c_register_devices对dtsi上所描述的设备进行实例化。并创建相应的sys文件:sys/bus/i2c/devices/0-00fe。

用于创建i2c_client实例化的方法连接:

https://blog.csdn.net/lugandong/article/details/48092397

主要看对于sysfs创建的二进制属性文件如何在应用程序中调用:

https://www.linuxidc.com/Linux/2013-10/91993p14.htm

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值