Linux驱动子系统之I2C(二)

4 总线驱动
4.1 概述
I2C总线驱动是I2C适配器的软件实现,提供I2C适配器与从设备间完成数据通信的能力,比如起始,停止,应答信号和master_xfer的实现函数。
I2C总线驱动由i2c_adapter和i2c_algorithm来描述
4.2 DM8168 I2C控制器的硬件描述
DM8168处理器内部集成了二个I2C控制器,通过一下寄存器来进行控制:

const static u8 omap4_reg_map[] = {         
   [OMAP_I2C_REV_REG] = 0x04, [OMAP_I2C_IE_REG] = 0x2c, [OMAP_I2C_STAT_REG] = 0x28, [OMAP_I2C_IV_REG] = 0x34, [OMAP_I2C_WE_REG] = 0x34, [OMAP_I2C_SYSS_REG] = 0x90, [OMAP_I2C_BUF_REG] = 0x94, [OMAP_I2C_CNT_REG] = 0x98, [OMAP_I2C_DATA_REG] = 0x9c, [OMAP_I2C_SYSC_REG] = 0x20, [OMAP_I2C_CON_REG] = 0xa4, [OMAP_I2C_OA_REG] = 0xa8, [OMAP_I2C_SA_REG] = 0xac, [OMAP_I2C_PSC_REG] = 0xb0, [OMAP_I2C_SCLL_REG] = 0xb4, [OMAP_I2C_SCLH_REG] = 0xb8, [OMAP_I2C_SYSTEST_REG] = 0xbC, [OMAP_I2C_BUFSTAT_REG] = 0xc0, [OMAP_I2C_REVNB_LO] = 0x00, [OMAP_I2C_REVNB_HI] = 0x04, [OMAP_I2C_IRQSTATUS_RAW] = 0x24, [OMAP_I2C_IRQENABLE_SET] = 0x2c, [OMAP_I2C_IRQENABLE_CLR] = 0x30, };

4.3 i2c-oamp总线驱动分析(platform_driver)
I2C总线驱动代码在drivers/i2c/busses/i2c-oamp.c,这个代码同样支持其他TI 芯片。
初始化模块和卸载模块

/* I2C may be needed to bring up other drivers */
static int __init
omap_i2c_init_driver(void)
{
    return platform_driver_register(&omap_i2c_driver);
}
subsys_initcall(omap_i2c_init_driver);

static void __exit omap_i2c_exit_driver(void)
{
    platform_driver_unregister(&omap_i2c_driver);
}
module_exit(omap_i2c_exit_driver);

总线驱动是基于platform来实现的,很符合设备驱动模型的思想。

static struct platform_driver omap_i2c_driver = {
    .probe        = omap_i2c_probe,
    .remove        = omap_i2c_remove,
    .driver        = {
        .name    = "omap_i2c",
        .owner    = THIS_MODULE,
    },
};

oamp_i2c_probe函数
当调用platform_driver_register函数注册platform_driver结构体时,如果platformdevice 和 platform driver匹配成功后,会调用probe函数,来初始化适配器硬件。

static int __devinit
omap_i2c_probe(struct platform_device *pdev)
{
    struct omap_i2c_dev    *dev;
    struct i2c_adapter    *adap;
    struct resource        *mem, *irq, *ioarea;
    struct omap_i2c_bus_platform_data *pdata = pdev->dev.platform_data;
    irq_handler_t isr;
    int r;
    u32 speed = 0;    

    /* NOTE: driver uses the static register mapping */
    mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!mem) {
        dev_err(&pdev->dev, "no mem resource?\n");
        return -ENODEV;
    }
    irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
    if (!irq) {
        dev_err(&pdev->dev, "no irq resource?\n");
        return -ENODEV;
    }

    ioarea = request_mem_region(mem->start, resource_size(mem),
            pdev->name);
    if (!ioarea) {
        dev_err(&pdev->dev, "I2C region already claimed\n");
        return -EBUSY;
    }

    dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL);
    if (!dev) {
        r = -ENOMEM;
        goto err_release_region;
    }

    if (pdata != NULL) {
        speed = pdata->clkrate;
        dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
    } else {
        speed = 100;    /* Default speed */
        dev->set_mpu_wkup_lat = NULL;
    }

    dev->speed = speed;
    dev->idle = 1;
    dev->dev = &pdev->dev;
    dev->irq = irq->start;
    dev->base = ioremap(mem->start, resource_size(mem));
    if (!dev->base) {
        r = -ENOMEM;
        goto err_free_mem;
    }

    platform_set_drvdata(pdev, dev);

    if (cpu_is_omap7xx())
        dev->reg_shift = 1;
    else if (cpu_is_omap44xx() || cpu_is_ti81xx())
        dev->reg_shift = 0;
    else
        dev->reg_shift = 2;

    if (cpu_is_omap44xx() || cpu_is_ti81xx())
        dev->regs = (u8 *) omap4_reg_map;
    else
        dev->regs = (u8 *) reg_map;

    pm_runtime_enable(&pdev->dev);
    omap_i2c_unidle(dev);

    dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) & 0xff;

    if (dev->rev <= OMAP_I2C_REV_ON_3430)
        dev->errata |= I2C_OMAP3_1P153;

    if (!(cpu_class_is_omap1() || cpu_is_omap2420())) {
        u16 s;

        /* Set up the fifo size - Get total size */
        s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> 14) & 0x3;
        dev->fifo_size = 0x8 << s;

        /*
         * Set up notification threshold as half the total available
         * size. This is to ensure that we can handle the status on int
         * call back latencies.
         */
        if (dev->rev >= OMAP_I2C_REV_ON_4430) {
            dev->fifo_size = 0;
            dev->b_hw = 0; /* Disable hardware fixes */
        } else {
            dev->fifo_size = (dev->fifo_size / 2);
            dev->b_hw = 1; /* Enable hardware fixes */
        }
        /* calculate wakeup latency constraint for MPU */
        if (dev->set_mpu_wkup_lat != NULL)
            dev->latency = (1000000 * dev->fifo_size) /
                       (1000 * speed / 8);
    }

    /* reset ASAP, clearing any IRQs */
    omap_i2c_init(dev);

    isr = (dev->rev < OMAP_I2C_REV_2) ? omap_i2c_rev1_isr : omap_i2c_isr;
    r = request_irq(dev->irq, isr, 0, pdev->name, dev);

    if (r) {
        dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
        goto err_unuse_clocks;
    }

    dev_info(dev->dev, "bus %d rev%d.%d at %d kHz\n",
         pdev->id, dev->rev >> 4, dev->rev & 0xf, dev->speed);

    omap_i2c_idle(dev);

    adap = &dev->adapter;
    i2c_set_adapdata(adap, dev);
    adap->owner = THIS_MODULE;
    adap->class = I2C_CLASS_HWMON;
    strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
    adap->algo = &omap_i2c_algo;
    adap->dev.parent = &pdev->dev;

    /* i2c device drivers may be active on return from add_adapter() */
    adap->nr = pdev->id;
    r = i2c_add_numbered_adapter(adap);
    if (r) {
        dev_err(dev->dev, "failure adding adapter\n");
        goto err_free_irq;
    }

    return 0;

err_free_irq:
    free_irq(dev->irq, dev);
err_unuse_clocks:
    omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0);
    omap_i2c_idle(dev);
    iounmap(dev->base);
err_free_mem:
    platform_set_drvdata(pdev, NULL);
    kfree(dev);
err_release_region:
    release_mem_region(mem->start, resource_size(mem));

    return r;
}

Probe主要工作是时能硬件并申请I2C适配器使用的IO地址,中断号等,然后向I2C核心添加这个适配器。I2c_adapter注册过程i2c_add_numbered_adapter->i2c_register_adapter
I2C总线通信方法

static const struct i2c_algorithm omap_i2c_algo = {
    .master_xfer    = omap_i2c_xfer,
    .functionality    = omap_i2c_func,
};

oamp_i2c_xfer函数是总线通信方式的具体实现,依赖于omap_i2c_wait_for_bb和omap_i2c_xfer_msg两个函数;

/*
 * Prepare controller for a transaction and call omap_i2c_xfer_msg
 * to do the work during IRQ processing.
 */
static int
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)
{
    struct omap_i2c_dev *dev = i2c_get_adapdata(adap);
    int i;
    int r;

    omap_i2c_unidle(dev);

    r = omap_i2c_wait_for_bb(dev);
    if (r < 0)
        goto out;

    for (i = 0; i < num; i++) {
        r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1)));
        if (r != 0)
            break;
    }

    if (r == 0)
        r = num;

    omap_i2c_wait_for_bb(dev);
out:
    omap_i2c_idle(dev);
    return r;
}

首先设置s3c I2C控制器是否忙,不忙然后调用omap_i2c_xfer_msg函数启动I2C消息传输。
omap_i2c_func函数返回适配器所支持的通信功能。

static u32
omap_i2c_func(struct i2c_adapter *adap)
{
    return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);
}

4.4 适配器的设备资源(platform_device)
DM8168的I2C总线驱动是基于platform来实现,前面我们分析了platform driver部分,再来看下platform device部分。
在arch/arm/mach-oamp2/omap_hwmod_81xx_data.c文件中定义了platform_device结构体以及I2C控制器的资源信息(注意由于DM8168属于不对称多核架构,分为l3(内部核之间)、l4(外围设备与核直接)两个总线,通过ti81xx_hwmod_init->omap_hwmod_init->_register将所有的设备资源(omap_hwmod结构体)加入到omap_hwmod_list链表中):

static struct omap_hwmod ti81xx_i2c1_hwmod = {
    .name        = "i2c1",
    .mpu_irqs    = i2c1_mpu_irqs,
    .mpu_irqs_cnt    = ARRAY_SIZE(i2c1_mpu_irqs),
    .sdma_reqs    = i2c1_edma_reqs,
    .sdma_reqs_cnt    = ARRAY_SIZE(i2c1_edma_reqs),
    .main_clk    = "i2c1_fck",
    .prcm        = {
        .omap4 = {
            .clkctrl_reg = TI816X_CM_ALWON_I2C_0_CLKCTRL,
        },
    },
    .slaves        = ti816x_i2c1_slaves,
    .slaves_cnt    = ARRAY_SIZE(ti816x_i2c1_slaves),
    .class        = &i2c_class,
    .omap_chip    = OMAP_CHIP_INIT(CHIP_IS_TI81XX),
};
///////////////////////////
static struct omap_hwmod ti816x_i2c2_hwmod = {
    .name           = "i2c2",
    .mpu_irqs       = i2c2_mpu_irqs,
    .mpu_irqs_cnt   = ARRAY_SIZE(i2c2_mpu_irqs),
    .sdma_reqs      = i2c2_edma_reqs,
    .sdma_reqs_cnt  = ARRAY_SIZE(i2c2_edma_reqs),
    .main_clk       = "i2c2_fck",
    .prcm           = {
        .omap4 = {
            .clkctrl_reg = TI816X_CM_ALWON_I2C_1_CLKCTRL,
        },
    },
    .slaves         = ti816x_i2c2_slaves,
    .slaves_cnt     = ARRAY_SIZE(ti816x_i2c2_slaves),
    .class          = &i2c_class,
    .omap_chip      = OMAP_CHIP_INIT(CHIP_IS_TI816X),
};

查找平台设备硬件资源(通过omap2_i2c_add_bus->omap_hwmod_lookup从omap_hwmod_list链表中查到相应的硬件资源(omap_hwmod)):

/**
 * omap_hwmod_lookup - look up a registered omap_hwmod by name
 * @name: name of the omap_hwmod to look up
 *
 * Given a @name of an omap_hwmod, return a pointer to the registered
 * struct omap_hwmod *, or NULL upon error.
 */
struct omap_hwmod *omap_hwmod_lookup(const char *name)
{
    struct omap_hwmod *oh;

    if (!name)
        return NULL;

    oh = _lookup(name);

    return oh;
}

将查找到的硬件资源填充平台设备的resource结构体(omap2_i2c_add_bus->omap_device_build->omap_device_build_ss->omap_device_fill_resources->omap_hwmod_fill_resources):

/**
 * omap_hwmod_fill_resources - fill struct resource array with hwmod data
 * @oh: struct omap_hwmod *
 * @res: pointer to the first element of an array of struct resource to fill
 *
 * Fill the struct resource array @res with resource data from the
 * omap_hwmod @oh.  Intended to be called by code that registers
 * omap_devices.  See also omap_hwmod_count_resources().  Returns the
 * number of array elements filled.
 */
int omap_hwmod_fill_resources(struct omap_hwmod *oh, struct resource *res)
{
    int i, j;
    int r = 0;

    /* For each IRQ, DMA, memory area, fill in array.*/

    for (i = 0; i < oh->mpu_irqs_cnt; i++) {
        (res + r)->name = (oh->mpu_irqs + i)->name;
        (res + r)->start = (oh->mpu_irqs + i)->irq;
        (res + r)->end = (oh->mpu_irqs + i)->irq;
        (res + r)->flags = IORESOURCE_IRQ;
        r++;
    }

    for (i = 0; i < oh->sdma_reqs_cnt; i++) {
        (res + r)->name = (oh->sdma_reqs + i)->name;
        (res + r)->start = (oh->sdma_reqs + i)->dma_req;
        (res + r)->end = (oh->sdma_reqs + i)->dma_req;
        (res + r)->flags = IORESOURCE_DMA;
        r++;
    }

    for (i = 0; i < oh->slaves_cnt; i++) {
        struct omap_hwmod_ocp_if *os;

        os = oh->slaves[i];

        for (j = 0; j < os->addr_cnt; j++) {
            (res + r)->name = (os->addr + j)->name;
            (res + r)->start = (os->addr + j)->pa_start;
            (res + r)->end = (os->addr + j)->pa_end;
            (res + r)->flags = IORESOURCE_MEM;
            r++;
        }
    }

    return r;
}

在板文件中把platform_device注册进内核(omap2_i2c_add_bus->omap_device_build->omap_device_build_ss->omap_device_register->platform_device_register):

/**
 * omap_device_register - register an omap_device with one omap_hwmod
 * @od: struct omap_device * to register
 *
 * Register the omap_device structure.  This currently just calls
 * platform_device_register() on the underlying platform_device.
 * Returns the return value of platform_device_register().
 */
int omap_device_register(struct omap_device *od)
{
    pr_debug("omap_device: %s: registering\n", od->pdev.name);

    od->pdev.dev.parent = &omap_device_parent;
    return platform_device_register(&od->pdev);
}

调用platform_device_add_data函数把适配器具体的数据赋值给dev.platform_data(omap2_i2c_add_bus->omap_device_build->omap_device_build_ss->platform_device_add_data):

/**
 * platform_device_add_data - add platform-specific data to a platform device
 * @pdev: platform device allocated by platform_device_alloc to add resources to
 * @data: platform specific data for this platform device
 * @size: size of platform specific data
 *
 * Add a copy of platform specific data to the platform device's
 * platform_data pointer.  The memory associated with the platform data
 * will be freed when the platform device is released.
 */
int platform_device_add_data(struct platform_device *pdev, const void *data,
                 size_t size)
{
    void *d;

    if (!data)
        return 0;

    d = kmemdup(data, size, GFP_KERNEL);
    if (d) {
        pdev->dev.platform_data = d;
        return 0;
    }
    return -ENOMEM;
}
EXPORT_SYMBOL_GPL(platform_device_add_data);

I2C总线驱动就分析到这里。

5 客户驱动
5.1 概述
I2C客户驱动是对I2C从设备的实现,一个具体的I2C客户驱动包括两个部分:一部分是i2c_driver,用于将设备挂接于i2c总线;另一部分是设备本身的驱动。
I2C客户驱动程序主要由i2c_driver和i2c_client来描述。
5.2 实例源码分析
好了,我们来深入了解客户驱动代码的实现,sound/soc/codesc/tlv320aic3x.c文件。
I2c_driver实现

/* machine i2c codec control layer */
static struct i2c_driver aic3x_i2c_driver = {
    .driver = {
        .name = "tlv320aic3x-codec",
        .owner = THIS_MODULE,
    },
    .probe    = aic3x_i2c_probe,
    .remove = aic3x_i2c_remove,
    .id_table = aic3x_i2c_id,
};

初始化和卸载

static int __init aic3x_modinit(void)
{
    int ret = 0;
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
    ret = i2c_add_driver(&aic3x_i2c_driver);
    if (ret != 0) {
        printk(KERN_ERR "Failed to register TLV320AIC3x I2C driver: %d\n",
               ret);
    }
#endif
    return ret;
}
module_init(aic3x_modinit);

static void __exit aic3x_exit(void)
{
#if defined(CONFIG_I2C) || defined(CONFIG_I2C_MODULE)
    i2c_del_driver(&aic3x_i2c_driver);
#endif
}
module_exit(aic3x_exit);

aic3x_i2c_Probe函数

/*
 * If the i2c layer weren't so broken, we could pass this kind of data
 * around
 */
static int aic3x_i2c_probe(struct i2c_client *i2c,
               const struct i2c_device_id *id)
{
    struct aic3x_pdata *pdata = i2c->dev.platform_data;
    struct aic3x_priv *aic3x;
    int ret;
    const struct i2c_device_id *tbl;

    aic3x = kzalloc(sizeof(struct aic3x_priv), GFP_KERNEL);
    if (aic3x == NULL) {
        dev_err(&i2c->dev, "failed to create private data\n");
        return -ENOMEM;
    }

    aic3x->control_data = i2c;
    aic3x->control_type = SND_SOC_I2C;

    i2c_set_clientdata(i2c, aic3x);
    if (pdata) {
        aic3x->gpio_reset = pdata->gpio_reset;
        aic3x->setup = pdata->setup;
    } else {
        aic3x->gpio_reset = -1;
    }

    for (tbl = aic3x_i2c_id; tbl->name[0]; tbl++) {
        if (!strcmp(tbl->name, id->name))
            break;
    }
    aic3x->model = tbl - aic3x_i2c_id;

    ret = snd_soc_register_codec(&i2c->dev,
            &soc_codec_dev_aic3x, &aic3x_dai, 1);
    if (ret < 0)
        kfree(aic3x);
    return ret;
}

Probe函数主要的工作是初始化芯片的控制类型,控制数据(i2c_client),并且注册解码器到声卡。
5.3  I2c_client实现
tlv320aic3x不依赖于具体的CPU和I2C控制器硬件特性,因此如果电路板包含该外设,只需要添加对应的i2c_board_info,下面是tlv320aic3x i2c_client在板文件中的实现:

static struct i2c_board_info __initdata ti816x_i2c_boardinfo0[] = {
    {
        I2C_BOARD_INFO("tlv320aic3x", 0x18),
    },
    {
        I2C_BOARD_INFO("ds1337", 0x68),
    },
    #ifdef CONFIG_REGULATOR_TPS40400
    {
        I2C_BOARD_INFO("pmbus", 0x38),
        .platform_data    = &pmbus_pmic_init_data,
    },
    #endif
    {
        I2C_BOARD_INFO("24c256",0x00),  //  
}, };

I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,在调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client(omap_register_i2c_bus->omap_register_i2c_bus)。

/**
 * omap_register_i2c_bus - register I2C bus with device descriptors
 * @bus_id: bus id counting from number 1
 * @clkrate: clock rate of the bus in kHz
 * @info: pointer into I2C device descriptor table or NULL
 * @len: number of descriptors in the table
 *
 * Returns 0 on success or an error code.
 */
int __init omap_register_i2c_bus(int bus_id, u32 clkrate,
              struct i2c_board_info const *info,
              unsigned len)
{
    int err;

    BUG_ON(bus_id < 1 || bus_id > omap_i2c_nr_ports());

    if (info) {
        err = i2c_register_board_info(bus_id, info, len);
        if (err)
            return err;
    }

    if (!i2c_pdata[bus_id - 1].clkrate)
        i2c_pdata[bus_id - 1].clkrate = clkrate;

    i2c_pdata[bus_id - 1].clkrate &= ~OMAP_I2C_CMDLINE_SETUP;

    return omap_i2c_add_bus(bus_id);
}

I2c_client的构建
我们调用I2c_register_board_info函数会把I2C从设备硬件特性信息注册到全局链表__i2c_board_list,但是还没有构建出一个i2c_client结构体,也没有注册进I2C总线。我们来分析一下构造的过程,调用i2c_add_adapter函数时,会遍历__i2c_board_list获得从设备信息来构造i2c_client:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。
5.4  I2c_driver和i2c_client的match
在调用i2c_add_driver注册i2c_driver和构建i2c_client时,都会调用i2c bus中注册的i2c_device_match()->i2c_match_id()函数通过i2c_driver->id_table->name和client->name来匹配(i2c_add_driver->i2c_register_driver->driver_register->bus_add_driver->driver_attach->__driver_attach->driver_match_device->i2c_device_match->i2c_match_id)

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}

5.5 测试
i2c-tools测试工具可以从http://www.lm-sensors.org/wiki/I2CTools下载,按照http://3sec.kilab.tw/?p=260来进行试验,这个工具可以测试I2C子系统。
i2c-tools中含有四個執行檔
i2cdetect – 用來列舉I2C bus和上面所有的裝置
i2cdump – 顯示裝置上所有register的值
i2cget – 讀取裝置上某個register的值
i2cset – 寫入裝置上某個register
./i2cdetect -l 查看有多少I2C总线组
./i2cdetect -y -r 1 查看看bus上有那些裝置

总结
I2c_driver、i2c_client与i2c_adapter
I2c_driver与i2c_client是一对多的关系,一个i2c_driver上可以支持多个同等类型的i2c_client。调用i2c_add_driver函数将I2c_driver注册到I2C总线上,调用i2c_register_board_info函数将i2c_client注册到全局链表__i2c_board_list。当调用i2c_add_adapter注册适配器时,遍历__i2c_board_list链表,i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()会构建i2c_client结构。当调用i2c_add_driver时,会先注册i2c_driver到I2C总线上,然后调用I2C BUS注册的match函数进行匹配,如果匹配成功,则先调用I2C BUS中注册的probe函数,在调用i2c_driver中实现的probe函数,完成相应的工作(i2c_add_numbered_adapter->i2c_register_adapter->i2c_scan_static_board_info->i2c_new_device->device_register->device_attach->__device_attach->driver_probe_device->really_probe->dev->bus->probe、drv->probe)。
i2c控制器驱动开发步骤:
1.通过platform_device和platform_device_register创建平台设备和资源i2c控制器驱动。
2.通过platform_driver和platform_driver_register、i2c_add_numbered_adapter实现。
i2c设备驱动开发步骤:
1.判断是写用户驱动还是客服驱动,客服驱动需要下面的两步。
2.通过I2C_BOARD_INFO和i2c_register_board_info来创建i2c_client,并且绑定i2c适配器(也就是控制器)。
3.通过i2c_driver和i2c_add_driver实现i2c客户驱动。

posted on 2013-06-14 11:28  CSlunatic  阅读(1818)  评论(0编辑  收藏  举报

导航