zoukankan      html  css  js  c++  java
  • 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客户驱动。

    嵌入式QQ交流群:127085086
  • 相关阅读:
    C#解压或压缩文件夹
    C#文件帮助类FoderHelper
    C#数据库帮助类SqlHelper
    非常有意思的程序猿数据比較
    hdu1066
    【SICP练习】149 练习4.5
    【spring+websocket的使用】
    Android Studio 使用正式签名进行调试
    基于空间直方图meanshift跟踪
    Intel为Google的物联网平台Brillo推出开发板Edison
  • 原文地址:https://www.cnblogs.com/cslunatic/p/3135265.html
Copyright © 2011-2022 走看看