嵌入式Linux驱动笔记(十)------通俗易懂式分析了解i2c框架

你好!这里是风筝的博客,

欢迎和我一起交流。

了解i2c框架时,在别的博客看到一张图,非常好,引用如下:
i2c描述

其中,i2c_client是具体的设备实例,是通过i2c总线连接到i2c_adapter的。无论是什么i2c设备,都可以通过i2c_adapter来访问i2c总线,i2c_adapter屏蔽了底层i2c总线控制时序,向上层提供一个统一的接口。
以kernel4.8.17为例:
在mach-smdk2440.c文件,

static struct platform_device *smdk2440_devices[] __initdata = {
    &s3c_device_ohci,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c0,
    &s3c_device_iis,
    &smdk2440_device_eth,
};

static void __init smdk2440_map_io(void)
{
    s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
    s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
    samsung_set_timer_source(SAMSUNG_PWM3, SAMSUNG_PWM4);
}

static void __init smdk2440_machine_init(void)
{
    s3c24xx_fb_set_platdata(&smdk2440_fb_info);
    s3c_i2c0_set_platdata(NULL);

    platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
    smdk_machine_init();
}

smdk2440_map_io函数里,调用s3c24xx_init_io函数:

void __init s3c24xx_init_io(struct map_desc *mach_desc, int size)
{
    arm_pm_idle = s3c24xx_default_idle;

    /* initialise the io descriptors we need for initialisation */
    iotable_init(mach_desc, size);
    iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc));

    if (cpu_architecture() >= CPU_ARCH_ARMv5) {
        samsung_cpu_id = s3c24xx_read_idcode_v5();
    } else {
        samsung_cpu_id = s3c24xx_read_idcode_v4();
    }

    s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));

    samsung_pwm_set_platdata(&s3c24xx_pwm_variant);
}

其中有s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids)),
参数cpu_ids是:

static struct cpu_table cpu_ids[] __initdata = {
    /*太多了,省略......*/
    {
        .idcode     = 0x32440000,
        .idmask     = 0xffffffff,
        .map_io     = s3c2440_map_io,
        .init_uarts = s3c244x_init_uarts,
        .init       = s3c2440_init,
        .name       = name_s3c2440
    },
    /*太多了,省略......*/

继续跟踪s3c_init_cpu函数

void __init s3c_init_cpu(unsigned long idcode,
             struct cpu_table *cputab, unsigned int cputab_size)
{
    cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);

    if (cpu == NULL) {
        printk(KERN_ERR "Unknown CPU type 0x%08lx\n", idcode);
        panic("Unknown S3C24XX CPU");
    }

    printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode);

    if (cpu->init == NULL) {
        printk(KERN_ERR "CPU %s support not enabled\n", cpu->name);
        panic("Unsupported Samsung CPU");
    }

    if (cpu->map_io)
        cpu->map_io();
}

就是最后一行,会调用map_io函数,即前面的s3c2440_map_io函数

void __init s3c2440_map_io(void)
{
    s3c244x_map_io();
    s3c24xx_gpiocfg_default.set_pull = s3c24xx_gpio_setpull_1up;
    s3c24xx_gpiocfg_default.get_pull = s3c24xx_gpio_getpull_1up;
}

进入s3c244x_map_io函数

void __init s3c244x_map_io(void)
{
    /* register our io-tables */
    iotable_init(s3c244x_iodesc, ARRAY_SIZE(s3c244x_iodesc));
    /* rename any peripherals used differing from the s3c2410 */
    s3c_device_sdi.name  = "s3c2440-sdi";
    s3c_device_i2c0.name  = "s3c2440-i2c";
    s3c_nand_setname("s3c2440-nand");
    s3c_device_ts.name = "s3c2440-ts";
    s3c_device_usbgadget.name = "s3c2440-usbgadget";
    s3c2410_device_dclk.name = "s3c2440-dclk";
}

这里,即是把s3c_device_i2c0结构体的名字改为了”s3c2440-i2c” !!!
好了,回到文章最开头的mach-smdk2440.c文件,看下smdk2440_machine_init函数,
里面会通过s3c_i2c0_set_platdata函数,设置default_i2c_data结构体的bus_num为0,以及设置i2c的IO口:npd->cfg_gpio = s3c_i2c0_cfg_gpio;
接着就会调用platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));在platform平台下进行设备注册,设备名字为”s3c2440-i2c”

有了platform-device,自然有driver
在i2c-s3c2410.c文件:

static const struct platform_device_id s3c24xx_driver_ids[] = {
    {
        .name       = "s3c2410-i2c",
        .driver_data    = 0,
    }, {
        .name       = "s3c2440-i2c",
        .driver_data    = QUIRK_S3C2440,
    }, {
        .name       = "s3c2440-hdmiphy-i2c",
        .driver_data    = QUIRK_S3C2440 | QUIRK_HDMIPHY | QUIRK_NO_GPIO,
    }, { },
};

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

可以看到,s3c24xx_driver_ids里是有”s3c2440-i2c”的,所以能和之前的device匹配成功,调用probe函数:

static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
    /*太长了,部分省略......*/
    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;
    /* setup info block for the i2c core */
    i2c->adap.algo_data = i2c;
    i2c->adap.dev.parent = &pdev->dev;
    i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);

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

这里主要注意两个地方:
一是:i2c->adap.algo = &s3c24xx_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,
};

还记得文章开始的那张图吗?
i2c_adapter和i2c_algorithm 都是操作i2c bus的结构体,前者定义一个i2c模块,后者定义操作模块的方法。(或者理解为:i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。)
这里就是i2c_algorithm!!
i2c的底层实现函数,进行封装好,
.master_xfer 用于i2c总线传输,传递给它的i2c_msg数组中每个I2C消息。
.functionality 用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SUMBUS_WRITE_BYTE等。

二是:ret = i2c_add_numbered_adapter(&i2c->adap);
这就是i2c_adapter了,进去看下函数实现:

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    if (adap->nr == -1) /* -1 means dynamically assign bus id */
        return i2c_add_adapter(adap);

    return __i2c_add_numbered_adapter(adap);
}
static int __i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    int id;

    mutex_lock(&core_lock);
    id = idr_alloc(&i2c_adapter_idr, adap, adap->nr, adap->nr + 1, GFP_KERNEL);
    mutex_unlock(&core_lock);
    if (WARN(id < 0, "couldn't get idr"))
        return id == -ENOSPC ? -EBUSY : id;

    return i2c_register_adapter(adap);
}

即最后调用i2c_register_adapter(adap)函数,在i2c_bus总线上注册,名字为:
dev_set_name(&adap->dev, “i2c-%d”, adap->nr);
这里说下i2c_adapter与i2c_client的关系:i2c_client依附于i2c_adapter,由于一个适配器上可以连接多个I2C设备,所以一个i2c_adapter也可以被多个i2c_client依附,i2c_adapter中包含依附于它的i2c_client的链表。

.

好咯,i2c框架差不多就是这样咯,我们以一个kernel里的例子来看下:
at24.c函数:

static struct i2c_driver at24_driver = {
    .driver = {
        .name = "at24",
        .acpi_match_table = ACPI_PTR(at24_acpi_ids),
    },
    .probe = at24_probe,
    .remove = at24_remove,
    .id_table = at24_ids,
};

static int __init at24_init(void)
{
    if (!io_limit) {
        pr_err("at24: io_limit must not be 0!\n");
        return -EINVAL;
    }

    io_limit = rounddown_pow_of_two(io_limit);
    return i2c_add_driver(&at24_driver);
}

这里调用i2c_add_driver函数在i2c_bus总线下注册,然后看下他的读写函数,以读函数为例:

static ssize_t at24_eeprom_read_i2c(struct at24_data *at24, char *buf,
                    unsigned int offset, size_t count)
{
    unsigned long timeout, read_time;
    struct i2c_client *client;
    struct i2c_msg msg[2];
    int status, i;
    u8 msgbuf[2];

    memset(msg, 0, sizeof(msg));
    client = at24_translate_offset(at24, &offset);

    if (count > io_limit)
        count = io_limit;
    i = 0;
    if (at24->chip.flags & AT24_FLAG_ADDR16)
        msgbuf[i++] = offset >> 8;
    msgbuf[i++] = offset;

    msg[0].addr = client->addr;
    msg[0].buf = msgbuf;
    msg[0].len = i;

    msg[1].addr = client->addr;
    msg[1].flags = I2C_M_RD;
    msg[1].buf = buf;
    msg[1].len = count;

    loop_until_timeout(timeout, read_time) {
        status = i2c_transfer(client->adapter, msg, 2);
        if (status == 2)
            status = count;

        dev_dbg(&client->dev, "read %zu@%d --> %d (%ld)\n",
                count, offset, status, jiffies);

        if (status == count)
            return count;
    }

    return -ETIMEDOUT;
}

里面就是会调用到i2c_transfer函数了,函数里面以i2c_msg(即I2C消息)为单位通信,i2c_transfer函数里又会调用到__i2c_transfer函数:

int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    unsigned long orig_jiffies;
    int ret, try;

    if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))
        return -EOPNOTSUPP;
    if (static_key_false(&i2c_trace_msg)) {
        int i;
        for (i = 0; i < num; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_read(adap, &msgs[i], i);
            else
                trace_i2c_write(adap, &msgs[i], i);
    }
    /* Retry automatically on arbitration loss */
    orig_jiffies = jiffies;
    for (ret = 0, try = 0; try <= adap->retries; try++) {
        ret = adap->algo->master_xfer(adap, msgs, num);
        if (ret != -EAGAIN)
            break;
        if (time_after(jiffies, orig_jiffies + adap->timeout))
            break;
    }

    if (static_key_false(&i2c_trace_msg)) {
        int i;
        for (i = 0; i < ret; i++)
            if (msgs[i].flags & I2C_M_RD)
                trace_i2c_reply(adap, &msgs[i], i);
        trace_i2c_result(adap, i, ret);
    }

    return ret;
}

就是这里,里面实现:ret = adap->algo->master_xfer(adap, msgs, num);
这就是之前说的i2c_algorithm 里实现的.master_xfer函数里,实现i2c总线传输函数。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值