基于Linux的I2C驱动组成结构

基于Linux的I2C驱动组成结构



Linux的I2C驱动架构
1. Linux的I2C驱动架构Linux中I2C总线的驱动分为两个部分,总线驱动(BUS)和设备驱动(DEVICE)。其中总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在在那里,等待设备驱动调用其函数。



设备驱动则是与挂在I2C总线上的具体的设备通讯的驱动。通过I2C总线驱动提供的函数,设备驱动可以忽略不同总线控制器的差异,不考虑其实现细节地与硬件设备通讯。



1.1 总线驱动在系统开机时,首先装载的是I2C总线驱动。一个总线驱动用于支持一条特定的I2C总线的读写。一个总线驱动通常需要两个模块,一个struct i2c_adapter和一个struct i2c_algorithm来描述:

/*
 * The following structs are for those who like to implement new bus drivers:
 * i2c_algorithm is the interface to a class of hardware solutions which can
 * be addressed using the same bus algorithms - i.e. bit-banging or the PCF8584
 * to name two of the most common.
 */
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);

    /* --- these optional/future use for some adapter types.*/
    int (*slave_send)(struct i2c_adapter *,char*,int);
    int (*slave_recv)(struct i2c_adapter *,char*,int);

    /* --- ioctl like call to set div. parameters. */
    int (*algo_control)(struct i2c_adapter *, unsigned int, unsigned long);

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




static struct i2c_adapter pb1550_board_adapter = {
name: "pb1550 adapter",
id: I2C_HW_AU1550_PSC,
algo: NULL,
algo_data: &pb1550_i2c_info,
inc_use: pb1550_inc_use,
dec_use: pb1550_dec_use,
client_register: pb1550_reg,
client_unregister: pb1550_unreg,
client_count: 0,
};

/*
 * 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 id;
	unsigned int class;
	struct i2c_algorithm *algo;/* the algorithm to access the bus	*/
	void *algo_data;

	/* --- administration stuff. */
	int (*client_register)(struct i2c_client *);
	int (*client_unregister)(struct i2c_client *);

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

	int timeout;
	int retries;
	struct device dev;		/* the adapter device */
	struct class_device class_dev;	/* the class device */

	int nr;
	struct list_head clients;
	struct list_head list;
	char name[I2C_NAME_SIZE];
	struct completion dev_released;
	struct completion class_dev_released;
};



这个样例挂接了一个叫做“pb1550 adapter”的驱动。但这个模块并未提供读写函数,具体的读写方法由第二个模块,struct i2c_algorithm提供。



static struct i2c_algorithm au1550_algo = {

.name = "Au1550 algorithm",

.id = I2C_ALGO_AU1550,

.master_xfer = au1550_xfer,

.functionality = au1550_func,

};



i2c_adap->algo = &au1550_algo;



这个样例给上述总线驱动增加了读写“算法”。通常情况下每个I2C总线驱动都定义一个自己的读写算法,但鉴于有些总线使用相同的算法,因而可以共用同一套读写函数。本例中的驱动定义了自己的读写算法模块,起名叫“Au1550 algorithm”。



全部填妥后,通过调用:

i2c_add_adapter(i2c_adap);



将这两个模块注册到操作系统里,总线驱动就算装上了。对于AMD au1550,这部分已经由AMD提供了。

1.2 设备驱动如前所述,总线驱动只是提供了对一条总线的读写机制,本身并不会去做通信。通信是由I2C设备驱动来做的,设备驱动透过I2C总线同具体的设备进行通讯。一个设备驱动有两个模块来描述,struct i2c_driver和struct i2c_client。



当系统开机、I2C总线驱动装入完成后,就可以装入设备驱动了。首先装入如下结构:



static struct i2c_driver driver = {

.name = "i2c TV tuner driver",

.id = I2C_DRIVERID_TUNER,

.flags = I2C_DF_NOTIFY,

.attach_adapter = tuner_probe,

.detach_client = tuner_detach,

.command = tuner_command,

};



i2c_add_driver(&driver);



这个i2c_driver一旦装入完成,其中的attach_adapter函数就会被调用。在其中可以遍历系统中的每个i2c总线驱动,探测想要访问的设备:



static int tuner_probe(struct i2c_adapter *adap)

{

return i2c_probe(adap, &addr_data, tuner_attach);

}



注意探测可能会找到多个设备,因而不仅一个I2C总线可以挂多个不同类型的设备,一个设备驱动也可以同时为挂在多个不同I2C总线上的设备服务。



每当设备驱动探测到了一个它能支持的设备,它就创建一个struct i2c_client来标识这个设备:

new_client->addr = address;

new_client->adapter = adapter;

new_client->driver = &driver;



/* Tell the I2C layer a new client has arrived */

err = i2c_attach_client(new_client);

if (err)

goto error;

/*
 * i2c_client identifies a single device (i.e. chip) that is connected to an 
 * i2c bus. The behaviour is defined by the routines of the driver. This
 * function is mainly used for lookup & other admin. functions.
 */
struct i2c_client {
	unsigned int flags;		/* div., see below		*/
	unsigned short addr;		/* chip address - NOTE: 7bit 	*/
					/* addresses are stored in the	*/
					/* _LOWER_ 7 bits		*/
	struct i2c_adapter *adapter;	/* the adapter we sit on	*/
	struct i2c_driver *driver;	/* and our access routines	*/
	int usage_count;		/* How many accesses currently  */
					/* to the client		*/
	struct device dev;		/* the device structure		*/
	struct list_head list;
	char name[I2C_NAME_SIZE];
	struct completion released;
};



可见,一个i2c_client代表着位于adapter总线上,地址为address,使用driver来驱动的一个设备。它将总线驱动与设备驱动,以及设备地址绑定在了一起。一个i2c_client就代表着一个I2C设备。



当得到I2C设备后,就可以直接对此设备进行读写:

/*

* The master routines are the ones normally used to transmit data to devices

* on a bus (or read from them). Apart from two basic transfer functions to

* transmit one message at a time, a more complex version can be used to

* transmit an arbitrary number of messages without interruption.

*/

extern int i2c_master_send(struct i2c_client *,const char* ,int);

extern int i2c_master_recv(struct i2c_client *,char* ,int);



与通常意义上的读写函数一样,这两个函数对i2c_client指针指定的设备,读写int个char。返回值为读写的字节数。对于我们现有的SLIC的驱动,只要将最后要往总线上进行读写的数据引出传输到这两个函数中,移植工作就算完成了,我们将得到一个Linux版的I2C设备驱动。I2C总线使用方法研究(作者LUOFUCHONG)

前言:

    记得以前曾研究过Linux内核下i2c子系统,了解了i2c总线上,适配器、设备驱动的注册过程与使用方法,详细请查看:
    1、i2c总线上,适配器、设备驱动注册:
    2、i2c总线使用:

    最近因为项目的需要,又重新看了一遍linux下的i2c子系统,有了新的体会。俗话说,好记性不如烂笔头,还是记下来比较好,一来当作总结,二来希望起到启发后人的效果^_^

正文:

    Linux的世界充斥着各种各样的想法,i2c子系统同样,新、旧架构并存。至于旧的架构,网上有非常详细的介绍,本人不打算再去重复,咱们直奔新的架构去。

    Linux下的设备驱动,顾名思义,就是由设备和驱动来组成的,i2c总线上的设备驱动也不例外。

一、i2c设备的注册

第一步:
    记得以前的i2c设备驱动,设备部分喜欢驱动运行的时候动态创建,新式的驱动倾向于向传统的linux下设备驱动看齐,采用静态定义的方式来注册设备,使用接口为:
int __init
i2c_register_board_info(int busnum,
    struct i2c_board_info const *info, unsigned len)
{
    int status;

    mutex_lock(&__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);//申请表示i2c设备的结构体空间
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!\n");
            status = -ENOMEM;
            break;
        }
        /* 填写i2c设备描述结构 */
        devinfo->busnum = busnum;
        devinfo->board_info = *info;
        list_add_tail(&devinfo->list, &__i2c_board_list);//添加到全局链表__i2c_board_list中
    }

    mutex_unlock(&__i2c_board_lock);

    return status;
}

第二步:
    系统初始化的时候,会根据板级i2c设备配置信息,创建i2c客户端设备(i2c_client),添加到i2c子系统中:
static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    struct i2c_devinfo    *devinfo;

    mutex_lock(&__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))
            printk(KERN_ERR "i2c-core: can't create i2c%d-%04x\n",
                i2c_adapter_id(adapter),
                devinfo->board_info.addr);
    }
    mutex_unlock(&__i2c_board_lock);
}

struct i2c_client *
i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client    *client;
    int            status;

    client = kzalloc(sizeof *client, GFP_KERNEL);//创建i2c_client设备
    if (!client)
        return NULL;

    client->adapter = adap;

    client->dev.platform_data = info->platform_data;

    if (info->archdata)
        client->dev.archdata = *info->archdata;

    client->flags = info->flags;
    client->addr = info->addr;
    client->irq = info->irq;

    strlcpy(client->name, info->type, sizeof(client->name));

    /* a new style driver may be bound to this device when we
    * return from this function, or any later moment (e.g. maybe
    * hotplugging will load the driver module). and the device
    * refcount model is the standard driver model one.
    */
    status = i2c_attach_client(client);//接入设备到i2c总线上
    if (status < 0) {
        kfree(client);
        client = NULL;
    }
    return client;
}

int i2c_attach_client(struct i2c_client *client)
{
    struct i2c_adapter *adapter = client->adapter;
    int res;

    /* Check for address business */
    res = i2c_check_addr(adapter, client->addr);//地址检测
    if (res)
        return res;

    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;

    if (client->driver)
        client->dev.driver = &client->driver->driver;

    if (client->driver && !is_newstyle_driver(client->driver)) {
        client->dev.release = i2c_client_release;
        client->dev.uevent_suppress = 1;
    } else
        client->dev.release = i2c_client_dev_release;

    snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),
        "%d-%04x", i2c_adapter_id(adapter), client->addr);
    res = device_register(&client->dev);    //注册设备
    if (res)
        goto out_err;

    mutex_lock(&adapter->clist_lock);
    list_add_tail(&client->list, &adapter->clients);
    mutex_unlock(&adapter->clist_lock);

    dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n",
        client->name, client->dev.bus_id);

    if (adapter->client_register) {
        if (adapter->client_register(client)) {
            dev_dbg(&adapter->dev, "client_register "
                "failed for client [%s] at 0x%02x\n",
                client->name, client->addr);
        }
    }

    return 0;

out_err:
    dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "
        "(%d)\n", client->name, client->addr, res);
    return res;
}

注:
    特别要提一下的是这个“i2c_check_addr”,引用<<i2c 源代码情景分析>>里的话:“i2c 设备的7 位地址是就当前i2c 总线而言的,是“相对地址”。不同的i2c 总线上的设备可以使用相同的7 位地址,但是它们所在的i2c 总线不同。所以在系统中一个i2c 设备的“绝对地址”由二元组(i2c 适配器的ID 和设备在该总线上的7 位地址)表示。”,所以这个函数的作用主要是排除同一i2c总线上出现多个地址相同的设备。

二、驱动的注册:

第一步:

static inline int i2c_add_driver(struct i2c_driver *driver)
{
    return 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;

    /* new style driver methods can't mix with legacy ones */
    if (is_newstyle_driver(driver)) {                           //呵呵,新旧架构的驱动可以从这里看出来
        if (driver->attach_adapter || driver->detach_adapter
                || driver->detach_client) {
            printk(KERN_WARNING
                    "i2c-core: driver [%s] is confused\n",
                    driver->driver.name);
            return -EINVAL;
        }
    }

    /* add the driver to the list of i2c drivers in the driver core */
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;

    /* for new style drivers, 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;

    mutex_lock(&core_lock);

    pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

    INIT_LIST_HEAD(&driver->clients);
    /* Walk the adapters that are already present */
    class_for_each_device(&i2c_adapter_class, NULL, driver,
                  __attach_adapter);

    mutex_unlock(&core_lock);
    return 0;
}

第二步:
    还记得在第一部分,我们注册了一i2c设备,其总线类型为i2c_bus_type,现在我们又在这总线上注册一驱动,那不得了,满足了设备+驱动的条件,i2c_bus_type总线上探测函数要被执行了。

struct bus_type i2c_bus_type = {
    .name        = "i2c",
    .dev_attrs    = i2c_dev_attrs,
    .match        = i2c_device_match,
    .uevent        = i2c_device_uevent,
    .probe        = i2c_device_probe,
    .remove        = i2c_device_remove,
    .shutdown    = i2c_device_shutdown,
    .suspend    = i2c_device_suspend,
    .resume        = i2c_device_resume,
};

static int i2c_device_probe(struct device *dev)
{
    struct i2c_client    *client = to_i2c_client(dev);
    struct i2c_driver    *driver = to_i2c_driver(dev->driver);
    int status;

    if (!driver->probe || !driver->id_table)
        return -ENODEV;
    client->driver = driver;
    if (!device_can_wakeup(&client->dev))
        device_init_wakeup(&client->dev,
                    client->flags & I2C_CLIENT_WAKE);
    dev_dbg(dev, "probe\n");

    status = driver->probe(client, i2c_match_id(driver->id_table, client));//调用具体i2c设备驱动的探测函数
    if (status)
        client->driver = NULL;
    return status;
}

转载:http://bbs.51soc.com/read.php?tid=45


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要模拟Linux I2C驱动,我们需要理解I2C协议和Linux驱动框架的基本原理。 I2C(Inter-Integrated Circuit)是一种串行通信协议,允许多个器件通过共享的两根线(SDA和SCL)进行通信。在Linux中,I2C驱动负责管理I2C总线上的设备与内核的通信。 首先,我们需要创建一个虚拟I2C设备。可以使用I2C-dev驱动模块来创建一个模拟的I2C设备节点文件。这可以通过在终端中运行“modprobe i2c-dev”命令来加载模块。 然后,我们需要编写一个I2C驱动程序,它需要遵循Linux驱动模型。驱动程序需要实现与I2C设备通信的函数,如读取和写入数据、发送和接收命令等。我们可以使用ioctl系统调用来实现这些操作。 另外,我们还需要在Linux的设备树(Device Tree)中添加对虚拟I2C设备的描述。这可以通过编辑设备树文件(如.dts或.dtsi)来完成。设备树会告诉内核如何初始化和配置I2C设备。 最后,我们需要编译和加载驱动程序。可以使用交叉编译工具链来编译驱动程序,并将其加载到Linux系统中。编译和加载驱动程序的具体步骤可以根据具体的开发环境和目标平台来进行配置和调整。 通过以上步骤,我们就可以在Linux系统上模拟一个I2C驱动。这样可以方便地进行I2C设备的开发和调试,而无需实际的硬件设备。同时,这也为Linux内核提供了一个通用的I2C接口,可以方便地与各种I2C设备进行通信。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值