http://wangshh03.blog.163.com/blog/static/4910341520101124056116/
8.1 初始化platform_bus
Platform总线的初始化是在platform_bus_init()完成的,代码如下:
http://lxr.linux.no/#linux+v2.6.25/drivers/base/platform.c#L621
26struct device platform_bus = {
27 .bus_id = "platform",
28};
29EXPORT_SYMBOL_GPL(platform_bus);
621int __init platform_bus_init(void)
622{
623 int error;
624
625 error = device_register(&platform_bus);
626 if (error)
627 return error;
628 error = bus_register(&platform_bus_type);
629 if (error)
630 device_unregister(&platform_bus);
631 return error;
632}
该函数创建了一个名为 “platform”的设备,后续platform的设备都会以此为parent。在sysfs中表示为:所有platform类型的设备都会添加在 platform_bus所代表的目录下,即 /sys/devices/platform下面。
-sh-3.1# ls /sys/devices/platform/
Fixed MDIO bus.0 fsl-i2c.0 serial8250
fsl-ehci.0 fsl-i2c.1 serial8250.0
fsl-gianfar.0 mpc83xx_spi.0 uevent
fsl-gianfar.1 mpc83xx_wdt.0
fsl-gianfar_mdio.-5 power
-sh-3.1# ls /sys/
block/ class/ firmware/ kernel/ power/
bus/ devices/ fs/ module/
-sh-3.1# ls /sys/bus/
i2c/ of_platform/ pci_express/ scsi/ usb/
mdio_bus/ pci/ platform/ spi/
-sh-3.1# ls /sys/bus/i2c/
devices/ drivers_autoprobe uevent
drivers/ drivers_probe
-sh-3.1# ls /sys/bus/platform/devices/
Fixed MDIO bus.0/ fsl-gianfar_mdio.-5/ mpc83xx_wdt.0/
fsl-ehci.0/ fsl-i2c.0/ serial8250/
fsl-gianfar.0/ fsl-i2c.1/ serial8250.0/
fsl-gianfar.1/ mpc83xx_spi.0/
-sh-3.1# ls /sys/bus/platform/drivers
drivers/ drivers_autoprobe drivers_probe
-sh-3.1# ls /sys/bus/platform/drivers/
fsl-ehci/ fsl-gianfar_mdio/ mpc83xx_spi/ serial8250/
fsl-gianfar/ fsl-i2c/ mpc83xx_wdt/
platform_bus必须在系统注册任何platform driver和platform device之前初始化,那么这是如何实现的呢?
http://lxr.linux.no/#linux+v2.6.25/drivers/base/init.c
14
20void __init driver_init(void)
21{
22
23 devices_init();
24 buses_init();
25 classes_init();
26 firmware_init();
27 hypervisor_init();
28
29
32 platform_bus_init();
33 system_bus_init();
34 cpu_dev_init();
35 memory_dev_init();
36}
init/main.c
start_kernel 》 rest_init 》 kernel_init 》 do_basic_setup》driver_init 》platform_bus_init
http://lxr.linux.no/#linux+v2.6.25/drivers/base/init.c#L32
724
731static void __init do_basic_setup(void)
732{
733
734 init_workqueues();
735 usermodehelper_init();
736 driver_init();
737 init_irq_proc();
738 do_initcalls();
739}
platform driver和platform device的初始化是在do_initcalls中进行的。
8.2 定义platform_device
http://lxr.linux.no/#linux+v2.6.25/arch/arm/plat-s3c24xx/devs.c#L276中定义了系统的资源,是一个高度可移植的文件,大部分板级资源都在这里集中定义。
274
275
276static struct resource s3c_i2c_resource[] = {
277 [0] = {
278 .start = S3C24XX_PA_IIC,
279 .end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
280 .flags = IORESOURCE_MEM,
281 },
282 [1] = {
283 .start = IRQ_IIC,
284 .end = IRQ_IIC,
285 .flags = IORESOURCE_IRQ,
286 }
287
288};
289
290struct platform_device s3c_device_i2c = {
291 .name = "s3c2410-i2c",
292 .id = -1,
293 .num_resources = ARRAY_SIZE(s3c_i2c_resource),
294 .resource = s3c_i2c_resource,
295};
296
297EXPORT_SYMBOL(s3c_device_i2c);
设备名称为s3c2410-i2c,“-1”只有一个i2c设备,两个资源s3c_i2c_resource,分别为i2c控制器的寄存器空间和中断信息。
8.3 注册platform_device
定义了platform_device后,需要添加到系统中,就可以调用函数platform_add_devices。
http://lxr.linux.no/#linux+v2.6.25/arch/arm/mach-s3c2440/mach-smdk2440.c
smdk2440_devices将系统资源组织起来,统一注册进内核。
151static struct platform_device *smdk2440_devices[] __initdata = {
152 &s3c_device_usb,
153 &s3c_device_lcd,
154 &s3c_device_wdt,
155 &s3c_device_i2c,
156 &s3c_device_iis,
157};
166static void __init smdk2440_machine_init(void)
167{
168 s3c24xx_fb_set_platdata(&smdk2440_fb_info);
169
170 platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
171 smdk_machine_init();
172}
173
174MACHINE_START(S3C2440, "SMDK2440")
175
176 .phys_io = S3C2410_PA_UART,
177 .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
178 .boot_params = S3C2410_SDRAM_PA + 0x100,
179
180 .init_irq = s3c24xx_init_irq,
181 .map_io = smdk2440_map_io,
182 .init_machine = smdk2440_machine_init,
183 .timer = &s3c24xx_timer,
184MACHINE_END
170 platform_add_devices(smdk2440_devices, ARRAY_SIZE(smdk2440_devices));
将系统所有资源注册进系统,在此之前platform bus需要初始化成功,否则无法将platform devices挂接到platform bus上。为了保证platform drive初始化时,相关platform资源已经注册进系统,smdk2440_machine_init需要很早执行,而其作为平台初始化init_machine 时,将优先于系统所有驱动的初始化。
其调用顺序如下:
start_kernel》setup_arch》init_machine》arch_initcall(customize_machine)
http://lxr.linux.no/#linux+v2.6.25/arch/arm/kernel/setup.c#L788
786arch_initcall(customize_machine);
787
788void __init setup_arch(char **cmdline_p)
789{
790 struct tag *tags = (struct tag *)&init_tags;
791 struct machine_desc *mdesc;
792 char *from = default_command_line;
793
794 setup_processor();
795 mdesc = setup_machine(machine_arch_type);
//根据machine id获得移植时定义的machine desc结构
796 machine_name = mdesc->name;
797
798 if (mdesc->soft_reboot)
799 reboot_setup("s");
800
801 if (__atags_pointer)
802 tags = phys_to_virt(__atags_pointer);
803 else if (mdesc->boot_params)
804 tags = phys_to_virt(mdesc->boot_params);
805
806
810 if (tags->hdr.tag != ATAG_CORE)
811 convert_to_tag_list(tags);
812 if (tags->hdr.tag != ATAG_CORE)
813 tags = (struct tag *)&init_tags;
814
815 if (mdesc->fixup)
816 mdesc->fixup(mdesc, tags, &from, &meminfo);
817
818 if (tags->hdr.tag == ATAG_CORE) {
819 if (meminfo.nr_banks != 0)
820 squash_mem_tags(tags);
821 save_atags(tags);
822 parse_tags(tags);
823 }
824
825 init_mm.start_code = (unsigned long) &_text;
826 init_mm.end_code = (unsigned long) &_etext;
827 init_mm.end_data = (unsigned long) &_edata;
828 init_mm.brk = (unsigned long) &_end;
829
830 memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
831 boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
832 parse_cmdline(cmdline_p, from);
833 paging_init(&meminfo, mdesc);
834 request_standard_resources(&meminfo, mdesc);
835
836#ifdef CONFIG_SMP
837 smp_init_cpus();
838#endif
839
840 cpu_init();
841
842
845 init_arch_irq = mdesc->init_irq;
846 system_timer = mdesc->timer;
847 init_machine = mdesc->init_machine;
//对init_machine指针赋值
848
849#ifdef CONFIG_VT
850#if defined(CONFIG_VGA_CONSOLE)
851 conswitchp = &vga_con;
852#elif defined(CONFIG_DUMMY_CONSOLE)
853 conswitchp = &dummy_con;
854#endif
855#endif
856}
777static void (*init_machine)(void) __initdata;
778
779static int __init customize_machine(void)
780{
781
782 if (init_machine)
783 init_machine();
784 return 0;
785}
786arch_initcall(customize_machine);
arch_initcall将customize_machine放在特定的段中,系统将在某个地方运行所有的arch_initcall修饰的函数。
http://lxr.linux.no/#linux+v2.6.25/include/linux/init.h#L182
152#ifndef MODULE //非可加载模块,即编译链接进内核的代码
153
154#ifndef __ASSEMBLY__
155
156
165
166#define __define_initcall(level,fn,id) \
167 static initcall_t __initcall_##fn##id __used \
168 __attribute__((__section__(".initcall" level ".init"))) = fn
169
170
176#define pure_initcall(fn) __define_initcall("0",fn,0)
177
178#define core_initcall(fn) __define_initcall("1",fn,1)
179#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
180#define postcore_initcall(fn) __define_initcall("2",fn,2)
181#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
182#define arch_initcall(fn) __define_initcall("3",fn,3)
183#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
184#define subsys_initcall(fn) __define_initcall("4",fn,4)
185#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
186#define fs_initcall(fn) __define_initcall("5",fn,5)
187#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
188#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
189#define device_initcall(fn) __define_initcall("6",fn,6)
190#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
191#define late_initcall(fn) __define_initcall("7",fn,7)
192#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
193
194#define __initcall(fn) device_initcall(fn)
195
196#define __exitcall(fn) \
197 static exitcall_t __exitcall_##fn __exit_call = fn
198
。。。。。。。。。
239#endif
240
241
249#define module_init(x) __initcall(x);
250
251
261#define module_exit(x) __exitcall(x);
262
263#else
各种xx_core_initcall被定义到了不同的分级的段中
所以arch_initcall == __initcall_fn3 它将被链接器放于section .initcall3.init. 中
module_init()==__initcall(fn)==device_initcall(fn)== __initcall_fn6
各个段的优先级由链接脚本定义
http://lxr.linux.no/#linux+v2.6.25/include/asm-generic/vmlinux.lds.h#L328
#define INITCALLS \
*(.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)
这个__initcall_start是在文件arch/xxx/kernel/vmlinux.lds.S定义的:
__initcall_start = .;
INITCALLS
__initcall_end = .;
http://lxr.linux.no/#linux+v2.6.25/init/main.c#L664
664static void __init do_initcalls(void)
665{
666 initcall_t *call;
667 int count = preempt_count();
668
669 for (call = __initcall_start; call < __initcall_end; call++) {
.。。。。
682
683 result = (*call)();
684
。。。 }
720
721 flush_scheduled_work();
722}
因此__initcall_fnx,数字越小,越先被调用,故arch_initcall优先于module_init所修饰的函数。
arch_initcall修饰的函数的调用顺序如下:
start_kernel 》 rest_init(在setup_arch之后) 》 kernel_init 》 do_basic_setup》do_initcalls(在driver_init()之后),因为platform_bus_init在此之前已经初始化完毕了,便可将设备挂接到总线上了。
8.4 定义platform_driver
Platform bus和设备都定义好了后,需要定义一个platform driver用来驱动此设备。
对于设备来说:
290struct platform_device s3c_device_i2c = {
291 .name = "s3c2410-i2c",
292 .id = -1,
293 .num_resources = ARRAY_SIZE(s3c_i2c_resource),
294 .resource = s3c_i2c_resource,
295};
296
297EXPORT_SYMBOL(s3c_device_i2c);
根据platform总线上device和driver的匹配规则可知,I2C 的platform driver的名字是s3c2410-i2c。
http://lxr.linux.no/#linux+v2.6.25/drivers/i2c/busses/i2c-s3c2410.c#L1
903
904
905static struct platform_driver s3c2410_i2c_driver = {
906 .probe = s3c24xx_i2c_probe,
907 .remove = s3c24xx_i2c_remove,
908 .resume = s3c24xx_i2c_resume,
909 .driver = {
910 .owner = THIS_MODULE,
911 .name = "s3c2410-i2c",
912 },
913};
8.5 注册platform_driver
http://lxr.linux.no/#linux+v2.6.25/drivers/i2c/busses/i2c-s3c2410.c#L1
925static int __init i2c_adap_s3c_init(void)
926{
927 int ret;
928
929 ret = platform_driver_register(&s3c2410_i2c_driver);
930 if (ret == 0) {
931 ret = platform_driver_register(&s3c2440_i2c_driver);
932 if (ret)
933 platform_driver_unregister(&s3c2410_i2c_driver);
934 }
935
936 return ret;
937}
938
945module_init(i2c_adap_s3c_init);
946module_exit(i2c_adap_s3c_exit);
在i2c_adap_s3c_init中注册s3c2410_i2c_driver,那么i2c_adap_s3c_init何时执行的呢?module_init(i2c_adap_s3c_init)表明其存放在initcall段,调用顺序如下:
init/main.c
start_kernel 》 rest_init 》 kernel_init 》 do_basic_setup》do_initcalls,因为platform_bus_init在此之前已经初始化完毕了,且设备已经注册到内核中了,驱动将和内核绑定,并最终调用s3c24xx_i2c_probe。
748
752
753static int s3c24xx_i2c_probe(struct platform_device *pdev)
754{
755 struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
756 struct resource *res;
757 int ret;
758
759
760
761 i2c->dev = &pdev->dev;
762 i2c->clk = clk_get(&pdev->dev, "i2c");
763 if (IS_ERR(i2c->clk)) {
764 dev_err(&pdev->dev, "cannot get clock\n");
765 ret = -ENOENT;
766 goto err_noclk;
767 }
768
769 dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
770
771 clk_enable(i2c->clk);
772
773
774
775 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
776 if (res == NULL) {
777 dev_err(&pdev->dev, "cannot find IO resource\n");
778 ret = -ENOENT;
779 goto err_clk;
780 }
781
782 i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
783 pdev->name);
784
785 if (i2c->ioarea == NULL) {
786 dev_err(&pdev->dev, "cannot request IO\n");
787 ret = -ENXIO;
788 goto err_clk;
789 }
790
791 i2c->regs = ioremap(res->start, (res->end-res->start)+1);
792
793 if (i2c->regs == NULL) {
794 dev_err(&pdev->dev, "cannot map IO\n");
795 ret = -ENXIO;
796 goto err_ioarea;
797 }
798
799 dev_dbg(&pdev->dev, "registers %p (%p, %p)\n", i2c->regs, i2c->ioarea, res);
800
801
802
803 i2c->adap.algo_data = i2c;
804 i2c->adap.dev.parent = &pdev->dev;
805
806
807
808 ret = s3c24xx_i2c_init(i2c);
809 if (ret != 0)
810 goto err_iomap;
811
812
815
816 res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
817 if (res == NULL) {
818 dev_err(&pdev->dev, "cannot find IRQ\n");
819 ret = -ENOENT;
820 goto err_iomap;
821 }
822
823 ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
824 pdev->name, i2c);
825
826 if (ret != 0) {
827 dev_err(&pdev->dev, "cannot claim IRQ\n");
828 goto err_iomap;
829 }
830
831 i2c->irq = res;
832
833 dev_dbg(&pdev->dev, "irq resource %p (%lu)\n", res,
834 (unsigned long)res->start);
835
836 ret = i2c_add_adapter(&i2c->adap);
837 if (ret < 0) {
838 dev_err(&pdev->dev, "failed to add bus to i2c core\n");
839 goto err_irq;
840 }
841
842 platform_set_drvdata(pdev, i2c);
843
844 dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id);
845 return 0;
846
847 err_irq:
848 free_irq(i2c->irq->start, i2c);
849
850 err_iomap:
851 iounmap(i2c->regs);
852
853 err_ioarea:
854 release_resource(i2c->ioarea);
855 kfree(i2c->ioarea);
856
857 err_clk:
858 clk_disable(i2c->clk);
859 clk_put(i2c->clk);
860
861 err_noclk:
862 return ret;
863}
当进入probe函数后,需要获取设备的资源信息,常用获取资源的函数主要是:
struct resource * platform_get_resource(struct platform_device *dev, unsigned int type, unsigned int num);
根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。
struct int platform_get_irq(struct platform_device *dev, unsigned int num);
获取资源中的中断号。
struct resource * platform_get_resource_byname(struct platform_device *dev, unsigned int type, char *name);
根据参数name所指定的名称,来获取指定的资源。
int platform_get_irq_byname(struct platform_device *dev, char *name);
根据参数name所指定的名称,来获取资源中的中断号。
此probe函数获取物理IO空间,通过request_mem_region和ioremap等操作物理地址转换成内核中的虚拟地址,初始化I2C控制器,通过platform_get_irq或platform_get_resource得到设备的中断号以后,就可以调用request_irq函数来向系统注册中断,并将此I2C控制器添加到系统中。
8.6 操作设备
进行了platform_device_register 和platform_driver_register后,驱动的相应信息就出现在sys目录的相应文件夹下,然后,我们该如何调用设备呢??怎么对设备进行打开读写等操作呢???
Platform总线只是为了方便管理挂接在CPU总线上的设备,与用户空间的交互,如读写还是需要利用file_operations。当然如果此platform设备无需和用户空间交互,则无需file_operations实例。
对于I2C总线来说,其file_operations如下:
http://lxr.linux.no/#linux+v2.6.25/drivers/i2c/i2c-core.c#L461
478static const struct file_operations i2cdev_fops = {
479 .owner = THIS_MODULE,
480 .llseek = no_llseek,
481 .read = i2cdev_read,
482 .write = i2cdev_write,
483 .ioctl = i2cdev_ioctl,
484 .open = i2cdev_open,
485 .release = i2cdev_release,
486};
其和platform bus的区别在于,platform bus提供机制访问I2C 控制器本身的资源,而I2C总线提供访问I2C 控制器上挂接的I2C设备的机制