4 總線驅(qū)動(dòng)
4.1 概述
I2C總線驅(qū)動(dòng)是I2C適配器的軟件實(shí)現(xiàn),提供I2C適配器與從設(shè)備間完成數(shù)據(jù)通信的能力,比如起始,停止,應(yīng)答信號(hào)和master_xfer的實(shí)現(xiàn)函數(shù)。
I2C總線驅(qū)動(dòng)由i2c_adapter和i2c_algorithm來(lái)描述
4.2?DM8168 I2C控制器的硬件描述
DM8168處理器內(nèi)部集成了二個(gè)I2C控制器,通過(guò)一下寄存器來(lái)進(jìn)行控制:
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總線驅(qū)動(dòng)分析(platform_driver)
I2C總線驅(qū)動(dòng)代碼在drivers/i2c/busses/i2c-oamp.c,這個(gè)代碼同樣支持其他TI 芯片。
初始化模塊和卸載模塊
/* I2C may be needed to bring up other drivers */static int __initomap_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);
總線驅(qū)動(dòng)是基于platform來(lái)實(shí)現(xiàn)的,很符合設(shè)備驅(qū)動(dòng)模型的思想。
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函數(shù)
當(dāng)調(diào)用platform_driver_register函數(shù)注冊(cè)platform_driver結(jié)構(gòu)體時(shí),如果platformdevice 和 platform driver匹配成功后,會(huì)調(diào)用probe函數(shù),來(lái)初始化適配器硬件。
static int __devinitomap_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? "); return -ENODEV; } irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!irq) { dev_err(&pdev->dev, "no irq resource? "); return -ENODEV; } ioarea = request_mem_region(mem->start, resource_size(mem), pdev->name); if (!ioarea) { dev_err(&pdev->dev, "I2C region already claimed "); 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 ", dev->irq); goto err_unuse_clocks; } dev_info(dev->dev, "bus %d rev%d.%d at %d kHz ", 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 "); 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主要工作是時(shí)能硬件并申請(qǐng)I2C適配器使用的IO地址,中斷號(hào)等,然后向I2C核心添加這個(gè)適配器。I2c_adapter注冊(cè)過(guò)程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函數(shù)是總線通信方式的具體實(shí)現(xiàn),依賴于omap_i2c_wait_for_bb和omap_i2c_xfer_msg兩個(gè)函數(shù);
/* * Prepare controller for a transaction and call omap_i2c_xfer_msg * to do the work during IRQ processing. */static intomap_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;}
首先設(shè)置s3c I2C控制器是否忙,不忙然后調(diào)用omap_i2c_xfer_msg函數(shù)啟動(dòng)I2C消息傳輸。
omap_i2c_func函數(shù)返回適配器所支持的通信功能。
static u32omap_i2c_func(struct i2c_adapter *adap){ return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK);}
4.4 適配器的設(shè)備資源(platform_device)
DM8168的I2C總線驅(qū)動(dòng)是基于platform來(lái)實(shí)現(xiàn),前面我們分析了platform driver部分,再來(lái)看下platform device部分。
在arch/arm/mach-oamp2/omap_hwmod_81xx_data.c文件中定義了platform_device結(jié)構(gòu)體以及I2C控制器的資源信息(注意由于DM8168屬于不對(duì)稱多核架構(gòu),分為l3(內(nèi)部核之間)、l4(外圍設(shè)備與核直接)兩個(gè)總線,通過(guò)ti81xx_hwmod_init->omap_hwmod_init->_register將所有的設(shè)備資源(omap_hwmod結(jié)構(gòu)體)加入到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),};
查找平臺(tái)設(shè)備硬件資源(通過(guò)omap2_i2c_add_bus->omap_hwmod_lookup從omap_hwmod_list鏈表中查到相應(yīng)的硬件資源(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;}
將查找到的硬件資源填充平臺(tái)設(shè)備的resource結(jié)構(gòu)體(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注冊(cè)進(jìn)內(nèi)核(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 ", od->pdev.name); od->pdev.dev.parent = &omap_device_parent; return platform_device_register(&od->pdev);}
調(diào)用platform_device_add_data函數(shù)把適配器具體的數(shù)據(jù)賦值給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總線驅(qū)動(dòng)就分析到這里。
5 客戶驅(qū)動(dòng)
5.1 概述
I2C客戶驅(qū)動(dòng)是對(duì)I2C從設(shè)備的實(shí)現(xiàn),一個(gè)具體的I2C客戶驅(qū)動(dòng)包括兩個(gè)部分:一部分是i2c_driver,用于將設(shè)備掛接于i2c總線;另一部分是設(shè)備本身的驅(qū)動(dòng)。
I2C客戶驅(qū)動(dòng)程序主要由i2c_driver和i2c_client來(lái)描述。
5.2 實(shí)例源碼分析
好了,我們來(lái)深入了解客戶驅(qū)動(dòng)代碼的實(shí)現(xiàn),sound/soc/codesc/tlv320aic3x.c文件。
I2c_driver實(shí)現(xiàn)
/* 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 ", 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函數(shù)
/* * 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 "); 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函數(shù)主要的工作是初始化芯片的控制類型,控制數(shù)據(jù)(i2c_client),并且注冊(cè)解碼器到聲卡。
5.3? I2c_client實(shí)現(xiàn)
tlv320aic3x不依賴于具體的CPU和I2C控制器硬件特性,因此如果電路板包含該外設(shè),只需要添加對(duì)應(yīng)的i2c_board_info,下面是tlv320aic3x i2c_client在板文件中的實(shí)現(xiàn):
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函數(shù)會(huì)把I2C從設(shè)備硬件特性信息注冊(cè)到全局鏈表__i2c_board_list,在調(diào)用i2c_add_adapter函數(shù)時(shí),會(huì)遍歷__i2c_board_list獲得從設(shè)備信息來(lái)構(gòu)造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的構(gòu)建
我們調(diào)用I2c_register_board_info函數(shù)會(huì)把I2C從設(shè)備硬件特性信息注冊(cè)到全局鏈表__i2c_board_list,但是還沒(méi)有構(gòu)建出一個(gè)i2c_client結(jié)構(gòu)體,也沒(méi)有注冊(cè)進(jìn)I2C總線。我們來(lái)分析一下構(gòu)造的過(guò)程,調(diào)用i2c_add_adapter函數(shù)時(shí),會(huì)遍歷__i2c_board_list獲得從設(shè)備信息來(lái)構(gòu)造i2c_client:i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()->device_register()。
5.4? I2c_driver和i2c_client的match
在調(diào)用i2c_add_driver注冊(cè)i2c_driver和構(gòu)建i2c_client時(shí),都會(huì)調(diào)用i2c bus中注冊(cè)的i2c_device_match()->i2c_match_id()函數(shù)通過(guò)i2c_driver->id_table->name和client->name來(lái)匹配(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 測(cè)試
i2c-tools測(cè)試工具可以從http://www.lm-sensors.org/wiki/I2CTools下載,按照http://3sec.kilab.tw/?p=260來(lái)進(jìn)行試驗(yàn),這個(gè)工具可以測(cè)試I2C子系統(tǒng)。
i2c-tools中含有四個(gè)執(zhí)行檔
i2cdetect – 用來(lái)列舉I2C bus和上面所有的裝置
i2cdump – 顯示裝置上所有register的值
i2cget – 讀取裝置上某個(gè)register的值
i2cset – 寫入裝置上某個(gè)register
./i2cdetect -l 查看有多少I2C總線組
./i2cdetect -y -r 1 查看看bus上有那些裝置
總結(jié)
I2c_driver、i2c_client與i2c_adapter
I2c_driver與i2c_client是一對(duì)多的關(guān)系,一個(gè)i2c_driver上可以支持多個(gè)同等類型的i2c_client。調(diào)用i2c_add_driver函數(shù)將I2c_driver注冊(cè)到I2C總線上,調(diào)用i2c_register_board_info函數(shù)將i2c_client注冊(cè)到全局鏈表__i2c_board_list。當(dāng)調(diào)用i2c_add_adapter注冊(cè)適配器時(shí),遍歷__i2c_board_list鏈表,i2c_register_adapter()->i2c_scan_static_board_info()->i2c_new_device()會(huì)構(gòu)建i2c_client結(jié)構(gòu)。當(dāng)調(diào)用i2c_add_driver時(shí),會(huì)先注冊(cè)i2c_driver到I2C總線上,然后調(diào)用I2C BUS注冊(cè)的match函數(shù)進(jìn)行匹配,如果匹配成功,則先調(diào)用I2C BUS中注冊(cè)的probe函數(shù),在調(diào)用i2c_driver中實(shí)現(xiàn)的probe函數(shù),完成相應(yīng)的工作(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控制器驅(qū)動(dòng)開(kāi)發(fā)步驟:
1.通過(guò)platform_device和platform_device_register創(chuàng)建平臺(tái)設(shè)備和資源i2c控制器驅(qū)動(dòng)。
2.通過(guò)platform_driver和platform_driver_register、i2c_add_numbered_adapter實(shí)現(xiàn)。
i2c設(shè)備驅(qū)動(dòng)開(kāi)發(fā)步驟:
1.判斷是寫用戶驅(qū)動(dòng)還是客服驅(qū)動(dòng),客服驅(qū)動(dòng)需要下面的兩步。
2.通過(guò)I2C_BOARD_INFO和i2c_register_board_info來(lái)創(chuàng)建i2c_client,并且綁定i2c適配器(也就是控制器)。
3.通過(guò)i2c_driver和i2c_add_driver實(shí)現(xiàn)i2c客戶驅(qū)動(dòng)。
?
評(píng)論
查看更多