Linux设备驱动--LCD平台设备与驱动(tiny4412)

2019-07-13 06:21发布

1 环境与简介     Host:Ubuntu14.04(64bit)     Target:Tiny4412     Kernel:linux-3.5.0 2 平台设备 2.1 声明
extern struct platform_device s5p_device_fimd0; 头文件:arch/arm/plat-samsung/include/plat/devs.h 对比分析:与《Linux设备驱动--LCD平台设备与驱动(smdk2440)》不同的是,这里不是通过EXPORT_SYMBOL(s5p_device_fimd0)使其在别的源文件可见,而是在头文件中声明该变量。 2.2 定义     先定义一个通用版的s5p_device_fimd0,其名字为s5p-fb,这个名字在后面中还会根据实际使用的配置进行设置。 struct platform_device s5p_device_fimd0 = { .name = "s5p-fb", .id = 0, .num_resources = ARRAY_SIZE(s5p_fimd0_resource), .resource = s5p_fimd0_resource, .dev = { .dma_mask = &samsung_device_dma_mask, .coherent_dma_mask = DMA_BIT_MASK(32), }, }; 源文件:arch/arm/plat-samsung/devs.c 对比分析:与《Linux设备驱动--LCD平台设备与驱动(smdk2440)》和《Linux设备驱动--LCD平台设备与驱动(smdk6410)》不同的是,这里平台设备并不命名为xxx_device_lcd或者xxx_device_fb,而是命名为xxx_device_fimd。前面两个都命名都容易让人想到是与显示相关的,但是后面的命名却让不熟悉的人感到困惑。其实,fimdfully Interactive mobile display的缩写,意为完全交互式移动显示设备[2]。知道这层之后,就不会对xxx_device_fimd感到困惑了。 2.3 引用     定义一个平台设备数组smdk4x12_devices,该数组包含了Tiny4412开发板的所有平台设备,其中当然包括上述s5p_device_fimd0,如下第35行所示: static struct platform_device *smdk4x12_devices[] __initdata = { #ifdef CONFIG_EXYNOS4_DEV_DWMCI &exynos_device_dwmci, #endif &s3c_device_hsmmc2, &s3c_device_hsmmc3, &wm8994_fixed_voltage0, &wm8994_fixed_voltage1, &wm8994_fixed_voltage2, &s3c_device_i2c0, &s3c_device_i2c1, &s3c_device_i2c2, &s3c_device_i2c3, #ifdef CONFIG_VIDEO_M5MOLS &s3c_device_i2c4, #endif &s3c_device_i2c7, &s3c_device_adc, &s3c_device_rtc, &s3c_device_wdt, #ifdef CONFIG_TINY4412_BUZZER &s3c_device_timer[0], #endif #ifdef CONFIG_VIDEO_EXYNOS_FIMC_LITE &exynos_device_flite0, &exynos_device_flite1, #endif &s5p_device_mipi_csis0, &s5p_device_mipi_csis1, &s5p_device_fimc0, &s5p_device_fimc1, &s5p_device_fimc2, &s5p_device_fimc3, &s5p_device_fimc_md, &s5p_device_fimd0, &mali_gpu_device, &s5p_device_mfc, &s5p_device_mfc_l, &s5p_device_mfc_r, &s5p_device_jpeg, #ifdef CONFIG_SAMSUNG_DEV_KEYPAD &samsung_device_keypad, #endif &tiny4412_device_1wire, &tiny4412_device_adc, #ifdef CONFIG_INPUT_GPIO &tiny4412_input_device, #endif #ifdef CONFIG_IR_GPIO_CIR &tiny4412_device_gpiorc, #endif #ifdef CONFIG_VIDEO_EXYNOS_FIMC_IS &exynos4_device_fimc_is, #endif #ifdef CONFIG_LCD_LMS501KF03 &s3c_device_spi_gpio, #endif #ifdef CONFIG_S3C64XX_DEV_SPI0 &s3c64xx_device_spi0, #endif #ifdef CONFIG_S3C64XX_DEV_SPI1 &s3c64xx_device_spi1, #endif #ifdef CONFIG_S3C64XX_DEV_SPI2 &s3c64xx_device_spi2, #endif #ifdef CONFIG_ION_EXYNOS &exynos_device_ion, #endif &s5p_device_i2c_hdmiphy, &s5p_device_hdmi, &s5p_device_mixer, &exynos4_bus_devfreq, &samsung_asoc_dma, &samsung_asoc_idma, #ifdef CONFIG_SND_SAMSUNG_I2S &exynos4_device_i2s0, #endif #ifdef CONFIG_SND_SAMSUNG_PCM &exynos4_device_pcm0, #endif #ifdef CONFIG_SND_SAMSUNG_SPDIF &exynos4_device_spdif, #endif &tiny4412_audio, #ifdef CONFIG_VIDEO_EXYNOS_FIMG2D &s5p_device_fimg2d, #endif #ifdef CONFIG_EXYNOS_THERMAL &exynos_device_tmu, #endif &s5p_device_ehci, &exynos4_device_ohci, &s5p_device_usbswitch, #if defined CONFIG_SND_SAMSUNG_ALP &exynos_device_srp, #endif #ifdef CONFIG_BUSFREQ_OPP &exynos4_busfreq, #endif #ifdef CONFIG_BATTERY_SAMSUNG &samsung_device_battery, #endif }; 源文件:arch/arm/mach-exynos/mach-tiny4412.c 2.4 注册 (1)注册函数     在smdk4x12_machine_init()函数中调用platform_add_devices()函数将上述smdk4x12_devices注册到系统,即可完成平台设备的注册,如下第121行所示:
static void __init smdk4x12_machine_init(void) { #ifdef CONFIG_TOUCHSCREEN_FT5X0X struct s3cfb_lcd *lcd = tiny4412_get_lcd(); ft5x0x_pdata.screen_max_x = lcd->width; ft5x0x_pdata.screen_max_y = lcd->height; #endif exynos_bootdev_init(); tiny4412_hwrev_init(); #ifdef CONFIG_S3C64XX_DEV_SPI0 spi_register_board_info(spi0_board_info, ARRAY_SIZE(spi0_board_info)); #endif #ifdef CONFIG_S3C64XX_DEV_SPI1 spi_register_board_info(spi1_board_info, ARRAY_SIZE(spi1_board_info)); #endif #ifdef CONFIG_S3C64XX_DEV_SPI2 spi_register_board_info(spi2_board_info, ARRAY_SIZE(spi2_board_info)); #endif if (samsung_pack() != EXYNOS4412_PACK_SCP) { #ifdef CONFIG_REGULATOR_MAX77686 max77686_populate_pdata(); #endif } s3c_adc_set_platdata(NULL); s3c_adc_setname("samsung-adc-v4"); s3c_i2c0_set_platdata(&tiny4412_i2c0_data); i2c_register_board_info(0, smdk4x12_i2c_devs0, ARRAY_SIZE(smdk4x12_i2c_devs0)); s3c_i2c1_set_platdata(&tiny4412_i2c1_data); i2c_register_board_info(1, smdk4x12_i2c_devs1, ARRAY_SIZE(smdk4x12_i2c_devs1)); s3c_i2c2_set_platdata(NULL); i2c_register_board_info(2, smdk4x12_i2c_devs2, ARRAY_SIZE(smdk4x12_i2c_devs2)); s3c_i2c3_set_platdata(&tiny4412_i2c3_data); i2c_register_board_info(3, smdk4x12_i2c_devs3, ARRAY_SIZE(smdk4x12_i2c_devs3)); s3c_i2c4_set_platdata(NULL); smdk4x12_rtc_wake_init(); smdk4x12_pmu_wdt_init(); smdk4x12_touch_init(); if (is_board_rev_B()) { #ifdef CONFIG_INPUT_GPIO tiny4412_key_info_fixup(); #endif } else { tiny4412_wifi_init(); } s3c_i2c7_set_platdata(&tiny4412_i2c7_data); i2c_register_board_info(7, smdk4x12_i2c_devs7, ARRAY_SIZE(smdk4x12_i2c_devs7)); s3c_hsotg_set_platdata(&smdk4x12_hsotg_pdata); #ifdef CONFIG_USB_EXYNOS_SWITCH smdk4x12_usbswitch_init(); #endif samsung_bl_set(&smdk4x12_bl_gpio_info, &smdk4x12_bl_data); tiny4412_fb_init_pdata(&smdk4x12_lcd0_pdata); s5p_fimd0_set_platdata(&smdk4x12_lcd0_pdata); #ifdef CONFIG_LCD_LMS501KF03 spi_register_board_info(spi_board_info, ARRAY_SIZE(spi_board_info)); #endif #ifdef CONFIG_SAMSUNG_DEV_KEYPAD samsung_keypad_set_platdata(&smdk4x12_keypad_data); #endif #ifdef CONFIG_EXYNOS4_DEV_DWMCI exynos_dwmci_set_platdata(&exynos_dwmci_pdata); #endif s3c_sdhci2_set_platdata(&smdk4x12_hsmmc2_pdata); s3c_sdhci3_set_platdata(&smdk4x12_hsmmc3_pdata); #ifdef CONFIG_ION_EXYNOS exynos_ion_set_platdata(); #endif s5p_tv_setup(); s5p_i2c_hdmiphy_set_platdata(NULL); s5p_hdmi_set_platdata(smdk4x12_i2c_hdmiphy, NULL, 0); #ifdef CONFIG_VIDEO_EXYNOS_FIMG2D s5p_fimg2d_set_platdata(&fimg2d_data); #endif #if defined(CONFIG_VIDEO_M5MOLS) || defined(CONFIG_VIDEO_S5K6A3) smdk4x12_camera_init(); #endif #ifdef CONFIG_VIDEO_EXYNOS_FIMC_LITE smdk4x12_set_camera_flite_platdata(); s3c_set_platdata(&exynos_flite0_default_data, sizeof(exynos_flite0_default_data), &exynos_device_flite0); s3c_set_platdata(&exynos_flite1_default_data, sizeof(exynos_flite1_default_data), &exynos_device_flite1); #endif smdk4x12_ehci_init(); #ifdef CONFIG_S3C64XX_DEV_SPI0 s3c64xx_spi0_set_platdata(NULL, 0, 1); #endif #ifdef CONFIG_S3C64XX_DEV_SPI1 s3c64xx_spi1_set_platdata(NULL, 0, 1); #endif #ifdef CONFIG_S3C64XX_DEV_SPI2 s3c64xx_spi2_set_platdata(NULL, 0, 1); #endif smdk4x12_ohci_init(); platform_add_devices(smdk4x12_devices, ARRAY_SIZE(smdk4x12_devices)); if (!uhost0) platform_device_register(&s3c_device_usb_hsotg); #ifdef CONFIG_VIDEO_EXYNOS_FIMC_IS exynos4_fimc_is_set_platdata(NULL); #endif if (soc_is_exynos4412()) { if ((samsung_rev() >= EXYNOS4412_REV_2_0)) initialize_prime_clocks(); else initialize_non_prime_clocks(); #ifdef CONFIG_S3C64XX_DEV_SPI0 exynos4_setup_clock(&s3c64xx_device_spi0.dev, "mdout_spi", "mout_mpll_user", 50 * MHZ); #endif } #ifdef CONFIG_BUSFREQ_OPP dev_add(&busfreq, &exynos4_busfreq.dev); ppmu_init(&exynos_ppmu[PPMU_DMC0], &exynos4_busfreq.dev); ppmu_init(&exynos_ppmu[PPMU_DMC1], &exynos4_busfreq.dev); ppmu_init(&exynos_ppmu[PPMU_CPU], &exynos4_busfreq.dev); #endif set_tmu_platdata(); } 源文件:arch/arm/mach-exynos/mach-tiny4412.c (2)注册时机     上述smdk4x12_machine_init()函数是何时被调用的呢?答案是在内核初始化设备的时候,如下第9行所示: MACHINE_START(TINY4412, "TINY4412") /* Maintainer: FriendlyARM (www.arm9.net) */ /* Maintainer: Kukjin Kim */ /* Maintainer: Changhwan Youn */ .atag_offset = 0x100, .init_irq = exynos4_init_irq, .map_io = smdk4x12_map_io, .handle_irq = gic_handle_irq, .init_machine = smdk4x12_machine_init, .init_late = exynos_init_late, .timer = &exynos4_timer, .restart = exynos4_restart, .reserve = &smdk4x12_reserve, MACHINE_END 源文件:arch/arm/mach-exynos/mach-tiny4412.c 3 平台驱动 3.1 声明 extern struct platform_driver fimd_driver;头文件:drivers/gpu/drm/exynos/exynos_drm_drv.h
3.2 定义 struct platform_driver fimd_driver = { .probe = fimd_probe, .remove = __devexit_p(fimd_remove), .driver = { .name = "exynos4-fb", .owner = THIS_MODULE, .pm = &fimd_pm_ops, }, }; 源文件:drivers/gpu/drm/exynos/exynos_drm_fimd.c
3.3 注册     在exynos_drm_init()函数中调用platform_driver_register()将上述fimd_driver注册到系统中,例如下面第8行所示。 static int __init exynos_drm_init(void) { int ret; DRM_DEBUG_DRIVER("%s ", __FILE__); #ifdef CONFIG_DRM_EXYNOS_FIMD ret = platform_driver_register(&fimd_driver); if (ret < 0) goto out_fimd; #endif #ifdef CONFIG_DRM_EXYNOS_HDMI ret = platform_driver_register(&hdmi_driver); if (ret < 0) goto out_hdmi; ret = platform_driver_register(&mixer_driver); if (ret < 0) goto out_mixer; ret = platform_driver_register(&exynos_drm_common_hdmi_driver); if (ret < 0) goto out_common_hdmi; #endif #ifdef CONFIG_DRM_EXYNOS_VIDI ret = platform_driver_register(&vidi_driver); if (ret < 0) goto out_vidi; #endif #ifdef CONFIG_DRM_EXYNOS_G2D ret = platform_driver_register(&g2d_driver); if (ret < 0) goto out_g2d; #endif ret = platform_driver_register(&exynos_drm_platform_driver); if (ret < 0) goto out; return 0; out: #ifdef CONFIG_DRM_EXYNOS_G2D platform_driver_unregister(&g2d_driver); out_g2d: #endif #ifdef CONFIG_DRM_EXYNOS_VIDI out_vidi: platform_driver_unregister(&vidi_driver); #endif #ifdef CONFIG_DRM_EXYNOS_HDMI platform_driver_unregister(&exynos_drm_common_hdmi_driver); out_common_hdmi: platform_driver_unregister(&mixer_driver); out_mixer: platform_driver_unregister(&hdmi_driver); out_hdmi: #endif #ifdef CONFIG_DRM_EXYNOS_FIMD platform_driver_unregister(&fimd_driver); out_fimd: #endif return ret; } 源文件:drivers/gpu/drm/exynos/exynos_drm_drv.c 4 设备与驱动匹配     平台设备和驱动必须同名才能匹配,然而上述平台设备的name为s5p-fb,而平台驱动的name为exynos4-fb,根本就不一致!这是不是搞错了呢?答案是否定的,这其实是一个用于支持多种显示设备的技巧:在特定的函数中对上述设备名进行更改,使其和驱动名匹配。下面分析该过程。 4.1 更改设备名字 (1)exynos4_map_io()     该函数调用s5p_fb_setname()函数将上述s5p_device_fimd0的名字由原来的s5p-fb改为exynos4-fb,见38行。 static void __init exynos4_map_io(void) { iotable_init(exynos4_iodesc, ARRAY_SIZE(exynos4_iodesc)); if (soc_is_exynos4210() && samsung_rev() == EXYNOS4210_REV_0) iotable_init(exynos4_iodesc0, ARRAY_SIZE(exynos4_iodesc0)); else iotable_init(exynos4_iodesc1, ARRAY_SIZE(exynos4_iodesc1)); if (soc_is_exynos4412()) iotable_init(exynos4412_iodesc, ARRAY_SIZE(exynos4412_iodesc)); else iotable_init(exynos4xxx_iodesc, ARRAY_SIZE(exynos4xxx_iodesc)); /* initialize device information early */ exynos4_default_sdhci0(); exynos4_default_sdhci1(); exynos4_default_sdhci2(); exynos4_default_sdhci3(); s3c_adc_setname("samsung-adc-v3"); s3c_fimc_setname(0, "exynos4-fimc"); s3c_fimc_setname(1, "exynos4-fimc"); s3c_fimc_setname(2, "exynos4-fimc"); s3c_fimc_setname(3, "exynos4-fimc"); s3c_sdhci_setname(0, "exynos4-sdhci"); s3c_sdhci_setname(1, "exynos4-sdhci"); s3c_sdhci_setname(2, "exynos4-sdhci"); s3c_sdhci_setname(3, "exynos4-sdhci"); /* The I2C bus controllers are directly compatible with s3c2440 */ s3c_i2c0_setname("s3c2440-i2c"); s3c_i2c1_setname("s3c2440-i2c"); s3c_i2c2_setname("s3c2440-i2c"); s5p_fb_setname(0, "exynos4-fb"); s5p_hdmi_setname("exynos4-hdmi"); } 源文件:arch/arm/mach-exynos/common.c (2)s5p_fb_setname()     该函数主要就是用于给s5p_device_fimd0这个全局变量改名,详见下面第5行: static inline void s5p_fb_setname(int id, char *name) { switch (id) { #ifdef CONFIG_S5P_DEV_FIMD0 case 0: s5p_device_fimd0.name = name; break; #endif default: printk(KERN_ERR "%s: invalid device id(%d) ", __func__, id); break; } } 源文件:arch/arm/plat-samsung/include/plat/fb-core.h  4.2 匹配过程     同《Linux设备驱动--WDT平台设备与驱动》第4节。 5 总结     与《Linux设备驱动--LCD平台设备与驱动(smdk2440)》和《Linux设备驱动--LCD平台设备与驱动(smdk6410)》对比可知,主要流程基本一致,然而Tiny4412为了兼容更多的显示设备,采取了更加灵活的方法。 参考资料 [1]Tiny 4412 lcd 驱动分析 [2]FIMD架构分析 [3]tiny4412LCD驱动加字符显示