Android学习之 Platform总线 2
来源:互联网 发布:matlab 矩阵怎么转置 编辑:程序博客网 时间:2024/06/16 18:52
1 基于Platform总线的驱动开发流程
·定义初始化platform bus
·定义各种platform devices
·注册各种platform devices
·定义相关platform driver
·注册相关platform driver
·操作相关设备
基于Mx53QSB为例,实现流程如下:
1.1 初始化platform_bus
初始化代码在kernel_imx/drivers/base/platform.c中#L28,
struct device platform_bus = {
.init_name = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);
int __init platform_bus_init(void) #L1020
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
该函数创建了一个名为“platform”的设备,后续platform的设备都会以此为parent,在sysfs中表示为:所有platform类型的设备都会添加在platform_bus所代表的目录下,即/sys/devices/platform。
Platform_bus必须在系统注册任何platform driver和platform device之前初始化,实现如下:
Kernel_imx/drivers/base/Init.c #L20
/**
* driver_init - initialize driver model.
*
* Call the driver model init functions to initialize their
* subsystems. Called early from init/main.c.
*/
void __init driver_init(void)
{
/* These are the core pieces */
devtmpfs_init();
devices_init();
buses_init();
classes_init();
firmware_init();
hypervisor_init();
/* These are also core pieces, but must come after the
* core core pieces.
*/
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
}
start_kernel(init/main.c#L539) >>rest_init(init/main.c#L429)>>kenel_init(init/main.c#L866)>>do_basic_setup(init/Main.c#L797)>>driver_init>>platform_bus_init
platform driver 和platform devices 的初始化是在do_initcalls(do_basic_setup(init/Main.c#L806处调用))中进行的的。
1.2 定义platform_device
Kernel_imx/arch/arm/mach-mx5/Devices.c #L649中定义了系统的资源,大部分板级资源都在这里集中定义。
static struct resource mxci2c1_resources[] = {
{
.start = I2C1_BASE_ADDR,
.end = I2C1_BASE_ADDR + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
{
.start = MXC_INT_I2C1,
.end = MXC_INT_I2C1,
.flags = IORESOURCE_IRQ,
},
};
static struct resource mxci2c2_resources[] = {
{
.start = I2C2_BASE_ADDR,
.end = I2C2_BASE_ADDR + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
{
.start = MXC_INT_I2C2,
.end = MXC_INT_I2C2,
.flags = IORESOURCE_IRQ,
},
};
static struct resource mxci2c3_resources[] = {
{
.start = I2C3_BASE_ADDR,
.end = I2C3_BASE_ADDR + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
{
.start = MXC_INT_I2C3,
.end = MXC_INT_I2C3,
.flags = IORESOURCE_IRQ,
},
};
struct platform_device mxci2c_devices[] = {
{
.name = "imx-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(mxci2c1_resources),
.resource = mxci2c1_resources,
},
{
.name = "imx-i2c",
.id = 1,
.num_resources = ARRAY_SIZE(mxci2c2_resources),
.resource = mxci2c2_resources,
},
{
.name = "imx-i2c",
.id = 2,
.num_resources = ARRAY_SIZE(mxci2c3_resources),
.resource = mxci2c3_resources,
},
};
设备名称为imx-i2c,.id说明设备的序号,共有三个, mxci2c1_resources,mxci2c2_resources,mxci2c3_resources分别对应设备的资源,资源包括I2c控制器的寄存器空间和中断信息。
1.3 注册platform_device
定义了platflat_device后,需要添加到系统中,就可以调用函数mxc_register_device。
Kernel_imx/arch /arm/plat-mxc.c#L108
int __init mxc_register_device(struct platform_device *pdev, void *data)
{
int ret;
pdev->dev.platform_data = data;
ret = platform_device_register(pdev);
if (ret)
pr_debug("Unable to register platform device '%s': %d/n",
pdev->name, ret);
return ret;
}
在mxc_board_init()函数中,调用mxc_register_device对一些外设进行注册。
static void __init mxc_board_init(void)
{
mxc_ipu_data.di_clk[0] = clk_get(NULL, "ipu_di0_clk");
mxc_ipu_data.di_clk[1] = clk_get(NULL, "ipu_di1_clk");
mxc_ipu_data.csi_clk[0] = clk_get(NULL, "ssi_ext1_clk");
mxc_spdif_data.spdif_core_clk = clk_get(NULL, "spdif_xtal_clk");
clk_put(mxc_spdif_data.spdif_core_clk);
mxcsdhc3_device.resource[2].start = IOMUX_TO_IRQ_V3(SD3_CD);
mxcsdhc3_device.resource[2].end = IOMUX_TO_IRQ_V3(SD3_CD);
mxc_cpu_common_init();
mx53_loco_io_init();
mxc_register_device(&mxc_dma_device, NULL);
mxc_register_device(&mxc_wdt_device, NULL);
mxc_register_device(&mxci2c_devices[0], &mxci2c_data);
mxc_register_device(&mxci2c_devices[1], &mxci2c_data);
mx53_loco_init_da9052();
mxc_register_device(&mxc_rtc_device, NULL);
mxc_register_device(&mxc_ipu_device, &mxc_ipu_data);
mxc_register_device(&mxc_ldb_device, &ldb_data);
mxc_register_device(&mxc_tve_device, &tve_data);
mxc_register_device(&mxcvpu_device, &mxc_vpu_data);
mxc_register_device(&gpu_device, &z160_revision);
mxc_register_device(&mxcscc_device, NULL);
mxc_register_device(&mxc_dvfs_core_device, &dvfs_core_data);
mxc_register_device(&busfreq_device, &bus_freq_data);
mxc_register_device(&mxc_iim_device, &iim_data);
mxc_register_device(&mxc_pwm2_device, NULL);
mxc_register_device(&mxc_pwm1_backlight_device, &mxc_pwm_backlight_data);
mxc_register_device(&mxcsdhc1_device, &mmc1_data);
mxc_register_device(&mxcsdhc3_device, &mmc3_data);
mxc_register_device(&mxc_ssi1_device, NULL);
mxc_register_device(&mxc_ssi2_device, NULL);
mxc_register_device(&mxc_alsa_spdif_device, &mxc_spdif_data);
mxc_register_device(&ahci_fsl_device, &sata_data);
mxc_register_device(&mxc_fec_device, &fec_data);
/* ASRC is only available for MX53 TO2.0 */
if (cpu_is_mx53_rev(CHIP_REV_2_0) >= 1) {
mxc_asrc_data.asrc_core_clk = clk_get(NULL, "asrc_clk");
clk_put(mxc_asrc_data.asrc_core_clk);
mxc_asrc_data.asrc_audio_clk = clk_get(NULL, "asrc_serial_clk");
clk_set_rate(mxc_asrc_data.asrc_audio_clk, 1190000);
clk_put(mxc_asrc_data.asrc_audio_clk);
mxc_register_device(&mxc_asrc_device, &mxc_asrc_data);
}
i2c_register_board_info(0, mxc_i2c0_board_info,
ARRAY_SIZE(mxc_i2c0_board_info));
i2c_register_board_info(1, mxc_i2c1_board_info,
ARRAY_SIZE(mxc_i2c1_board_info));
mxc_register_device(&mxc_sgtl5000_device, &sgtl5000_data);
mx5_usb_dr_init();
mx5_set_host1_vbus_func(mx53_loco_usbh1_vbus);
mx5_usbh1_init();
mxc_register_device(&mxc_v4l2_device, NULL);
mxc_register_device(&mxc_v4l2out_device, NULL);
mxc_register_device(&mxc_android_pmem_device, &android_pmem_data);
mxc_register_device(&mxc_android_pmem_gpu_device, &android_pmem_gpu_data);
mxc_register_device(&usb_mass_storage_device, &mass_storage_data);
mxc_register_device(&usb_rndis_device, &rndis_data);
mxc_register_device(&android_usb_device, &android_usb_data);
loco_add_device_buttons();
}
mxc_board_init定义在Mx53_LOCO板子的MACHINE_START中。
MACHINE_START(MX53_LOCO, "Freescale MX53 LOCO Board")
/* Maintainer: Freescale Semiconductor, Inc. */
.fixup = fixup_mxc_board,
.map_io = mx5_map_io,
.init_irq = mx5_init_irq,
.init_machine = mxc_board_init,
.timer = &mxc_timer,
MACHINE_END
利用mxc_register_device将系统资源注册进系统,在此之前platform bus 需要初始化成功,否则无法将platform devices挂接到platform bus上。为了保证platform drvier初始化时,相关platform资源已经注册进系统,mxc_board_init需要很早执行,而其作为init_machine时,将优先于系统所有驱动的初始化。
调用顺序如下:start_kernel(init/main.c#L539) >>setup_arch(arch/arm/kernel/setup.c#L670)>> init_machine(arch/arm/kernel/setup.c#L742)>> customize_machine(arch/arm/kernel/setup.c#L663)>>arch_initcall(arch/arm/kernel/setup.c#L670)
void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;
unwind_init();
setup_processor();
mdesc = setup_machine(machine_arch_type);
machine_name = mdesc->name;
if (mdesc->soft_reboot)
reboot_setup("s");
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);
else
init_tags.mem.start = PHYS_OFFSET;
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;
if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags);
}
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;
parse_early_param();
paging_init(mdesc);
request_standard_resources(&meminfo, mdesc);
#ifdef CONFIG_SMP
smp_init_cpus();
#endif
cpu_init();
tcm_init();
/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
early_trap_init();
}
static void (*init_machine)(void) __initdata;
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (init_machine)
init_machine();
return 0;
}
arch_initcall(customize_machine);
对于arch-initcall将customize_machine放在特定的段中,系统将在某个地方运行所有的arch_initcall所修饰的函数。
Kernel-imx/include/linux/Init.h#L156
#ifndef MODULE
#ifndef __ASSEMBLY__
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*
* The `id' arg to __define_initcall() is needed so that multiple initcalls
* can point at the same handler without causing duplicate-symbol build errors.
*/
#define __define_initcall(level,fn,id) /
static initcall_t __initcall_##fn##id __used /
__attribute__((__section__(".initcall" level ".init"))) = fn
/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall("early",fn,early)
/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
*/
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) /
static exitcall_t __exitcall_##fn __exit_call = fn
#define console_initcall(fn) /
static initcall_t __initcall_##fn /
__used __section(.con_initcall.init) = fn
#define security_initcall(fn) /
static initcall_t __initcall_##fn /
__used __section(.security_initcall.init) = fn
struct obs_kernel_param {
const char *str;
int (*setup_func)(char *);
int early;
};
/*
* Only for really core code. See moduleparam.h for the normal way.
*
* Force the alignment so the compiler doesn't space elements of the
* obs_kernel_param "array" too far apart in .init.setup.
*/
#define __setup_param(str, unique_id, fn, early) /
static const char __setup_str_##unique_id[] __initconst /
__aligned(1) = str; /
static struct obs_kernel_param __setup_##unique_id /
__used __section(.init.setup) /
__attribute__((aligned((sizeof(long))))) /
= { __setup_str_##unique_id, fn, early }
#define __setup(str, fn) /
__setup_param(str, fn, fn, 0)
/* NOTE: fn is as per module_param, not __setup! Emits warning if fn
* returns non-zero. */
#define early_param(str, fn) /
__setup_param(str, fn, fn, 1)
/* Relies on boot_command_line being set */
void __init parse_early_param(void);
void __init parse_early_options(char *cmdline);
#endif /* __ASSEMBLY__ */
/**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls() (if
* builtin) or at module insertion time (if a module). There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module. If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
#else /* MODULE */
/* Don't use these in modules, but some people do... */
#define early_initcall(fn) module_init(fn)
#define core_initcall(fn) module_init(fn)
#define postcore_initcall(fn) module_init(fn)
#define arch_initcall(fn) module_init(fn)
#define subsys_initcall(fn) module_init(fn)
#define fs_initcall(fn) module_init(fn)
#define device_initcall(fn) module_init(fn)
#define late_initcall(fn) module_init(fn)
#define security_initcall(fn) module_init(fn)
/* Each module must use one module_init(). */
#define module_init(initfn) /
static inline initcall_t __inittest(void) /
{ return initfn; } /
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) /
static inline exitcall_t __exittest(void) /
{ return exitfn; } /
void cleanup_module(void) __attribute__((alias(#exitfn)));
#define __setup_param(str, unique_id, fn) /* nothing */
#define __setup(str, func) /* nothing */
#endif
高亮显示部分arch_initcall =_define_initcall("3",fn,3)可知,其被链接器放在section .initcall3.init中;
Kernel_imx/include/asm_generic/Vmlinux.lds.h#L595
#define INITCALLS /
*(.initcallearly.init) /
VMLINUX_SYMBOL(__early_initcall_end) = .; /
*(.initcall0.init) /
*(.initcall0s.init) /
*(.initcall1.init) /
*(.initcall1s.init) /
*(.initcall2.init) /
*(.initcall2s.init) /
*(.initcall3.init) /
*(.initcall3s.init) /
*(.initcall4.init) /
*(.initcall4s.init) /
*(.initcall5.init) /
*(.initcall5s.init) /
*(.initcallrootfs.init) /
*(.initcall6.init) /
*(.initcall6s.init) /
*(.initcall7.init) /
*(.initcall7s.init)
#define INIT_CALLS /
VMLINUX_SYMBOL(__initcall_start) = .; /
INITCALLS /
VMLINUX_SYMBOL(__initcall_end) = .;
Kernel_imx/init/Main.c#L779定义
static void __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn < __initcall_end; fn++)
do_one_initcall(*fn);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
因此_initcall_fnx,数字越小,越先被调用,所以arch_initcall优先于module_init(platform_driver,后续讲到)所修饰的函数。
arch_initcall修饰的函数调用顺序如下:
start_kernel(init/main.c#L539) >>rest_init(init/main.c#L429在函数的最后一个调用)>>kenel_init(init/main.c#L866)>>do_basic_setup(init/Main.c#L797)>> do_initcalls()(在函数的最后),因为此时platform_bus_init在此前已经初始化完毕,因此便可将设备挂接到总线上了。
1.4 定义platform_driver
Platform bus和设备都定义好之后,需要定义一个platform driver用来驱动设备。
对于设备来说:
struct platform_device mxci2c_devices[] = {
{
.name = "imx-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(mxci2c1_resources),
.resource = mxci2c1_resources,
},
{
.name = "imx-i2c",
.id = 1,
.num_resources = ARRAY_SIZE(mxci2c2_resources),
.resource = mxci2c2_resources,
},
{
.name = "imx-i2c",
.id = 2,
.num_resources = ARRAY_SIZE(mxci2c3_resources),
.resource = mxci2c3_resources,
},
};
由platform总线上device和driver的匹配规则可知,I2C的platform driver的名字是imx-i2c。
Kernel_imx/drivers/i2c/busses/Mxc_i2c.c#L774
/*!
* This structure contains pointers to the power management callback functions.
*/
static struct platform_driver mxci2c_driver = {
.driver = {
.name = "mxc_i2c",
.owner = THIS_MODULE,
.pm = &mxci2c_dev_pm_ops,
},
.probe = mxci2c_probe,
.remove = mxci2c_remove,
};
1.5 注册platform_driver
Kernel_imx/drivers/i2c/busses/Mxc_i2c.c#L789
/*!
* Function requests the interrupts and registers the i2c adapter structures.
*
* @return The function returns 0 on success and a non-zero value on failure.
*/
static int __init mxc_i2c_init(void)
{
/* Register the device driver structure. */
return platform_driver_register(&mxci2c_driver);
}
subsys_initcall(mxc_i2c_init);
module_exit(mxc_i2c_exit);
在mxc_i2c_init中注册mxci2c_driver,其执行是在subsys_initcall定义的代码段中(有些driver是在module_initcall定义的代码段中,具体见Kernel-imx/include/linux/Init.h#L156),
#define subsys_initcall(fn) __define_initcall("4",fn,4)
因此在代码段3中,把设备注册到内核中,在4把驱动和内核绑定,并最终调用mxci2c_probe。
static int mxci2c_probe(struct platform_device *pdev)
{
mxc_i2c_device *mxc_i2c;
struct mxc_i2c_platform_data *i2c_plat_data = pdev->dev.platform_data;
struct resource *res;
int id = pdev->id;
u32 clk_freq;
int ret = 0;
int i;
mxc_i2c = kzalloc(sizeof(mxc_i2c_device), GFP_KERNEL);
if (!mxc_i2c) {
return -ENOMEM;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
ret = -ENODEV;
goto err1;
}
mxc_i2c->membase = ioremap(res->start, res->end - res->start + 1);
/*
* Request the I2C interrupt
*/
mxc_i2c->irq = platform_get_irq(pdev, 0);
if (mxc_i2c->irq < 0) {
ret = mxc_i2c->irq;
goto err2;
}
ret = request_irq(mxc_i2c->irq, mxc_i2c_handler,
0, pdev->name, mxc_i2c);
if (ret < 0) {
goto err2;
}
init_waitqueue_head(&mxc_i2c->wq);
mxc_i2c->low_power = false;
gpio_i2c_active(id);
mxc_i2c->clk = clk_get(&pdev->dev, "i2c_clk");
clk_freq = clk_get_rate(mxc_i2c->clk);
mxc_i2c->clkdiv = -1;
if (i2c_plat_data->i2c_clk) {
/* Calculate divider and round up any fractional part */
int div = (clk_freq + i2c_plat_data->i2c_clk - 1) /
i2c_plat_data->i2c_clk;
for (i = 0; i2c_clk_table[i].div != 0; i++) {
if (i2c_clk_table[i].div >= div) {
mxc_i2c->clkdiv = i2c_clk_table[i].reg_value;
break;
}
}
}
if (mxc_i2c->clkdiv == -1) {
i--;
mxc_i2c->clkdiv = 0x1F; /* Use max divider */
}
dev_dbg(&pdev->dev, "i2c speed is %d/%d = %d bps, reg val = 0x%02X/n",
clk_freq, i2c_clk_table[i].div,
clk_freq / i2c_clk_table[i].div, mxc_i2c->clkdiv);
/*
* Set the adapter information
*/
strlcpy(mxc_i2c->adap.name, pdev->name, 48);
mxc_i2c->adap.id = mxc_i2c->adap.nr = id;
mxc_i2c->adap.algo = &mxc_i2c_algorithm;
mxc_i2c->adap.timeout = 1;
platform_set_drvdata(pdev, mxc_i2c);
i2c_set_adapdata(&mxc_i2c->adap, mxc_i2c);
ret = i2c_add_numbered_adapter(&mxc_i2c->adap);
if (ret < 0)
goto err3;
printk(KERN_INFO "MXC I2C driver/n");
return 0;
err3:
free_irq(mxc_i2c->irq, mxc_i2c);
gpio_i2c_inactive(id);
err2:
iounmap(mxc_i2c->membase);
err1:
dev_err(&pdev->dev, "failed to probe i2c adapter/n");
kfree(mxc_i2c);
return ret;
}
当进入probe函数后,需要获取设备的资源信息,其函数为platform_get_resource,根据参数类型(eg:type=IORESOURCE_MEN)来获取指定的资源。
利用platform_get_irq来获取资源中的中断号。
Probe函数获取物理IO空间,通过request_mem_region和ioremap等操作物理地址转换成内核中的虚拟地址,初始化I2c控制器。获得中断号后,通过request_irq来向系统注册中断,并将此I2c控制器添加到系统中。
1.6 操作设备
进行了platform_device_register和platform_driver_registe后,驱动的相应信息就出现在sys目录的相应文件夹下,然后就是考虑如何调用设备和怎么对设备进行打开读写等操作
Platform总线只是为了方便管理接在CPU总线上的设备,与用户空间的交互,如读写还是需要利用file_operations。
对于I2C总线来说,其file_operations如下:
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
对于file_operations的具体实现,后续进行
- Android学习之 Platform总线 2
- Android学习之Platform总线 1
- platform总线 学习
- linux设备驱动之platform总线驱动学习
- linux驱动之platform总线
- Platform总线
- platform总线
- platform总线
- platform总线
- Platform总线
- Platform总线
- platform总线
- platform 总线
- platform总线
- platform总线
- Platform总线
- Linux设备模型之platform总线
- Linux设备驱动模型之platform总线
- 一个页面多次调用自定义控件时,注册JS脚本
- avoid pop-up Program Compatibility Assistant (PCA) dialog
- Win7上Android开发环境搭建
- jquery 插件(三) 精选动画仿flash插件1
- http协议content-encoding & transfer-encoding
- Android学习之 Platform总线 2
- 从HelloWorld看iphone程序的生命周期
- Http协议中消息的编码
- Workflow异常
- oracle: job简单示例
- ADO.NET中的事务
- 利用lipo编译合并iPhone模拟器和真机通用的静态类库
- HTTP1.1和HTTP1.0的区别
- 设计模式-工厂模式