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

时间:2022-03-11 06:37:05

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 <kgene.kim@samsung.com> */
/* Maintainer: Changhwan Youn <chaos.youn@samsung.com> */
.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\n", __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)\n", __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驱动加字符显示