设备驱动中的i2c(kernel-4.7)

来源:互联网 发布:linux自动登录ssh脚本 编辑:程序博客网 时间:2024/05/13 07:22

必要的概念和已有的总结还是要拷贝来的:

I2C架构概述

Linux的I2C体系结构分为3个组成部分:
I2C核心:I2C核心提供了I2C总线驱动和设备驱动的注册,注销方法,I2C通信方法(”algorithm”)上层的,与具体适配器无关的代码以及探测设备,检测设备地址的上层代码等。
I2C总线驱动:I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。
I2C设备驱动:I2C设备驱动(也称为客户驱动)是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据

linux驱动中i2c驱动架构

这里写图片描述

上图完整的描述了linux i2c驱动架构,虽然I2C硬件体系结构比较简单,但是i2c体系结构在linux中的实现却相当复杂

架构层次分类

  第一层:提供i2c adapter的硬件驱动,探测、初始化i2c adapter(如申请i2c的io地址和中断号),驱动soc控制的i2c adapter在硬件上产生信号(start、stop、ack)以及处理i2c中断。覆盖图中的硬件实现层

  第二层:提供i2c adapter的algorithm,用具体适配器的xxx_xferf()数来填充i2c_algorithmmaster_xfer函数指针,并把赋值后的i2c_algorithm再赋值给i2c_adapte的algo指针。覆盖图中的访问抽象层、i2c核心层

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

  第四层:实现i2c设备所对应的具体device的驱动,i2c_driver只是实现设备与总线的挂接,而挂接在总线上的设备则是千差万别的,所以要实现具体设备device的write()read()ioctl()等方法,赋值给file_operations,然后注册字符设备(多数是字符设备)。覆盖图中的driver驱动层

I2C调用图示:
这里写图片描述

Linux下I2C体系文件构架

在Linux内核源代码中的driver目录下包含一个i2c目录
i2c-core.c这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口。
i2c-dev.c实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访设备时的主设备号都为89,次设备号为0-255。i2c-dev.c并没有针对特定的设备而设计,只是提供了通用的read(),write(),和ioctl()等接口,应用层可以借用这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式。
busses文件夹这个文件中包含了一些I2C总线的驱动,如针对S3C2410,S3C2440,S3C6410等处理器的I2C控制器驱动为i2c-s3c2410.c.
algos文件夹实现了一些I2C总线适配器的algorithm.

以上总结可知:
在Linux驱动中I2C系统中主要包含以下几个成员:
i2c_adapter即I2C适配器
i2c_driver 某个I2C设备的设备驱动,可以以driver理解。
i2c_client某个I2C设备的设备声明,可以以device理解。
i2c_algorithm 某个I2C设备数据的传输算法,对应一套通信方法。

I2C adapter

i2c_adapter对应与物理上的一个适配器,而i2c_algorithm对应一套通信方法,一个i2c适配器需要i2c_algorithm中提供的(i2c_algorithm中的又是更下层与硬件相关的代码提供)通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithmi2c_adapter什么也做不了,因此i2c_adapter中包含其使用i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生i2c访问周期需要的start stop ack信号,以i2c_msg(即i2c消息)为单位发送和接收通信数据。
i2c_adapter是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,但最主要的工作是需要完成i2c_algorithm结构体。这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型。i2c_algorithm结构体如下:

/** * struct i2c_algorithm - represent I2C transfer method * @master_xfer: Issue a set of i2c transactions to the given I2C adapter *   defined by the msgs array, with num messages available to transfer via *   the adapter specified by adap. * @smbus_xfer: Issue smbus transactions to the given I2C adapter. If this *   is not present, then the bus layer will try and convert the SMBus calls *   into I2C transfers instead. * @functionality: Return the flags that this algorithm/adapter pair supports *   from the I2C_FUNC_* flags. * @reg_slave: Register given client to I2C slave mode of this adapter * @unreg_slave: Unregister given client from I2C slave mode of this adapter * * 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. * * The return codes from the @master_xfer field should indicate the type of * error code that occurred during the transfer, as documented in the kernel * Documentation file Documentation/i2c/fault-codes. */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); //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传输函数指针    /* To determine what the adapter supports */    u32 (*functionality) (struct i2c_adapter *); //返回适配器支持的功能#if IS_ENABLED(CONFIG_I2C_SLAVE)    int (*reg_slave)(struct i2c_client *client);    int (*unreg_slave)(struct i2c_client *client);#endif};

如果一个I2C适配器不支持I2C通道,那么就将master_xfer成员设为NULL。如果适配器支持SMBUS协议,那么需要去实现smbus_xfer,如果smbus_xfer指针被设为NULL,那么当使用SMBUS协议的时候将会通过I2C通道进行仿真。master_xfer指向的函数的返回值应该是已经成功处理的消息数,或者返回负数表示出错了。functionality指针很简单,告诉询问着这个I2C主控器都支持什么功能。

在内核的drivers/i2c/i2c-stub.c中实现了一个i2c adapter的例子,其中实现的是更为复杂的SMBUS。

SMBus 与 I2C的区别
通常情况下,I2C和SMBus是兼容的,但是还是有些微妙的区别的。

时钟速度对比:

I2C SMBus 最小 无 最大 100kHZ(标准)400kHz(快速模式)2MHz(高速模式) 超时 无

在电气特性上他们也有所不同,SMBus要求的电压范围更低。
以下为i2c_adapter的结构体定义:

/* * 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 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; //控制并发访问的自旋锁    struct rt_mutex mux_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;  //client链表头    struct i2c_bus_recovery_info *bus_recovery_info;    const struct i2c_adapter_quirks *quirks;    void (*lock_bus)(struct i2c_adapter *, unsigned int flags);    int (*trylock_bus)(struct i2c_adapter *, unsigned int flags);    void (*unlock_bus)(struct i2c_adapter *, unsigned int flags);};#define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev)

I2C driver

具体的I2C设备驱动,如相机、传感器、触摸屏、背光控制器常见硬件设备大多都有或都是通过I2C协议与主机进行数据传输、控制。结构体如下:

/** * struct i2c_driver - represent an I2C device driver * @class: What kind of i2c device we instantiate (for detect) * @attach_adapter: Callback for bus addition (deprecated) * @probe: Callback for device binding * @remove: Callback for device unbinding * @shutdown: Callback for device shutdown * @alert: Alert callback, for example for the SMBus alert protocol * @command: Callback for bus-wide signaling (optional) * @driver: Device driver model driver * @id_table: List of I2C devices supported by this driver * @detect: Callback for device detection * @address_list: The I2C addresses to probe (for detect) * @clients: List of detected clients we created (for i2c-core use only) * * The driver.owner field should be set to the module owner of this driver. * The driver.name field should be set to the name of this driver. * * For automatic device detection, both @detect and @address_list must * be defined. @class should also be set, otherwise only devices forced * with module parameters will be created. The detect function must * fill at least the name field of the i2c_board_info structure it is * handed upon successful detection, and possibly also the flags field. * * If @detect is missing, the driver will still work fine for enumerated * devices. Detected devices simply won't be supported. This is expected * for the many I2C/SMBus devices which can't be detected reliably, and * the ones which can always be enumerated in practice. * * The i2c_client structure which is handed to the @detect callback is * not a real i2c_client. It is initialized just enough so that you can * call i2c_smbus_read_byte_data and friends on it. Don't do anything * else with it. In particular, calling dev_dbg and friends on it is * not allowed. */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 *);//关闭设备    /* 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;//I2C设备的驱动模型    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;};#define to_i2c_driver(d) container_of(d, struct i2c_driver, driver)

I2C client

即I2C设备。I2C设备的注册一般在板级代码中,在解析实例前还是先熟悉几个定义:

/** * struct i2c_client - represent an I2C slave device * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address; *  I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking * @addr: Address used on the I2C bus connected to the parent adapter. * @name: Indicates the type of the device, usually a chip name that's *  generic enough to hide second-sourcing and compatible revisions. * @adapter: manages the bus segment hosting this I2C device * @dev: Driver model device node for the slave. * @irq: indicates the IRQ generated by this device (if any) * @detected: member of an i2c_driver.clients list or i2c-core's *  userspace_devices list * @slave_cb: Callback when I2C slave mode of an adapter is used. The adapter *  calls it to pass on slave events to the slave driver. * * An i2c_client identifies a single device (i.e. chip) connected to an * i2c bus. The behaviour exposed to Linux is defined by the driver * managing the device. */struct i2c_client {    unsigned short flags;       /* div., see below      */    unsigned short addr;        /* chip address - NOTE: 7bit    */                    /* addresses are stored in the  */                    /* _LOWER_ 7 bits       */    char name[I2C_NAME_SIZE];    struct i2c_adapter *adapter;    /* the adapter we sit on    */    struct device dev;      /* the device structure     */    int irq;            /* irq issued by device     */    struct list_head detected;#if IS_ENABLED(CONFIG_I2C_SLAVE)    i2c_slave_cb_t slave_cb;    /* callback for slave mode  */#endif};#define to_i2c_client(d) container_of(d, struct i2c_client, dev)/** * struct i2c_board_info - template for device creation * @type: chip type, to initialize i2c_client.name * @flags: to initialize i2c_client.flags * @addr: stored in i2c_client.addr * @platform_data: stored in i2c_client.dev.platform_data * @archdata: copied into i2c_client.dev.archdata * @of_node: pointer to OpenFirmware device node * @fwnode: device node supplied by the platform firmware * @irq: stored in i2c_client.irq * * I2C doesn't actually support hardware probing, although controllers and * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's * a device at a given address.  Drivers commonly need more information than * that, such as chip type, configuration, associated IRQ, and so on. * * i2c_board_info is used to build tables of information listing I2C devices * that are present.  This information is used to grow the driver model tree. * For mainboards this is done statically using i2c_register_board_info(); * bus numbers identify adapters that aren't yet available.  For add-on boards, * i2c_new_device() does this dynamically with the adapter already known. */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 fwnode_handle *fwnode;    int     irq;};/** * I2C_BOARD_INFO - macro used to list an i2c device and its address * @dev_type: identifies the device type * @dev_addr: the device's address on the bus. * * This macro initializes essential fields of a struct i2c_board_info, * declaring what has been provided on a particular board.  Optional * fields (such as associated irq, or device-specific platform_data) * are provided using conventional syntax. */#define I2C_BOARD_INFO(dev_type, dev_addr) \    .type = dev_type, .addr = (dev_addr)

S3C2410 I2C 总线驱动实例

1.S3C2410I2C 控制器硬件描述

S3C2410处理器内部集成了一个I2C控制器,通过4个寄存器就可方便地对其进行控制,这4个寄存器如下。

1 IICCON:I2C 控制寄存器。2 IICSTAT:I2C 状态寄存器。3 IICDS:I2C 收发数据移位寄存器。4 IICADD:I2C 地址寄存器。

S3C2410处理器内部集成的I2C控制器可支持主、从两种模式,我们主要使用其主模式。通过对IICCON、IICDS和IICADD寄存器的操作,可在I2C总线上产生开始位、停止位、数据和地址,而传输的状态则通过IICSTAT寄存器获取。

2.S3C2410I2C 总线驱动总体分析
S3C2410的I2C总线驱动设计主要要完成以下工作。
1, 设计对应于i2c_adapter_xxx_init()模板的S3C2410的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。
2, 设计对应于i2c_adapter_xxx_xfer()模板的S3C2410适配器的通信方法函数。针对 S3C2410, functionality() 函数 只需 简单 地返 回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING 表明其支持的功能。

内核源代码中的/drivers/i2c/busses/i2c-s3c2410.c

图示给出了S3C2410驱动中的主要函数与模板函数的对应关系,由于实现通信方法的方式不一样,模板的一个函数可能对应与S3C2410I2C 总线驱动的多个函数。
这里写图片描述

3.S3C2410I2C 适配器驱动的模块加载与卸载

I2C适配器驱动被作为一个单独的模块加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体, 结构体包含了具体适配器的probe()函数、remove()函数、resume()函数指针等信息,它需要被定义和赋值:

static struct platform_driver s3c24xx_i2c_driver = {    .probe      = s3c24xx_i2c_probe,    .remove     = s3c24xx_i2c_remove,    .id_table   = s3c24xx_driver_ids,    .driver     = {        .name   = "s3c-i2c",        .pm = S3C24XX_DEV_PM_OPS,        .of_match_table = of_match_ptr(s3c24xx_i2c_match),    },}; static int __init i2c_adap_s3c_init(void){    return platform_driver_register(&s3c24xx_i2c_driver);}subsys_initcall(i2c_adap_s3c_init);static void __exit i2c_adap_s3c_exit(void){    platform_driver_unregister(&s3c24xx_i2c_driver);}module_exit(i2c_adap_s3c_exit);

s3c24xx_i2c_probe()函数将被调用,以初始化适配器硬件 :

static int s3c24xx_i2c_probe(struct platform_device *pdev){    struct s3c24xx_i2c *i2c;    struct s3c2410_platform_i2c *pdata = NULL;    struct resource *res;    int ret;    if (!pdev->dev.of_node) {        pdata = dev_get_platdata(&pdev->dev);        if (!pdata) {            dev_err(&pdev->dev, "no platform data\n");            return -EINVAL;        }    }    i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);    if (!i2c)        return -ENOMEM;    i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);    if (!i2c->pdata)        return -ENOMEM;    i2c->quirks = s3c24xx_get_device_quirks(pdev);    i2c->sysreg = ERR_PTR(-ENOENT);    if (pdata)        memcpy(i2c->pdata, pdata, sizeof(*pdata));    else        s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);    strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));    i2c->adap.owner = THIS_MODULE;    i2c->adap.algo = &s3c24xx_i2c_algorithm;    i2c->adap.retries = 2;    i2c->adap.class = I2C_CLASS_DEPRECATED;    i2c->tx_setup = 50;    init_waitqueue_head(&i2c->wait);    /* find the clock and enable it */     /*使能I2C的时钟*/    i2c->dev = &pdev->dev;    i2c->clk = devm_clk_get(&pdev->dev, "i2c");    if (IS_ERR(i2c->clk)) {        dev_err(&pdev->dev, "cannot get clock\n");        return -ENOENT;    }    dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);    /* map the registers */     /*映射寄存器*/    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    i2c->regs = devm_ioremap_resource(&pdev->dev, res);    if (IS_ERR(i2c->regs))        return PTR_ERR(i2c->regs);    dev_dbg(&pdev->dev, "registers %p (%p)\n",        i2c->regs, res);    /* setup info block for the i2c core */     /*设置I2C的信息块*/    i2c->adap.algo_data = i2c;    i2c->adap.dev.parent = &pdev->dev;    i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);    /* inititalise the i2c gpio lines */    if (i2c->pdata->cfg_gpio)        i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));    else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c))        return -EINVAL;    /* initialise the i2c controller */    ret = clk_prepare_enable(i2c->clk);    if (ret) {        dev_err(&pdev->dev, "I2C clock enable failed\n");        return ret;    }    /*初始化I2C控制器*/    ret = s3c24xx_i2c_init(i2c);    clk_disable(i2c->clk);    if (ret != 0) {        dev_err(&pdev->dev, "I2C controller init failed\n");        clk_unprepare(i2c->clk);        return ret;    }    /*     * find the IRQ for this unit (note, this relies on the init call to     * ensure no current IRQs pending     */      /*申请中断*/    if (!(i2c->quirks & QUIRK_POLL)) {        i2c->irq = ret = platform_get_irq(pdev, 0);        if (ret <= 0) {            dev_err(&pdev->dev, "cannot find IRQ\n");            clk_unprepare(i2c->clk);            return ret;        }        ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,                       0, dev_name(&pdev->dev), i2c);        if (ret != 0) {            dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);            clk_unprepare(i2c->clk);            return ret;        }    }    ret = s3c24xx_i2c_register_cpufreq(i2c);    if (ret < 0) {        dev_err(&pdev->dev, "failed to register cpufreq notifier\n");        clk_unprepare(i2c->clk);        return ret;    }    /*     * Note, previous versions of the driver used i2c_add_adapter()     * to add the bus at any number. We now pass the bus number via     * the platform data, so if unset it will now default to always     * being bus 0.     */       /*添加i2c_adapter*/    i2c->adap.nr = i2c->pdata->bus_num;    i2c->adap.dev.of_node = pdev->dev.of_node;    platform_set_drvdata(pdev, i2c);    pm_runtime_enable(&pdev->dev);    ret = i2c_add_numbered_adapter(&i2c->adap);    if (ret < 0) {        dev_err(&pdev->dev, "failed to add bus to i2c core\n");        pm_runtime_disable(&pdev->dev);        s3c24xx_i2c_deregister_cpufreq(i2c);        clk_unprepare(i2c->clk);        return ret;    }    dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));    return 0;}

上述代码中的主体工作是使能硬件并申请I2C适配器使用的I/O地址,在这些工作都完成无误后添加i2c adaper适配器。 因为 S3C2410内部 集成 I2C控制 器,可以确定 I2C适配 器一 定存 在, s3c24xx_i2c_probe()函数虽然命名“探测”,但实际没有也不必进行任何探测工作,之所以这样命名完全是一种设计习惯。

s3c24xx_i2c_probe()函数完成相反功能的函数是s3c24xx_i2c_remove()函数,它在适配器模块卸载函数调用platform_driver_unregister()函数时所示通过platform_driver的remove指针方式被调用。
s3cxxx_i2c_remove() 卸载函数代码:

static int s3c24xx_i2c_remove(struct platform_device *pdev){    struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);    clk_unprepare(i2c->clk);    pm_runtime_disable(&pdev->dev);    s3c24xx_i2c_deregister_cpufreq(i2c);    i2c_del_adapter(&i2c->adap);    if (pdev->dev.of_node && IS_ERR(i2c->pctrl))        s3c24xx_i2c_dt_gpio_free(i2c);    return 0;}

以上函数用到的s3c24xx_i2c结构体进行适配器所有信息的封装,类似于私有信息结构体,它与xxx_i2c结构体模板对应。
s3c24xx_i2c结构体如下:

struct s3c24xx_i2c {    wait_queue_head_t   wait;    kernel_ulong_t      quirks;    unsigned int        suspended:1;    struct i2c_msg      *msg;    unsigned int        msg_num;    unsigned int        msg_idx;    unsigned int        msg_ptr;    unsigned int        tx_setup;    unsigned int        irq;    enum s3c24xx_i2c_state  state;    unsigned long       clkrate;    void __iomem        *regs;    struct clk      *clk;    struct device       *dev;    struct i2c_adapter  adap;    struct s3c2410_platform_i2c *pdata;    int         gpios[2];    struct pinctrl          *pctrl;#if defined(CONFIG_ARM_S3C24XX_CPUFREQ)    struct notifier_block   freq_transition;#endif    struct regmap       *sysreg;    unsigned int        sys_i2c_cfg;};

4.S3C2410I2C 总线通信方法

由以上probe()函数中i2c->adap.algo = &s3c24xx_i2c_algorithm;行可得I2C适配器对应的i2c_algorithm结构体
s3c24xx_i2c_algorithm的定义如下:

/* i2c bus registration info */static const struct i2c_algorithm s3c24xx_i2c_algorithm = {    .master_xfer        = s3c24xx_i2c_xfer,    .functionality      = s3c24xx_i2c_func,};

上述代码指定了S3C2410 I2C 总线通信传输函数s3c24xx_i2c_xfer(),这个
函数非常关键,所有I2C总线上对设备的访问最终应该由它来完成。

S3C2410 I2C 总线驱动的master_xfer函数:

/* * first port of call from the i2c bus code when an message needs * transferring across the i2c bus. */static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,            struct i2c_msg *msgs, int num){    struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;    int retry;    int ret;    ret = clk_enable(i2c->clk);    if (ret)        return ret;    for (retry = 0; retry < adap->retries; retry++) {        ret = s3c24xx_i2c_doxfer(i2c, msgs, num);        if (ret != -EAGAIN) {            clk_disable(i2c->clk);            return ret;        }        dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);        udelay(100);    }    clk_disable(i2c->clk);    return -EREMOTEIO;}

调用了s3c24xx_i2c_doxfer 函数 :

/* * this starts an i2c transfer */static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,                  struct i2c_msg *msgs, int num){    unsigned long timeout;    int ret;    if (i2c->suspended)        return -EIO;    ret = s3c24xx_i2c_set_master(i2c);    if (ret != 0) {        dev_err(i2c->dev, "cannot get bus (error %d)\n", ret);        ret = -EAGAIN;        goto out;    }    i2c->msg     = msgs;    i2c->msg_num = num;    i2c->msg_ptr = 0;    i2c->msg_idx = 0;    i2c->state   = STATE_START;    s3c24xx_i2c_enable_irq(i2c);    s3c24xx_i2c_message_start(i2c, msgs);    if (i2c->quirks & QUIRK_POLL) {        ret = i2c->msg_idx;        if (ret != num)            dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);        goto out;    }    timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);    ret = i2c->msg_idx;    /*     * Having these next two as dev_err() makes life very     * noisy when doing an i2cdetect     */    if (timeout == 0)        dev_dbg(i2c->dev, "timeout\n");    else if (ret != num)        dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret);    /* For QUIRK_HDMIPHY, bus is already disabled */    if (i2c->quirks & QUIRK_HDMIPHY)        goto out;    s3c24xx_i2c_wait_idle(i2c);    s3c24xx_i2c_disable_bus(i2c); out:    i2c->state = STATE_IDLE;    return ret;}

调用了s3c24xx_i2c_message_start 函数:

/* * put the start of a message onto the bus */static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c,                      struct i2c_msg *msg){    unsigned int addr = (msg->addr & 0x7f) << 1;    unsigned long stat;    unsigned long iiccon;    stat = 0;    stat |=  S3C2410_IICSTAT_TXRXEN;    if (msg->flags & I2C_M_RD) {        stat |= S3C2410_IICSTAT_MASTER_RX;        addr |= 1;    } else        stat |= S3C2410_IICSTAT_MASTER_TX;    if (msg->flags & I2C_M_REV_DIR_ADDR)        addr ^= 1;    /* todo - check for whether ack wanted or not */    s3c24xx_i2c_enable_ack(i2c);    iiccon = readl(i2c->regs + S3C2410_IICCON);    writel(stat, i2c->regs + S3C2410_IICSTAT);    dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr);    writeb(addr, i2c->regs + S3C2410_IICDS);    /*     * delay here to ensure the data byte has gotten onto the bus     * before the transaction is started     */    ndelay(i2c->tx_setup);    dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon);    writel(iiccon, i2c->regs + S3C2410_IICCON);    stat |= S3C2410_IICSTAT_START;    writel(stat, i2c->regs + S3C2410_IICSTAT);    if (i2c->quirks & QUIRK_POLL) {        while ((i2c->msg_num != 0) && is_ack(i2c)) {            i2c_s3c_irq_nextbyte(i2c, stat);            stat = readl(i2c->regs + S3C2410_IICSTAT);            if (stat & S3C2410_IICSTAT_ARBITR)                dev_err(i2c->dev, "deal with arbitration loss\n");        }    }}

s3c24xx_i2c_xfer()函数调用s3c24xx_i2c_doxfer()函数传输I2C消息,s3c24xx_i2c_doxfer()首先将S3C2410的I2C适配器设置为I2C主设备,其后使能I2C中断,并用s3c24xx_i2c_message_start()函数启动I2C消息的传输。
s3c24xx_i2c_message_start()函数写S3C2410适配器对应的控制寄存器,向I2C从设备传递开始位和从设备地址。

上述代码只是启动了I2C消息数组的传输周期,并没有完整实现下图中给出的algorithm master_xfer 流程。这个流程的完整实现需要借助I2C适配器上的中断来步步推进。
S3C2410 I2C 适配器中断处理函数以及其依赖的i2s_s3c_irq_nextbyte()函数的源代码:

/* * process an interrupt and work out what to do */static int i2c_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat){    unsigned long tmp;    unsigned char byte;    int ret = 0;    switch (i2c->state) {    case STATE_IDLE:        dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);        goto out;    case STATE_STOP:        dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);        s3c24xx_i2c_disable_irq(i2c);        goto out_ack;    case STATE_START:        /*         * last thing we did was send a start condition on the         * bus, or started a new i2c message         */        if (iicstat & S3C2410_IICSTAT_LASTBIT &&            !(i2c->msg->flags & I2C_M_IGNORE_NAK)) {            /* ack was not received... */            dev_dbg(i2c->dev, "ack was not received\n");            s3c24xx_i2c_stop(i2c, -ENXIO);            goto out_ack;        }        if (i2c->msg->flags & I2C_M_RD)            i2c->state = STATE_READ;        else            i2c->state = STATE_WRITE;        /*         * Terminate the transfer if there is nothing to do         * as this is used by the i2c probe to find devices.         */        if (is_lastmsg(i2c) && i2c->msg->len == 0) {            s3c24xx_i2c_stop(i2c, 0);            goto out_ack;        }        if (i2c->state == STATE_READ)            goto prepare_read;        /*         * fall through to the write state, as we will need to         * send a byte as well         */    case STATE_WRITE:        /*         * we are writing data to the device... check for the         * end of the message, and if so, work out what to do         */        if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {            if (iicstat & S3C2410_IICSTAT_LASTBIT) {                dev_dbg(i2c->dev, "WRITE: No Ack\n");                s3c24xx_i2c_stop(i2c, -ECONNREFUSED);                goto out_ack;            }        } retry_write:        if (!is_msgend(i2c)) {            byte = i2c->msg->buf[i2c->msg_ptr++];            writeb(byte, i2c->regs + S3C2410_IICDS);            /*             * delay after writing the byte to allow the             * data setup time on the bus, as writing the             * data to the register causes the first bit             * to appear on SDA, and SCL will change as             * soon as the interrupt is acknowledged             */            ndelay(i2c->tx_setup);        } else if (!is_lastmsg(i2c)) {            /* we need to go to the next i2c message */            dev_dbg(i2c->dev, "WRITE: Next Message\n");            i2c->msg_ptr = 0;            i2c->msg_idx++;            i2c->msg++;            /* check to see if we need to do another message */            if (i2c->msg->flags & I2C_M_NOSTART) {                if (i2c->msg->flags & I2C_M_RD) {                    /*                     * cannot do this, the controller                     * forces us to send a new START                     * when we change direction                     */                    s3c24xx_i2c_stop(i2c, -EINVAL);                }                goto retry_write;            } else {                /* send the new start */                s3c24xx_i2c_message_start(i2c, i2c->msg);                i2c->state = STATE_START;            }        } else {            /* send stop */            s3c24xx_i2c_stop(i2c, 0);        }        break;    case STATE_READ:        /*         * we have a byte of data in the data register, do         * something with it, and then work out whether we are         * going to do any more read/write         */        byte = readb(i2c->regs + S3C2410_IICDS);        i2c->msg->buf[i2c->msg_ptr++] = byte;        /* Add actual length to read for smbus block read */        if (i2c->msg->flags & I2C_M_RECV_LEN && i2c->msg->len == 1)            i2c->msg->len += byte; prepare_read:        if (is_msglast(i2c)) {            /* last byte of buffer */            if (is_lastmsg(i2c))                s3c24xx_i2c_disable_ack(i2c);        } else if (is_msgend(i2c)) {            /*             * ok, we've read the entire buffer, see if there             * is anything else we need to do             */            if (is_lastmsg(i2c)) {                /* last message, send stop and complete */                dev_dbg(i2c->dev, "READ: Send Stop\n");                s3c24xx_i2c_stop(i2c, 0);            } else {                /* go to the next transfer */                dev_dbg(i2c->dev, "READ: Next Transfer\n");                i2c->msg_ptr = 0;                i2c->msg_idx++;                i2c->msg++;            }        }        break;    }    /* acknowlegde the IRQ and get back on with the work */ out_ack:    tmp = readl(i2c->regs + S3C2410_IICCON);    tmp &= ~S3C2410_IICCON_IRQPEND;    writel(tmp, i2c->regs + S3C2410_IICCON); out:    return ret;}

中断处理函数s3c24xx_i2c_irq()主要通过调用i2s_s3c_irq_nextbyte()函数进行传输工作的进一步 推进 。i2s_s3c_irq_nextbyte()函数 通过switch(i2c->state)语句分成i2c->state的不同状态进行处理,在每种状态下, 先检查i2c->state的状态与硬件寄存器应该处于的状态是否一致,如果不一致,则证明有误,直接返回。当I2C处于读状态STATE_READ或写状态STATE_WRITE时,通过is_lastmsg()函数判断是否传输的是最后一条I2C消息 ,如果是,则产生停止位,否则通过i2c->msg_idx++i2c->msg++推进到下一条消息。

i2c设备驱动调用图示:
这里写图片描述

未完待续。。。。

1 0
原创粉丝点击