当前位置: 首页 > news >正文

​Linux·i2c驱动架构​

目录

Linux-i2c驱动架构

总线驱动

i2c设备驱动和设备


 

Linux-i2c驱动架构

       i2c驱动在Linux通过一个周的学习后发现i2c总线的驱动框架还是和Linux整体的驱动框架是相同的,思想并不特殊比较复杂的内容如i2c核心的内容都是内核驱动框架实现完成的,今天我们暂时只分析驱动开发需要完成的部分。iic驱动驱动开发主要是完成四个部分的内容,struct i2c_adapter(适配器),struct i2c_algorithm (通讯接口),i2c_driver(设备驱动),struct i2c_client(iic设备),其中适配器和通讯接口的实现一般SOC厂商都会针对自家的i2c控制器IP实现在Linux 内核驱动中,也就是可以参考。即使没有驱动开发人员也可以自己实现主要就是按照框架需求实现主要的接口函数就可以了。所以这里需要先明确一点的就是I2C驱动是分为总线驱动和设备驱动的,设备在I2C架构中由i2c_client描述,用户空间也可以直接使用总线驱动完成和总线上的设备的数据交换只是有些设备的i2c操作时序很复杂,如果在用户空间来操作i2c设备效率和软件分层上也是不符合常用习惯的。所以I2C的拓扑图大就是总线驱动,设备驱动和设备,设备和设备驱动都是挂接在具体的总线上的这是由硬件决定的,有了设备驱动对i2c设备的操作就编程读写普通文件一样容易从而降低了用户控件的程序开发的复杂程度并提高了执行效率。

总线驱动

i2c_adapter 主要是用来抽像的描述i2c控制器的,结合其中algo通讯接口部分一起组成i2c总线驱动,其中适配器具体结构体如下:

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;

    /* data fields that are valid for all devices    */
    struct rt_mutex bus_lock;

    int timeout;            /* in jiffies */
    int retries;
    struct device dev;        /* the adapter device */

    int nr;
    char name[48];
    struct completion dev_released;

    struct mutex userspace_clients_lock;
    struct list_head userspace_clients;

    struct i2c_bus_recovery_info *bus_recovery_info;
};

其中的algo 指向适配对应到的具体的硬件的操作接口封装。除此之外其中还有一些属性如retries重试次数,class 保存i2c的一些特有的属性标志。总线驱动的适配器的部分内容比较简单基本就是一些软件部分内容的实现设置;要完成i2c总线驱动部分最主要的还是硬件通讯接口的实现,这一部分是和具体的硬件相关的所以也是驱动开发的重点。i2c总线驱动将硬件操作接口抽象封装如下:

struct i2c_algorithm {
    /* If an adapter algorithm can't do I2C-level access, set master_xfer
       to NULL. If an adapter algorithm can do SMBus access, set
       smbus_xfer. If set to NULL, the SMBus protocol is simulated
       using common I2C messages */
    /* master_xfer should return the number of messages successfully
       processed, or a negative value on error */
    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 *);
};

其中master_xfer接口就是i2c主机驱动传输数据的接口,包括收和发。其中smbus_xfer 用于smbus时序发送数据。因为i2c接口和smbus是相同的有些SOC的i2c外设控制器同时支持这两种时序发送数据。最后就是functionality 接口他会返回当前总线支持的通讯协议和特性。三星的实现如下,其中具体的标志的含义可以参考具体说明文档。I2C_FUNC_I2C  指示支持I2C时序,I2C_FUNC_SMBUS_EMUL  应该是支持smbus时序。

/* declare our i2c functionality */
static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART |
        I2C_FUNC_PROTOCOL_MANGLING;
}

i2c总线驱动的的定义一步骤如下(前提是已经实现了接口封装中的各个通讯函数)

  1. 分配内存。
  2. 然后开始配置具体的成员变量。
  3. 在绑定具体的接口函数即algo。

最后就是总线驱动的注册接口了由i2c内核实现,完成向内核注册增加一个I2C总线适配器。

增加适配器

int i2c_add_adapter(struct i2c_adapter * adap);

int i2c_add_adapter(struct i2c_adapter *adapter)
{
    struct device *dev = &adapter->dev;
    int id;
   //设备树的方式添加设备  
    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);
        }
    }
    //普通将设备写在板级文件中的方式
    mutex_lock(&core_lock);
    //分配一个总线ID 这个ID有总线驱动子系统维护
    id = idr_alloc(&i2c_adapter_idr, adapter,
               __i2c_first_dynamic_bus_num, 0, GFP_KERNEL);
    mutex_unlock(&core_lock);
    if (id < 0)
        return id;

    adapter->nr = id;
    //适配器的注册,前提适配器有id
    return i2c_register_adapter(adapter);
}

整个执行流程基本分为如下的过程,如果未指定适配器id则先申请ID然后就是调用i2c_register_adapter

static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0;

    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p))) {
        res = -EAGAIN;
        goto out_list;
    }

    /* Sanity checks */
    if (unlikely(adap->name[0] == '\0')) {
        pr_err("i2c-core: Attempt to register an adapter with "
               "no name!\n");
        return -EINVAL;
    }
    if (unlikely(!adap->algo)) {
        pr_err("i2c-core: Attempt to register adapter '%s' with "
               "no algo!\n", adap->name);
        return -EINVAL;
    }

    rt_mutex_init(&adap->bus_lock);
    //这里说明用户空间是可以直接使用总线设备 来操作总线上的设备的
    mutex_init(&adap->userspace_clients_lock);
    INIT_LIST_HEAD(&adap->userspace_clients);
    //默认超时时间为一个系统心跳时间
    /* Set default timeout to 1 second if not already set */
    if (adap->timeout == 0)
        adap->timeout = HZ;
    //前面申请的Id,所以说ID是必须的
    dev_set_name(&adap->dev, "i2c-%d", adap->nr);
    //i2c_bus_type  i2c_adapter_type 都是内核实现的一些操作的封装
    adap->dev.bus = &i2c_bus_type;
    adap->dev.type = &i2c_adapter_type;
    //和普通的设备注册相同,有个疑问就是驱动何时注册的?
    res = device_register(&adap->dev);
    if (res)
        goto out_list;

    dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);
    //兼容性相关
#ifdef CONFIG_I2C_COMPAT
    res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
                       adap->dev.parent);
    if (res)
        dev_warn(&adap->dev,
             "Failed to create compatibility class link\n");
#endif
    //总线恢复相关
    /* bus recovery specific initialization */
    if (adap->bus_recovery_info) {
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;

        if (!bri->recover_bus) {
            dev_err(&adap->dev, "No recover_bus() found, not using recovery\n");
            adap->bus_recovery_info = NULL;
            goto exit_recovery;
        }

        /* Generic GPIO recovery */
        if (bri->recover_bus == i2c_generic_gpio_recovery) {
            if (!gpio_is_valid(bri->scl_gpio)) {
                dev_err(&adap->dev, "Invalid SCL gpio, not using recovery\n");
                adap->bus_recovery_info = NULL;
                goto exit_recovery;
            }

            if (gpio_is_valid(bri->sda_gpio))
                bri->get_sda = get_sda_gpio_value;
            else
                bri->get_sda = NULL;

            bri->get_scl = get_scl_gpio_value;
            bri->set_scl = set_scl_gpio_value;
        } else if (!bri->set_scl || !bri->get_scl) {
            /* Generic SCL recovery */
            dev_err(&adap->dev, "No {get|set}_gpio() found, not using recovery\n");
            adap->bus_recovery_info = NULL;
        }
    }

添加的过程删除了异常处理的部分,执行过程大致就是先初始化适配器中的核心成员包括超时,设备链表,指定总线和设备类型后注册适配器设备。剩下的就是总线恢复的一些内容可以暂时不看。除此之外上面还发现一个问题就是添加总线适配器的过程发现不知道在何时添加的总线适配器驱动。根据前面对Linux设备注册过程的了解这里先看一下设备注册过程的绑定的总线type中的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;

    if (!client)
        return 0;

    /* 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;
}

发现他在不使用设备树的情况下是通过驱动的id_table来完成设备和驱动的匹配的。继续找最后找到如下的代码在drivers\i2c\i2c-core.c 中:

static int __init i2c_init(void)
{
    int retval;

    retval = bus_register(&i2c_bus_type);
    if (retval)
        return retval;
#ifdef CONFIG_I2C_COMPAT
    i2c_adapter_compat_class = class_compat_register("i2c-adapter");
    if (!i2c_adapter_compat_class) {
        retval = -ENOMEM;
        goto bus_err;
    }
#endif
    retval = i2c_add_driver(&dummy_driver);
    if (retval)
        goto class_err;
    return 0;

class_err:
#ifdef CONFIG_I2C_COMPAT
    class_compat_unregister(i2c_adapter_compat_class);
bus_err:
#endif
    bus_unregister(&i2c_bus_type);
    return retval;
}


/* We must initialize early, because some subsystems register i2c drivers
 * in subsys_initcall() code, but are linked (and initialized) before i2c.
 */
postcore_initcall(i2c_init);

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

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

    /* Can't register until after driver model init */
    if (unlikely(WARN_ON(!i2c_bus_type.p)))
        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;
    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);
    if (res)
        return res;

    /* Drivers should switch to dev_pm_ops instead. */
    if (driver->suspend)
        pr_warn("i2c-core: driver [%s] using legacy suspend method\n",
            driver->driver.name);
    if (driver->resume)
        pr_warn("i2c-core: driver [%s] using legacy resume method\n",
            driver->driver.name);

    pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);
    //如果设备先注册,则这里负责匹配
    /* Walk the adapters that are already present */
    i2c_for_each_dev(driver, __process_new_driver);

    return 0;
}
//设备注册过程的一个关键代码
static int __device_attach(struct device_driver *drv, void *data)
{
    struct device *dev = data;
    /* 如果总线有定义mach函数则 调用总线的mach函数 这个接口由总线提供  */
    if (!driver_match_device(drv, dev))
        return 0;
    /* 前面的匹配未成功 进一步探测*/
    return driver_probe_device(drv, dev);
}
 

上面的代码注册了一个虚拟的I2C设备,他的作用就是产生对应的总线设备如i2c-0,i2c-1等,也就完善了Linux的驱动框架的内容有了设备(I2C适配器)和驱动(dummy_driver)。适配器设备注册时会先执行总线的match接口(红色部分代码)也就是上面的i2c_device_match

接口,再看底下虚拟驱动的定义可以知道总线的匹配会失败最终执行的就是驱动自己的probe接口这里就是driver_probe_device dummy_probe接口。

static const struct i2c_device_id dummy_id[] = {
    { "dummy", 0 },
    { },
};

static int dummy_probe(struct i2c_client *client,
               const struct i2c_device_id *id)
{
    return 0;
}

static int dummy_remove(struct i2c_client *client)
{
    return 0;
}

static struct i2c_driver dummy_driver = {
    .driver.name    = "dummy",
    .probe        = dummy_probe,
    .remove        = dummy_remove,
    .id_table    = dummy_id,
};

删除适配器

void i2c_del_adapter(struct i2c_adapter * adap)

void i2c_del_adapter(struct i2c_adapter *adap)
{
    struct i2c_adapter *found;
    struct i2c_client *client, *next;

    /* First make sure that this adapter was ever added */
    mutex_lock(&core_lock);
    found = idr_find(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);
    if (found != adap) {
        pr_debug("i2c-core: attempting to delete unregistered "
             "adapter [%s]\n", adap->name);
        return;
    }

    acpi_i2c_remove_space_handler(adap);
    /* Tell drivers about this removal */
    mutex_lock(&core_lock);
    bus_for_each_drv(&i2c_bus_type, NULL, adap,
                   __process_removed_adapter);
    mutex_unlock(&core_lock);

    /* Remove devices instantiated from sysfs */
    mutex_lock_nested(&adap->userspace_clients_lock,
              i2c_adapter_depth(adap));
    list_for_each_entry_safe(client, next, &adap->userspace_clients,
                 detected) {
        dev_dbg(&adap->dev, "Removing %s at 0x%x\n", client->name,
            client->addr);
        list_del(&client->detected);
        i2c_unregister_device(client);
    }
    mutex_unlock(&adap->userspace_clients_lock);

    /* Detach any active clients. This can't fail, thus we do not
     * check the returned value. This is a two-pass process, because
     * we can't remove the dummy devices during the first pass: they
     * could have been instantiated by real devices wishing to clean
     * them up properly, so we give them a chance to do that first. */
    device_for_each_child(&adap->dev, NULL, __unregister_client);
    device_for_each_child(&adap->dev, NULL, __unregister_dummy);

#ifdef CONFIG_I2C_COMPAT
    class_compat_remove_link(i2c_adapter_compat_class, &adap->dev,
                 adap->dev.parent);
#endif

    /* device name is gone after device_unregister */
    dev_dbg(&adap->dev, "adapter [%s] unregistered\n", adap->name);

    /* wait until all references to the device are gone
     *
     * FIXME: This is old code and should ideally be replaced by an
     * alternative which results in decoupling the lifetime of the struct
     * device from the i2c_adapter, like spi or netdev do. Any solution
     * should be throughly tested with DEBUG_KOBJECT_RELEASE enabled!
     */
    init_completion(&adap->dev_released);
    device_unregister(&adap->dev);
    wait_for_completion(&adap->dev_released);

    /* free bus id */
    mutex_lock(&core_lock);
    idr_remove(&i2c_adapter_idr, adap->nr);
    mutex_unlock(&core_lock);

    /* Clear the device structure in case this adapter is ever going to be
       added again */
    memset(&adap->dev, 0, sizeof(adap->dev));
}

基本的执行过程和设备的删除类似,但是增加了驱动的卸载,因为有些驱动是绑定到到要删除的适配器上的,适配器已经删除了自然需要删除相关设备。剩下的两部分就是i2c具体的设备驱动和设备的相关内容了。

i2c设备驱动和设备

      不通过具体的设备驱动也是可以访问i2c设备,但是应用层就需要处理i2c设备驱动本应该处理的内容,但是此时用户空间的代码的读写写逻辑的比较复杂需要考虑设备的读写时序的问题了,这样就不符合驱动的分层考虑了。不通过具体设备直接当问i2c总线上的设备需要使用i2c核心定义一个结构体来完成对i2c具体操作的定义那就是struct i2c_msg,因为通过上面的i2c总线驱动的通讯接口部分的int(master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,int num)接口可以看出来i2c的数据传输过程就是以struct i2c_msg为单位的,这个结构在用户空间也是可以使用的,所以用户可以直接通过总线完成和总线上挂接的设备进行数据交互(有条件的)。这里先解释正常情况下的设备操作方式---通过对应的设备驱动来操作设备,而设备驱动实际上最后也是调用的适配器的algo 句柄指向的master_xfer接口来实现数据传输。i2c设备的注册在系统的板级文件中实现通过struct i2c_board_info 结构进行定义然后通过i2c_register_board_info 进行注册需要注意的是设备的添加要遭遇驱动的注册,因为阅读源码发现设备添加时未进行驱动匹配代码如下

int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;

    down_write(&__i2c_board_lock);

    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)
        __i2c_first_dynamic_bus_num = busnum + 1;

    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);
    }

    up_write(&__i2c_board_lock);

    return status;
}

然后就是struct i2c_board_info和struct i2c_msg的定义了如下:

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];
    unsigned short    flags;
    unsigned short    addr;
    void        *platform_data;
    struct dev_archdata    *archdata;
    struct device_node *of_node;
    struct acpi_dev_node acpi_node;
    int        irq;
};

这里可以参考后面的struct i2c_client 的结构进行分析。

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 */
    __u16 len;        /* msg length                */
    __u8 *buf;        /* pointer to msg data            */
};

很简单就三个成员,然后再来看i2c设备驱动的部分。

i2c设备驱动就是针对一个特定的设备定义一种操作接口由用户接口通过vfs接口调用从而完成对指定设备的读写操作,读写寄存器或存储空间的内容。他的定义如下

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;

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    /* driver model interfaces that don't relate to enumeration  */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);

    /* Alert callback, for example for the SMBus alert protocol.
     * The format and meaning of the data value depends on the protocol.
     * For the SMBus alert protocol, there is a single bit of data passed
     * as the alert response's low bit ("event flag").
     */
    void (*alert)(struct i2c_client *, unsigned int data);

    /* a ioctl like command that can be used to perform specific functions
     * with the device.
     */
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;
    const struct i2c_device_id *id_table;

    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};

看起来是不是和设备驱动很相似,确实这也是linux内核驱动框架的抽象意义。如果熟悉前面的platform_driver的初始化和添加流程这里也是类似的。基本上也是用户完成一些接口的实现然后绑定到指定的驱动成员上最后调用驱动注册接口。如下这是我自己实现的At24cxx系列eeprom的i2c设备驱动。

static struct i2c_driver eerom_driver = {
    .driver = {
        .name    = "at24xx",
    },
    .id_table    = at24c0xx_idtable,
    .probe        = at24c0xx_probe,
    .remove        = at24c0xx_remove,
};

int __init eeprom_i2c_driver_init(void)
{
    int retval;

    retval=i2c_add_driver(&eerom_driver);
    if(retval)
    {
        printk(KERN_INFO "eeprom_i2c_driver_init fail\n");
    }
    return 0;
}

重点是驱动和设备的匹配操作这里和platform驱动和设备的match还是有一定的的差别的,通过i2c_add_driver 添加设备驱动到i2c总线,所有的i2c适配器都在同一个总线,由设备信息确定设备在哪一个适配器上。当驱动注册后会执行总线的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;

    if (!client)
        return 0;

    /* 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;
}

通过代码可以发现匹配规则除了类似platform总线的设备树和acpi模式外只支持id_table的方式匹配设备。match完成匹配后就会执行i2c总线的探测函数探测驱动和设备。总线的probe函数最后还是会调用driver的probe接口完成设备的探测。

static int i2c_device_probe(struct device *dev)
{
    struct i2c_client    *client = i2c_verify_client(dev);
    struct i2c_driver    *driver;
    int status;

    if (!client)
        return 0;

    driver = to_i2c_driver(dev->driver);
    if (!driver->probe || !driver->id_table)
        return -ENODEV;

    if (!device_can_wakeup(&client->dev))
        device_init_wakeup(&client->dev,
                    client->flags & I2C_CLIENT_WAKE);
    dev_dbg(dev, "probe\n");

    acpi_dev_pm_attach(&client->dev, true);
    status = driver->probe(client, i2c_match_id(driver->id_table, client));
    if (status)
        acpi_dev_pm_detach(&client->dev, true);

    return status;
}

 具体的probe接口就可以定义软件接口进行一系列的接口配置将用户的vfs接口映射到i2c总线的读写。下面拿我自己实现的一个eeprom的读写驱动接口实例来分析。主要看probe接口

#define  BUFF_SIZE          64
#define  EEPROM_PAGE_SIZE   8
#define  LEN_GET(index,len)    \
        (((index+EEPROM_PAGE_SIZE)<(len))?(EEPROM_PAGE_SIZE):(len-index))

enum type
{ 
    at24c01x,
    at24c02x,
    at24c04x,
    at24c08x,
    at24c16x,
};

enum mem_size
{ 
    AT24C01=(1<<10),
    AT24C02=(2<<10),
    AT24C04=(4<<10),
    AT24C08=(8<<10),
    AT24C16=(16<<10),
};

struct eeprom_chip {
    struct miscdevice    devmisc;
    struct i2c_client    *client;
    struct iic_ops      *ops;
    struct device       *dev;
    struct mutex        eeprom_mutex;
    char                buf[BUFF_SIZE];
    enum mem_size       memsize;
    uint8_t             is_open;
    kernel_ulong_t      eeprom_type;

};

struct iic_ops{
    int (*send)(struct eeprom_chip *, size_t,short);
    int (*recv)(struct eeprom_chip *, size_t,short);
};

static struct i2c_device_id at24c0xx_idtable[] = {

    { "24c04x", at24c04x},

    { "24c08x",at24c08x},

    { "24c16x",at24c16x},

    { }

};

 

static struct i2c_driver eerom_driver = {

    .driver = {

        .name   = "at24xx",

    },

    .id_table   = at24c0xx_idtable,

    .probe      = at24c0xx_probe,

    .remove     = at24c0xx_remove,

};

 

probe接口的实现主要是通过增加中间层使用了杂项设备驱动,将vfs的读写接口传递到i2c总线上的读写接口从而实现读写eeprom就如同读写一个普通文件一样简单,从而简化了用户空间的调用逻辑。

static int at24c0xx_probe(struct i2c_client *client,
                  const struct i2c_device_id *id)
{
    int rc;
    struct eeprom_chip *eeprom;
    struct device *dev = &client->dev;
    /* check function*/
    if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
        return -ENODEV;
    eeprom = devm_kzalloc(&client->dev,sizeof(struct eeprom_chip), GFP_KERNEL);
    if (eeprom == NULL) {
        printk(KERN_INFO "failed to create our eeprom_data\n");
        return -ENOMEM;
    }
    eeprom->eeprom_type = id->driver_data;
    eeprom->dev=get_device(dev);
    eeprom->client = client;
    eeprom->ops = &iic_ops;
    eeprom->devmisc.fops = &eeprom_ops;
    eeprom->devmisc.minor = 223;
    eeprom->devmisc.name=client->name;
    eeprom->devmisc.parent = eeprom->dev;
    eeprom->memsize = get_eeprom_memsize(eeprom->eeprom_type);
    // printk(KERN_INFO "I2C:at24c0x:%d byte\n",eeprom->memsize);
    mutex_init(&eeprom->eeprom_mutex);
    rc = misc_register(&eeprom->devmisc);
    if (rc) {
        printk(KERN_INFO "%s client created misc_register Fail\n",client->name);
    }
    i2c_set_clientdata(client, eeprom);
    /* rest of the initialisation goes here. */
    // printk(KERN_INFO "%s client created\n",client->name);

    return 0;
}

全部驱动代码,设备就是Linux3-16-57版本内核的三星的smdkv210的板级文件添加的修改了一下兼容属性,从而使当前驱动可以匹配上。

  1 #include <linux/module.h>
  2 #include <linux/kernel.h>
  3 #include <linux/device.h>
  4 #include <linux/miscdevice.h>
  5 #include <linux/delay.h>
  6 #include <linux/errno.h>
  7 #include <linux/gpio.h>
  8 #include <linux/slab.h>
  9 #include <linux/i2c.h>
 10 #include <linux/init.h>
 11 #include <linux/fcntl.h>
 12 #include <linux/io.h>
 13 #include <linux/fs.h>
 14 #include <linux/types.h>
 15 #include <linux/mutex.h>
 16 #include <linux/completion.h>
 17 #include <linux/hardirq.h>
 18 #include <linux/irqflags.h>
 19 #include <linux/rwsem.h>
 20 #include <linux/pm_runtime.h>
 21 #include <asm/uaccess.h>
 22 
 23 #define  BUFF_SIZE          64
 24 #define  EEPROM_PAGE_SIZE   8
 25 #define  LEN_GET(index,len)    \
 26         (((index+EEPROM_PAGE_SIZE)<(len))?(EEPROM_PAGE_SIZE):(len-index))
 27 
 28 enum type
 29 { 
 30     at24c01x,
 31     at24c02x,
 32     at24c04x,
 33     at24c08x,
 34     at24c16x,
 35 };
 36 
 37 enum mem_size
 38 { 
 39     AT24C01=(1<<10),
 40     AT24C02=(2<<10),
 41     AT24C04=(4<<10),
 42     AT24C08=(8<<10),
 43     AT24C16=(16<<10),
 44 };
 45 
 46 struct eeprom_chip {
 47     struct miscdevice    devmisc;
 48     struct i2c_client    *client;
 49     struct iic_ops      *ops;
 50     struct device       *dev;
 51     struct mutex        eeprom_mutex;
 52     char                buf[BUFF_SIZE];
 53     enum mem_size       memsize;
 54     uint8_t             is_open;
 55     kernel_ulong_t      eeprom_type;
 56 
 57 };
 58 
 59 struct iic_ops{
 60     int (*send)(struct eeprom_chip *, size_t,short);
 61     int (*recv)(struct eeprom_chip *, size_t,short);
 62 };
 63 
 64 static enum mem_size get_eeprom_memsize(enum type type)
 65 {
 66     enum mem_size mem_size;
 67     switch(type)
 68     {
 69         case at24c01x:
 70             mem_size = AT24C01;
 71             break;
 72         case at24c02x:
 73             mem_size = AT24C02;
 74             break;
 75         case at24c04x:
 76             mem_size = AT24C04;
 77             break;
 78         case at24c08x:
 79             mem_size = AT24C08;
 80             break;
 81         case at24c16x:
 82             mem_size = AT24C16;
 83             break;
 84         default:
 85             mem_size = AT24C01;
 86             break;
 87     }
 88     return mem_size;
 89 }
 90 
 91 static short get_device_addr(struct eeprom_chip  *chip,short pos)
 92 {
 93     short  addr_dataaddr;
 94     struct i2c_client *client = chip->client;
 95     addr_dataaddr =client->addr;
 96 
 97     if(chip->eeprom_type <= at24c04x)
 98     {
 99         addr_dataaddr =addr_dataaddr|(pos&0x100);
100     }else if (chip->eeprom_type == at24c08x){
101         addr_dataaddr =addr_dataaddr|(pos&0x300);
102     }else if (chip->eeprom_type == at24c16x){
103         addr_dataaddr =addr_dataaddr|(pos&0x700);
104     }
105     return addr_dataaddr;
106 }
107 static int i2c_eeprom_write(struct eeprom_chip *chip, size_t len,short pos)
108 {
109     char   w_buf[10];
110     int    status,i=0,ret=0;
111     short  page_cnt,write_index=0;
112     short  align_pos = ((pos+EEPROM_PAGE_SIZE-1)/8)*8;
113     struct i2c_client *client = chip->client;
114     struct  i2c_msg msg={
115         .flags = I2C_M_NOSTART,
116         .buf = w_buf,
117     };
118     if(align_pos == pos){
119         page_cnt = ((len+EEPROM_PAGE_SIZE-1)/8);
120     }else{
121         page_cnt = 1+((len-(align_pos-pos)+EEPROM_PAGE_SIZE-1)/8);
122     }
123     for(i=0;i<page_cnt;i++)
124     {
125         if((align_pos != pos)&&!i){
126             msg.len = align_pos-pos+1;
127         }else{
128             msg.len = LEN_GET(write_index,len)+1;
129         }
130         msg.addr =get_device_addr(chip,pos+write_index);
131         w_buf[0] = pos+write_index;
132         memcpy(&w_buf[1],&chip->buf[write_index],msg.len);
133         write_index += msg.len-1;
134         // w_buf[msg.len] = '\0';
135         // printk(KERN_INFO "%02x\n",w_buf[0]);
136         // printk(KERN_INFO "%s\n",&w_buf[1]);
137         status = i2c_transfer(client->adapter,&msg,1);
138         if(status)
139         {
140             ret+=msg.len-1;
141         }
142         mdelay(1);
143     }
144     memset(chip->buf,0,BUFF_SIZE);
145     return ret;
146 }
147 
148 static int i2c_eeprom_read(struct eeprom_chip *chip, size_t count,short pos)
149 {
150     int rc;
151     u8 data_addr = 0xFF&pos;
152     struct i2c_client *client = chip->client;
153     
154     struct i2c_msg  msg[]={
155         [0]={
156             .addr = get_device_addr(chip,pos),
157             .flags = client->flags & I2C_M_TEN,
158             .len = 1,
159             .buf = &data_addr,
160         },
161         [1]={
162             .addr = get_device_addr(chip,pos),
163             .flags = I2C_M_RD,
164             .len = count,
165             .buf = chip->buf,
166         },
167     };
168     rc= i2c_transfer(client->adapter,msg,2);
169     if(rc==2)
170         return count;
171     return 0;
172 }
173 
174 static int eeprom_open(struct inode *inode,struct file *filp)
175 {
176     struct miscdevice *misc = filp->private_data;
177     struct eeprom_chip* chip = container_of(misc, struct eeprom_chip,
178                          devmisc); 
179     // printk(KERN_INFO "eeprom_open\n");                  
180     if(chip->is_open){
181         // printk(KERN_INFO "eeprom_open busy\n"); 
182         return -EBUSY; 
183     }
184     filp->private_data =  chip;
185     chip->is_open = 1;
186     return 0;
187 }
188 
189 static ssize_t eeprom_read(struct file* filp,char* __user buf,size_t cnt,loff_t *offt)
190 {
191     int ret,count;
192     loff_t new_pos = *offt +cnt;
193     struct eeprom_chip* chip = filp->private_data;
194     // printk(KERN_INFO "eeprom_read\n");
195     if(new_pos>chip->memsize)
196     {
197         // printk(KERN_INFO "eeprom_open mem\n"); 
198         return -ENOMEM; 
199     }
200     if(cnt > BUFF_SIZE)
201     {
202         // printk(KERN_INFO "eeprom_open mem\n");
203         return -ENOMEM; 
204     }
205     // printk(KERN_INFO "position is %ld\n",(size_t)*offt);
206     mutex_lock(&chip->eeprom_mutex);
207     count=chip->ops->recv(chip,cnt, *offt);
208     // printk(KERN_INFO "chip->ops->recv return %d\n",count);
209     ret = copy_to_user((void*)buf,(void*)chip->buf,count);
210     if(ret){
211         return -EIO;
212     }
213     mutex_unlock(&chip->eeprom_mutex);
214     *offt += count;
215     return count;
216 }
217 
218 static ssize_t eeprom_write(struct file* filp,const char*__user  buf,size_t cnt ,loff_t *offt)
219 {
220     int ret;
221     loff_t new_pos = *offt +cnt; 
222     struct eeprom_chip* chip = filp->private_data;
223     // printk(KERN_INFO "eeprom_write\n");
224     if(new_pos>chip->memsize)
225     {
226         // printk(KERN_INFO "eeprom_open mem\n");
227         return -ENOMEM; 
228     }
229     if(cnt>BUFF_SIZE)
230     {
231         // printk(KERN_INFO "eeprom_open mem\n");
232         return -ENOMEM; 
233     }
234     // printk(KERN_INFO "position is %ld\n",(size_t)*offt);
235     ret=copy_from_user((void*)chip->buf,(void*)buf,cnt);
236     if(ret){
237         return -EIO;
238     }
239     mutex_lock(&chip->eeprom_mutex);
240     ret=chip->ops->send(chip,cnt,*offt);
241     // printk(KERN_INFO "chip->ops-> send %d\n",ret);
242     mutex_unlock(&chip->eeprom_mutex);
243     *offt += ret;
244     return ret;
245 }
246 
247 static loff_t eeprom_llseek(struct file *filp,loff_t vel,int orig )
248 {
249     
250     struct eeprom_chip* chip = filp->private_data;
251     mutex_lock(&chip->eeprom_mutex);
252     switch (orig)
253     {
254         //cur
255         case 1:
256             if( (filp->f_pos + vel) < 0 || (filp->f_pos + vel)>chip->memsize)
257                 return -ESPIPE;
258             filp->f_pos += vel;
259             /* code */
260             break;
261         //end
262         case 2:
263             if(vel > 0||(vel+filp->f_pos)<0)
264                 return -ESPIPE;
265             filp->f_pos += vel;
266             /* code */
267             break;
268         //set
269         default:
270             if(vel>chip->memsize || vel<0)
271                 return -ESPIPE;
272             filp->f_pos = vel;    
273             break;
274     }
275     mutex_unlock(&chip->eeprom_mutex);
276     return 0;
277 }
278 
279 static int eeprom_close(struct inode *inode,struct file *filp)
280 {
281     
282     struct eeprom_chip* chip = filp->private_data;
283     filp->private_data = NULL;
284     // printk(KERN_INFO "eeprom_close\n");
285     chip->is_open = 0;
286     
287     return 0;
288 }
289 
290 struct file_operations eeprom_ops={
291     .owner = THIS_MODULE,
292     .open = eeprom_open,
293     .write = eeprom_write,
294     .read = eeprom_read,
295     .llseek = eeprom_llseek,
296     .release = eeprom_close ,
297 };
298 
299 struct iic_ops  iic_ops={
300     .recv = i2c_eeprom_read,
301     .send = i2c_eeprom_write,
302 };
303 static struct i2c_device_id at24c0xx_idtable[] = {
304     { "24c04x", at24c04x},
305     { "24c08x",at24c08x},
306     { "24c16x",at24c16x},
307     { }
308 };
309 
310 static int at24c0xx_probe(struct i2c_client *client,
311                   const struct i2c_device_id *id)
312 {
313     int rc;
314     struct eeprom_chip *eeprom;
315     struct device *dev = &client->dev;
316     /* check function*/
317     if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
318         return -ENODEV;
319     eeprom = devm_kzalloc(&client->dev,sizeof(struct eeprom_chip), GFP_KERNEL);
320     if (eeprom == NULL) {
321         printk(KERN_INFO "failed to create our eeprom_data\n");
322         return -ENOMEM;
323     }
324     eeprom->eeprom_type = id->driver_data;
325     eeprom->dev=get_device(dev);
326     eeprom->client = client;
327     eeprom->ops = &iic_ops;
328     eeprom->devmisc.fops = &eeprom_ops;
329     eeprom->devmisc.minor = 223;
330     eeprom->devmisc.name=client->name;
331     eeprom->devmisc.parent = eeprom->dev;
332     eeprom->memsize = get_eeprom_memsize(eeprom->eeprom_type);
333     // printk(KERN_INFO "I2C:at24c0x:%d byte\n",eeprom->memsize);
334     mutex_init(&eeprom->eeprom_mutex);
335     rc = misc_register(&eeprom->devmisc);
336     if (rc) {
337         printk(KERN_INFO "%s client created misc_register Fail\n",client->name);
338     }
339     i2c_set_clientdata(client, eeprom);
340     /* rest of the initialisation goes here. */
341     // printk(KERN_INFO "%s client created\n",client->name);
342 
343     return 0;
344 }
345 
346 static int at24c0xx_remove(struct i2c_client *client)
347 {
348     struct eeprom_chip *eeprom = i2c_get_clientdata(client);
349     struct device *dev = &client->dev;
350     misc_deregister(&eeprom->devmisc);
351     put_device(dev);
352     return 0;
353 }
354 
355 static struct i2c_driver eerom_driver = {
356     .driver = {
357         .name    = "at24xx",
358     },
359     .id_table    = at24c0xx_idtable,
360     .probe        = at24c0xx_probe,
361     .remove        = at24c0xx_remove,
362     /* if device autodetection is needed: */
363     // .class        = I2C_CLASS_SOMETHING,
364     // .detect        = at24_detect,
365     // .address_list    = normal_i2c,
366 };
367 
368 int __init eeprom_i2c_driver_init(void)
369 {
370     int retval;
371 
372     retval=i2c_add_driver(&eerom_driver);
373     if(retval)
374     {
375         printk(KERN_INFO "eeprom_i2c_driver_init fail\n");
376     }
377     // printk(KERN_INFO "i2c_add_driver eerom_driver\n");
378     return 0;
379 }
380 
381 void __exit eeprom_i2c_driver_exit(void)
382 {
383     i2c_del_driver(&eerom_driver);
384 }
385 
386 module_init(eeprom_i2c_driver_init);
387 module_exit(eeprom_i2c_driver_exit);
388 
389 MODULE_LICENSE("GPL");
390 MODULE_DESCRIPTION("This is an general driver of atmel eeprom 24xx");
391 MODULE_VERSION("0.1");
392 MODULE_AUTHOR("smile");

设备添加部分

static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
    { I2C_BOARD_INFO("24c08x", 0x50), },     /* Samsung S524AD0XD1 */
    { I2C_BOARD_INFO("wm8580", 0x1b), },
};

通过这一段的学习发现Linux驱动基本都是多元部分的组合实现,学习过程是一个拼积木的过程各个部分的小知识点的不断累计最后才能看明白内核中别人实现好的一个驱动。可以看看基础的部分,如内核内部的并发控制,同步异步IO的实现,阻塞和非阻塞、中断等一些基本但是非常通用的知识点内容从而巩固整体驱动开发知识体系的完善,加深Linux驱动框架的理解,从而在驱动开发上更加自由灵活也对应用的开发提供内核实现机制的深入理解基础。

相关文章:

  • git代码仓库更换
  • SS【1】:转置卷积与膨胀卷积
  • tensorflow2从入门到精通——自编码器系列原理以及实现
  • python的opencv操作记录(五) - 空间域与频域转换
  • java学习day41(JavaWeb)JavaScript高级
  • 训练数据有缺陷?TrustAI来帮你!
  • WebSocket的使用,前后端发送消息的例子
  • MDM属性跳转功能说明
  • java计算机毕业设计物流信息管理系统录像演示源码+系统+数据库+lw文档+mybatis+运行部署
  • 没有基础能否学Java
  • 【CSS】笔记3-三大样式
  • (13)Latex:基于ΤΕΧ的自动排版系统——写论文必备
  • redis 为什么这么快,你真的知道吗?
  • 【湖仓一体化】存OR算之争?SPL 我都要
  • 学习偏态分布的相关知识和原理的4篇论文推荐
  • [笔记] php常见简单功能及函数
  • css布局,左右固定中间自适应实现
  • HTTP 简介
  • Iterator 和 for...of 循环
  • learning koa2.x
  • PHP的类修饰符与访问修饰符
  • Vue ES6 Jade Scss Webpack Gulp
  • 基于Android乐音识别(2)
  • 计算机在识别图像时“看到”了什么?
  • 推荐一个React的管理后台框架
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 小白应该如何快速入门阿里云服务器,新手使用ECS的方法 ...
  • ​iOS实时查看App运行日志
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #13 yum、编译安装与sed命令的使用
  • #我与Java虚拟机的故事#连载01:人在JVM,身不由己
  • ()、[]、{}、(())、[[]]等各种括号的使用
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (算法设计与分析)第一章算法概述-习题
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • .NET Core 中插件式开发实现
  • .Net小白的大学四年,内含面经
  • @Async注解的坑,小心
  • @Bean, @Component, @Configuration简析
  • @RequestBody的使用
  • @Transaction注解失效的几种场景(附有示例代码)
  • [20150707]外部表与rowid.txt
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [AMQP Connection 127.0.0.1:5672] An unexpected connection driver error occured
  • [Android开源]EasySharedPreferences:优雅的进行SharedPreferences数据存储操作
  • [leetcode] Balanced Binary Tree
  • [luoguP3159] [CQOI2012]交换棋子(最小费用最大流)
  • [MRCTF2020]Ez_bypass1
  • [OGRE]看备注学编程(02):打地鼠01-布置场地九只地鼠
  • [POJ2728] Desert King
  • [pyqt5]pyqt5设置窗口背景图片后上面所有图片都会变成和背景图片一样
  • [Repo Git] manifests的写法
  • [Windows] Win11 常用快捷键
  • [量化投资-学习笔记004]Python+TDengine从零开始搭建量化分析平台-EMA均线
  • [小程序]页面事件