mobile CCTV,mobile surveillance,police body worn cameras

 forgetPW
 registerNow
search
view: 2825|reply: 3

ZT:Linux I2C 驱动分析 by luofuchong

[copyURL]

13

主题

373

帖子

2023

积分

vipMem

Rank: 6Rank: 6

积分
2023
poston 2015-11-12 10:58 | 显示全部楼层 |阅读模式

最近在看Linux 2.6.21内核的I2C驱动,也在网上查了一下资料,有错也有对,有些心得,记录下来吧。里面认识或许多有不当之处,还恳请指正。


1. I2C 协议

1.1  I2C总线工作原理

      I2C总线是由数据线SDA和时钟SCL构成的串行总线,各种被控制器件均并联在这条总线上,每个器件都有一个唯一的地址识别,可以作为总线上的一个发送器件或接收器件(具体由器件的功能决定)
1.2  I2C总线的几种信号状态

      1.  空闲状态:SDASCL都为高电平。


      2.  开始条件(S)SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。


      3.  结束条件(P)SCL为低电平时,SDA由低电平向高电平跳变,结束传送数据。


      4.  数据有效:在SCL的高电平期间, SDA保持稳定,数据有效。SDA的改变只能发生在SCL的底电平期间。
      5.  ACK信号: 数据传输的过程中,接收器件每接收一个字节数据要产生一个ACK信号,向发送器件发出特定的低电平脉冲,表示已经收到数据。
1.3  I2C总线基本操作


      I2C总线必须由主器件(通常为微控制器)控制,主器件产生串行时钟(SCL),同时控制总线的传输方向,并产生开始和停止条件。


      数据传输中,首先主器件产生开始条件,随后是器件的控制字节(前七位是从器件的地址,最后一位为读写位 )。接下来是读写操作的数据,以及 ACK响应信号。数据传输结束时,主器件产生停止条件

2. Linux I2C 结构分析
2.1 层次分析
1. I2C Core
I2C Core用于维护Linux的I2C核心部分,其中维护了两个静态的List,分别记录系统中的I2C driver结构和I2C adapter结构。
static LIST_HEAD(adapters);
static LIST_HEAD(drivers);
I2C core提供接口函数,允许一个I2C adatper,I2C driver和I2C client初始化时在I2C core中进行注册,以及退出时进行注销。具体可以参见i2c_core.c代码。
同时还提供了I2C总线读写访问的一般接口(具体的实现在与I2C控制器相关的I2C adapter中实现),主要应用在I2C设备驱动中。
常用的主要是
i2c_master_send()
i2c_master_recv()
i2c_transfer()

2. I2C bus driver
总线驱动的职责,是为系统中每个I2C总线增加相应的读写方法。但是总线驱动本身并不会进行任何的通讯,它只是存在在那里,等待设备驱动调用其函数。
在系统开机时,首先装载的是I2C总线驱动。一个总线驱动用于支持一条特定的I2C总线的读写。一个总线驱动通常需要两个模块,一个struct i2c_adapter和一个struct i2c_algorithm来描述:
在 buses目录下的i2c-pxa.c中实现了PXA的I2C总线适配器,I2C adapter 构造一个对I2C core层接口的数据结构,并通过接口函数向I2C core注册一个控制器。I2C adapter主要实现对I2C总线访问的算法,iic_xfer() 函数就是I2C adapter底层对I2C总线读写方法的实现。同时I2C adpter 中还实现了对I2C控制器中断的处理函数。

1) i2c-pxa.c定义了i2c_algorithm,并且实现了master的发送函数i2c_pxa_xfer(),以及设备查询总线的模式的函数i2c_pxa_functionality()

static const struct i2c_algorithm i2c_pxa_algorithm = {
.master_xfer = i2c_pxa_xfer,
.functionality = i2c_pxa_functionality,
};


2) i2c-pxa.c中,实现了i2c_adapter,主要是在定义pxa-i2c时进行初始化,并且i2c_pxa_probe()中进行填充parent指针,并且调用
ret = i2c_add_adapter(&i2c->adap);
进行添加。

static struct pxa_i2c i2c_pxa = {
.lock = SPIN_LOCK_UNLOCKED,
.adap = {
  .owner  = THIS_MODULE,
  .algo  = &i2c_pxa_algorithm,
  .name  = "pxa2xx-i2c.0",
  .retries = 5,
},
};

总的来说,在i2c-pxa中,使用platform驱动模型,完成了i2c的总线两种模块struct i2c_adapterstruct i2c_algorithm


3. I2C device driver
I2C只有总线驱动是不够的,必须有设备才能工作。这就是I2C device driver的必要性。I2C的device是有两个模块来描述的,struct i2c_driverstruct i2c_client
在介绍chips目录下的device driver前有必要介绍一下i2c-dev.c文件。
      i2c-dev.c中提供了一个通用的I2C设备的驱动程序,实现了字符类型设备的访问接口,对设备的具体访问是通过I2C adapter来实现的。构造一个对I2C core层接口的数据结构,通过接口函数向 I2C Core注册一个I2C设备驱动。同时构造一个对用户层接口的数据结构,并通过接口函数向内核注册为一个主设备号为89的字符类型设备。

static struct i2c_driver i2cdev_driver = {
.driver = {
  .name = "dev_driver",
},
.id  = I2C_DRIVERID_I2CDEV,
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
.detach_client = i2cdev_detach_client,
};
struct i2c_dev {
struct list_head list;
struct i2c_adapter *adap;
struct device *dev;
};

该文件提供了用户层对I2C设备的访问,包括open,read,write,ioctl,release等常规文件操作,我们可以通过open函数打开 I2C的设备文件,通过ioctl函数设定要访问从设备的地址,然后就可以通过 read和write函数完成对I2C设备的读写操作。

static const struct file_operations i2cdev_fops = {
.owner  = THIS_MODULE,
.llseek  = no_llseek,
.read  = i2cdev_read,
.write  = i2cdev_write,
.ioctl  = i2cdev_ioctl,
.open  = i2cdev_open,
.release = i2cdev_release,
};

      注:通过I2C driver提供的通用方法可以访问任何一个I2C的设备,但是其中实现的read,write及ioctl等功能完全是基于一般设备的实现,所有的操作数据都是基于字节流,没有明确的格式和意义。为了更方便和有效地使用I2C设备,我们可以为一个具体的I2C设备开发特定的I2C设备驱动程序,在驱动中完成对特定的数据格式的解释以及实现一些专用的功能。
在chips目录下包含着各种device 的driver,完成各种从设备的注册。作为一般的I2C设备,使用i2c-dev.c里的操作足够完成操作了。
当然如果不能完成,则需要独立完成该驱动,这就是chips目录下的代码。因为i2c-dev.c已经实现了I2C设备的文件操作接口,所以只要实现struct i2c_driver就可以了。对于某些特殊的操作,可以使用command接口进行控制。 当然,对于i2接口的fm芯片,则将struct i2c_driver放在i2c的chips目录下,而将另外fm操作相关的代码放在了/media/radio目录下了。在这个目录下需要完成读写接口的完成,这个大部分使用V4L2架构。
继续分析中……

reply

使用道具 report

13

主题

373

帖子

2023

积分

vipMem

Rank: 6Rank: 6

积分
2023
 Owner| poston 2015-11-12 10:58 | 显示全部楼层
一点想法
kernel:linux-2.6.22

发现linux下字符驱动用的越来越少了,感觉就剩key、led、rtc还在用了(内核下都有现成的~_~),而且都是因为ioctl的缘故而保留的。
记得曾经在一本书上看到介绍说ioctl机制不好,在以后的内核中会被逐渐摒弃之类的。
如果真是这样的话,那需要用到字符驱动的机会就越来越少了(个人意见,有误的话欢迎指正^_^)

ioctl机制虽然不好,可是发现新版本的内核下字符驱动有了新的应用,重新焕发出生气来:
象spi、i2c这些总线驱动,都会提供一些api接口函数,供设备驱动程序调用以操作总线。
按照这种思路,如果要操作spi、i2c总线的话,有且只能通过驱动来实现。
也就是说:需要编写相应的设备驱动程序来实现。
然而,一些基于spi、i2c的设备驱动具有相当的共通性,所以内核下提供了这么一个通用的spi、i2c字符设备驱动供用户直接调用以操作总线,达到操作设备的目的。
reply agree Against

使用道具 report

13

主题

373

帖子

2023

积分

vipMem

Rank: 6Rank: 6

积分
2023
 Owner| poston 2015-11-12 10:59 | 显示全部楼层
发贴心情
i2c关键结构体分析
1、总线配置结构体:
struct s3c2410_platform_i2c {
        unsigned int    flags;
        unsigned int    slave_addr;     /* slave address for controller */
        unsigned long   bus_freq;       /* standard bus frequency */
        unsigned long   max_freq;       /* max frequency for the bus */
        unsigned long   min_freq;       /* min frequency for the bus */
        unsigned int    sda_delay;      /* pclks (s3c2440 only) */
};

2、总线描述结构体:
struct s3c24xx_i2c {
        spinlock_t              lock;        //自选锁(防止总线资源被并发访问)
        wait_queue_head_t       wait;        //等待队列(当有数据需要收/发时启动总线,然后守候在等待队列,直到数据收/发结束后被唤醒返回)

        struct i2c_msg          *msg;        //i2c信息指针
        unsigned int            msg_num;    //需要传输的i2c msg数
        unsigned int            msg_idx;    //成功传输的i2c msg数
        unsigned int            msg_ptr;    //当前i2c msg内指针

        unsigned int            tx_setup;    //延时值(保证总线启动时数据已经传输到总线上)

        enum s3c24xx_i2c_state  state;        //i2c总线状态

        void __iomem            *regs;:
        struct clk              *clk;
        struct device           *dev;
        struct resource         *irq;
        struct resource         *ioarea;
        struct i2c_adapter      adap;        //总线适配器(个人觉得它更像设备驱动中的设备而非驱动)
};

3、总线适配器:
struct i2c_adapter {
        struct module *owner;
        unsigned int id;
        unsigned int class;
        const struct i2c_algorithm *algo;             //i2c总线访问算法
        void *algo_data;                    //用来保存struct s3c24xx_i2c结构指针

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

        /* data fields that are valid for all devices   */
        u8 level;                                   //nesting level for lockdep
        struct mutex bus_lock;
        struct mutex clist_lock;

        int timeout;
        int retries;
        struct device dev;                          //the adapter device

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

4、总线访问算法:
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,    //i2c msg发送函数
                           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);

        /* --- 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 *);                //标志i2c适配器所支持的功能
};

struct i2c_msg {
        __u16 addr;     /* slave address                        */
        __u16 flags;
#define I2C_M_TEN       0x10    /* we have a ten bit chip address       */
#define I2C_M_RD        0x01
#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 /* length will be first received byte */
        __u16 len;              /* msg length                           */
        __u8 *buf;              /* pointer to msg data                  */
};

这里有我画的简单i2c重要结构体分析图:
http://www.cnitblog.com/Files/lu ... %9E%84%E4%BD%93.rar
reply agree Against

使用道具 report

13

主题

373

帖子

2023

积分

vipMem

Rank: 6Rank: 6

积分
2023
 Owner| poston 2015-11-12 10:59 | 显示全部楼层
发贴心情
i2c总线上,适配器、设备驱动注册
1、总线适配器注册:
1)drivers/i2c/i2c-core.c
int i2c_add_adapter(struct i2c_adapter *adapter)
{
        int     id, res = 0;

retry:
        if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
                return -ENOMEM;

        mutex_lock(&core_lists);
        /* "above" here means "above or equal to", sigh */
        res = idr_get_new_above(&i2c_adapter_idr, adapter,
                                __i2c_first_dynamic_bus_num, &id);
        mutex_unlock(&core_lists);

        if (res < 0) {
                if (res == -EAGAIN)
                        goto retry;
                return res;
        }

        adapter->nr = id;            //使用动态的总线号来标识总线适配器。
        return i2c_register_adapter(adapter);
}
EXPORT_SYMBOL(i2c_add_adapter);

2)drivers/i2c/i2c-core.c
static int i2c_register_adapter(struct i2c_adapter *adap)
{
        int res = 0;
        struct list_head   *item;
        struct i2c_driver  *driver;

        mutex_init(&adap->bus_lock);        //初始化总线访问控制变量(总线上数据传输时使用)
        mutex_init(&adap->clist_lock);        //初始化客户端访问控制变量(操作客户端结构时使用)
        INIT_LIST_HEAD(&adap->clients);        //初始化客户端链表头

        mutex_lock(&core_lists);
        list_add_tail(&adap->list, &adapters);    //添加到总线适配器链表中

        /* Add the adapter to the driver core.
         * If the parent pointer is not set up,
         * we add this adapter to the host bus.
         */
        if (adap->dev.parent == NULL) {        
                adap->dev.parent = &platform_bus;
                pr_debug("I2C adapter driver [%s] forgot to specify "
                         "physical device\n", adap->name);
        }
        sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
        adap->dev.release = &i2c_adapter_dev_release;
        adap->dev.class = &i2c_adapter_class;
        res = device_register(&adap->dev);    //注册设备
        if (res)
                goto out_list;

        dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name);

        /* create pre-declared device nodes for new-style drivers */
        if (adap->nr < __i2c_first_dynamic_bus_num)
                i2c_scan_static_board_info(adap);

        /* let legacy drivers scan this bus for matching devices */
        list_for_each(item,&drivers) {        //搜索总线上的所有设备驱动,通过调用其attach_adapter接口函数,查找匹配的设备。
                driver = list_entry(item, struct i2c_driver, list);
                if (driver->attach_adapter)
                        /* We ignore the return code; if it fails, too bad */
                        driver->attach_adapter(adap);
        }

out_unlock:
        mutex_unlock(&core_lists);
        return res;

out_list:
        list_del(&adap->list);
        idr_remove(&i2c_adapter_idr, adap->nr);
        goto out_unlock;
}

2、设备驱动注册(以i2c-dev.c为例):
1)include/linux/i2c.h
static inline int i2c_add_driver(struct i2c_driver *driver)
{
        return i2c_register_driver(THIS_MODULE, driver);
}

2)drivers/i2c/i2c-core.c
int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
        int res;

        /* 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_lists);

        list_add_tail(&driver->list,&drivers);    //添加到设备驱动链表中
        pr_debug("i2c-core: driver [%s] registered\n", driver->driver.name);

        /* legacy drivers scan i2c busses directly */
        if (driver->attach_adapter) {
                struct i2c_adapter *adapter;

                list_for_each_entry(adapter, &adapters, list) {    //让设备驱动搜索匹配的适配器(通过调用其attach_adapter接口)
                        driver->attach_adapter(adapter);
                }
        }

        mutex_unlock(&core_lists);
        return 0;
}
EXPORT_SYMBOL(i2c_register_driver);

3)drivers/i2c/i2c-dev.c
static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
        struct i2c_dev *i2c_dev;
        int res;

        i2c_dev = get_free_i2c_dev(adap);    //创建并初始化i2c_dev结构
        if (IS_ERR(i2c_dev))
                return PTR_ERR(i2c_dev);

        /* register this i2c device with the driver core */
        i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,        //注意,这里使用的次设备号为adap->nr,便于以后获取adap结构。
                                     MKDEV(I2C_MAJOR, adap->nr),
                                     "i2c-%d", adap->nr);
        if (IS_ERR(i2c_dev->dev)) {
                res = PTR_ERR(i2c_dev->dev);
                goto error;
        }
        res = device_create_file(i2c_dev->dev, &dev_attr_name);        //创建设备文件
        if (res)
                goto error_destroy;

        pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
                 adap->name, adap->nr);
        return 0;
error_destroy:
        device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
        return_i2c_dev(i2c_dev);
        return res;
}

总结:
    一个适配器对应一个i2c控制器。
reply agree Against

使用道具 report

creditRule

QQ|wireless surveillance

GMT+8, 2024-3-28 20:15 , Processed in 0.062496 second(s), 20 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

QuickReply backToTop BackToList