Linux内核中换屏技术
21.5.1 u-boot中的参数bootargs实现换屏
在uboot中有一个 bootargs环境变量,这个参数就是传递数据给内核的。
对tiny4412提供的内核,可以通过修改 bootargs 实现驱动不同的LCD屏。
21.5.2 分析bootargs中的lcd参数
再启动uboot的时候会有如下的环境参数:
bootargs=noinitrd root=/dev/nfs nfsroot=192.168.10.106:/home/xyd/rootfs/ ip=192.168.10.123:192.168.10.106:192.168.10.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0 coherent_pool=2M lcd=S702 |
每个参数之间都是空格分开。
可以看到上面传递了2,启动内核之后就可以驱动S702屏。
在内核启动阶段,内核会根据lcd=S720这个信息,在注册平台设备/驱动前把LCD平台数据修改了。
21.5.3 分析在注册平台设备/驱动前修改lcd平台数据过程
首先从板级文件Mach-tiny4412.c中开始看。在这个文件中我们会看到一个机器初始化函数。
(1) 机器初始化函数中多平台设备注册代码段
代码段如下,在02299行可以看到:
static void __init smdk4x12_machine_init(void) { …… //注册多平台设备 platform_add_devices(smdk4x12_devices, //包含LCD平台设备结构指针 ARRAY_SIZE(smdk4x12_devices));//数组元素个数,即平台设备个数 …… } |
注册平台设备函数原型:
int platform_add_devices(struct platform_device **devs, int num) |
LCD平台设备层结构指针就是存放在 smdk4x12_devices的,以上函数注册包含LCD平台设备。所以,我们可以推出修改平台数据的地方应该在 platform_add_devices 函数前。
问题:在哪里修改lcd平台数据?
在platform_add_devices之前,有和lcd相关代码,下面的调用是在有触摸屏的时候才调用。我们只是把它作为例子来找到能识别s702屏的地方。
后边会讲到真正调用识别S702屏的地方。
代码如下:
static void __init smdk4x12_machine_init(void) { …… #ifdef CONFIG_TOUCHSCREEN_FT5X0X//配置FT5X0X触摸屏的宏 struct s3cfb_lcd *lcd = tiny4412_get_lcd(); ft5x0x_pdata.screen_max_x = lcd->width; ft5x0x_pdata.screen_max_y = lcd->height; #endif …… platform_add_devices(smdk4x12_devices, ARRAY_SIZE(smdk4x12_devices)); …… } |
其实真正的获得lcd参数结构是在tiny4412_fb_init_pdata函数里边调用,后面会有分析。
问题:现在我们分析如何通过bootargc传参获得lcd平台数据?
(2) 机器初始化函数中调用一个和lcd相关的tiny4412_get_lcd函数
SI里边调转,此函数在Tiny4412-lcds.c (linux-3.5\arch\arm\mach-exynos)文件下。
代码如下:
struct s3cfb_lcd *tiny4412_get_lcd(void) { return tiny4412_lcd_config[lcd_idx].lcd; } |
这个函数只返回一个数组元素,此数组定义和初始化如下:
在文件Tiny4412-lcds.c (linux-3.5\arch\arm\mach-exynos) 中。
代码如下:是一个结构体数组。
static struct { char *name; struct s3cfb_lcd *lcd; int ctp; } tiny4412_lcd_config[] = { { "HD700",&wxga_hd700, 1 }, { "S70",&wvga_s70, 1 }, { "S702",&wvga_s70, 3 }, { "W50",&wvga_w50, 0 }, { "W101",&wsvga_w101, 1 }, { "A97",&xga_a97, 0 }, { "LQ150",&xga_lq150, 1 }, { "L80",&vga_l80, 1 }, { "HD101",&wxga_hd101, 1 }, { "BP101",&wxga_bp101, 1 }, { "HDM",&hdmi_def, 0 },/* Pls keep it at last */ }; |
所以我们就要关注lcd_idx值,在哪里设置这个值。
SI跳转得到如下:
static int lcd_idx = 0; //在本文件中定义为静态变量 |
(3) 在哪里得到lcd_idx的值
我们采用搜索的方式查找lcd_idx,可以找到以下:
Tiny4412-lcds.c (arch\arm\mach-exynos):lcd_idx = i; |
进入查看搜索语句所在代码:
static int __inittiny4412_setup_lcd(char *str) { int i; …… /* 这里想通过名字查找对应的lcd配置参数,返回下标号lcd_idx */ for (i = 0; i < ARRAY_SIZE(tiny4412_lcd_config); i++) { if (!strcascmp(tiny4412_lcd_config[i].name, str)) { lcd_idx = i; break; } } __ret: printk("TINY4412: %s selected\n", tiny4412_lcd_config[lcd_idx].name); return 0; } |
这个是static静态函数,在本文件中有效。
这里我们可以知道tiny4412_lcd_config是一个数组,它就是存放所有支持的LCD屏信息。 通过比较数组成员name和tiny4412_setup_lcd参数str决定lcd_idx的值。
到这里就有疑问了:tiny4412_setup_lcd在哪里被调用?
(4) 补充知识:字符串比较函数。
int strcasecmp(const char *s1, const char *s2) { int c1, c2; do { c1 = tolower(*s1++); //转换小写 c2 = tolower(*s2++); //转换小写 } while (c1 == c2 && c1 != 0); return c1 - c2; } 这个函数是不区分大小写比较 |
21.5.4 几个重要结构
(1) 结构定义即初始化
这个结构体是当前板子所支持的lcd屏列表。
/* 定义lcd屏支持列表 */ static struct { char *name; struct s3cfb_lcd *lcd; int ctp; } tiny4412_lcd_config[] = { { "HD700",&wxga_hd700, 1 }, { "S70",&wvga_s70, 1 },//我们当前板子上所使用的屏 { "W50",&wvga_w50, 0 }, { "W101",&wsvga_w101, 1 }, { "A97",&xga_a97, 0 }, { "HDM",&hdmi_def, 0 },/* Pls keep it at last */ }; |
里边struct s3cfb_lcd *lcd;结构成员是存放lcd屏的工作时序参数。
(2)结构体定义
如下:这个结构已经包含LCD驱动所需要的平台数据信息。
/* * @width:horizontal resolution---水平分辨率 * @height:vertical resolution---垂直分辨率 * @p_width:width of lcd in mm---屏物理尺寸(宽,mm单位) * @p_height:height of lcd in mm---屏物理尺寸(高,mm单位) * @bpp:bits per pixel * @freq:vframe frequency---刷屏频率 * @timing:timing values--时序参数 * @polarity:polarity settings---极性参数 */ struct s3cfb_lcd { intwidth; intheight; intp_width; intp_height; intbpp; intfreq; structs3cfb_lcd_timing timing; structs3cfb_lcd_polarity polarity; }; |
(3) s3cfb_lcd_timing屏的时序参数结构
/* * @h_fp:horizontal front porch * @h_bp:horizontal back porch * @h_sw:horizontal sync width * @v_fp:vertical front porch * @v_fpe:vertical front porch for even field * @v_bp:vertical back porch * @v_bpe:vertical back porch for even field */ struct s3cfb_lcd_timing { inth_fp; inth_bp; inth_sw; intv_fp; intv_fpe; intv_bp; intv_bpe; intv_sw; }; |
(4) 屏的时序极性配置
/* * @rise_vclk:if 1, video data is fetched at rising edge * @inv_hsync:if HSYNC polarity is inversed * @inv_vsync:if VSYNC polarity is inversed * @inv_vden:if VDEN polarity is inversed */ struct s3cfb_lcd_polarity { intrise_vclk; intinv_hsync; intinv_vsync; intinv_vden; }; |
21.5.5 以bootargc传入lcd=S70为例子填充s3cfb_lcd结构
定义struct s3cfb_lcd结构变量wvga_s70 ,并初始化屏参数;
在文件Tiny4412-lcds.c (arch\arm\Mach-exynos)下实现。
//以下信息是根据LCD屏的手册得到的 static struct s3cfb_lcdwvga_s70= { .width = 800, .height = 480, .p_width = 155, .p_height = 93, .bpp = 24, .freq = 63, .timing = {//屏的时序参数结构 .h_fp = 80, .h_bp = 36, .h_sw = 10, .v_fp = 22, .v_fpe = 1, .v_bp = 15, .v_bpe = 1, .v_sw = 8, }, .polarity = {//屏的时序极性结构 .rise_vclk = 1, .inv_hsync = 1, .inv_vsync = 1, .inv_vden = 0, }, }; |
21.5.6 确定是谁使用 tiny4412_setup_lcd(char *str),str是否是"s70"
通过搜索tiny4412_setup_lcd的方式,在本文件中可以找到使用处:
我们可以知道是被early_param调用:
early_param("lcd", tiny4412_setup_lcd); |
early_param这个宏是内核用来在系统启动初期需要解析的参数。
如:
上面的语句,系统启动初期会判断在bootargc环境变量中有”lcd”这个字符串;
如果有则把lcd =的值作为 tiny4412_setup_lcd函数的参数;
然后调用执行tiny4412_setup_lcd函数。
补充知识:early_param宏
/* early_param用于注册内核选项解析的处理函数 */
#define early_param(str, fn) \
__setup_param(str, fn, fn, 1)
str:参数名,就是bootargs 参数传递进来的参数对左边的字符串。
fn:处理函数,原型是 int XXX(char *s); 这个函数的参数是就 str=后面的字符串
示例:
…… lcd=s70
就是作为传递给处理函数fn。
经过上面的分析已经知道 struct s3cfb_lcd *tiny4412_get_lcd(void) 返回屏的时序参数结构。
之前说真正调用tiny4412_get_lcd是:
smdk4x12_machine_init调用tiny4412_fb_init_pdata;
tiny4412_fb_init_pdata里边调用了tiny4412_get_lcd。
所以,就要知道哪里使用了tiny4412_get_lcd(void)返回的s3cfb_lcd *结构指针。
21.5.7 真正调用了tiny4412_get_lcd(void),并使用返回Lcd结构指针修改屏的参数结构
在机器初始化函数中往下看,调用了一个tiny4412_fb_init_pdata函数,该函数参数就是LCD驱动数据结构指针。
真正的用意是修改函数参数,就是修改LCD驱动数据结构指针(smdk4x12_lcd0_pdata)里边的数据,实现这个LCD驱动数据结构(smdk4x12_lcd0_pdata)填充的是s70的参数数据。
代码如下:
static void __init smdk4x12_machine_init(void) { …… samsung_bl_set(&smdk4x12_bl_gpio_info, &smdk4x12_bl_data); //调用下面函数,其中有部分代码修改了smdk4x12_lcd0_pdata结构的数据。 tiny4412_fb_init_pdata(&smdk4x12_lcd0_pdata); s5p_fimd0_set_platdata(&smdk4x12_lcd0_pdata); …… platform_add_devices(smdk4x12_devices, ARRAY_SIZE(smdk4x12_devices)); …… } |
先分析tiny4412_fb_init_pdata送进来的参数smdk4x12_lcd0_pdata是什么?
static struct s3c_fb_platdata smdk4x12_lcd0_pdata __initdata = { .win[0]= &smdk4x12_fb_win0, .win[1]= &smdk4x12_fb_win1, .win[2]= &smdk4x12_fb_win2, .win[3]= &smdk4x12_fb_win3, .win[4]= &smdk4x12_fb_win4, .vtiming= &smdk4x12_lcd_timing, .vidcon0= VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB, .vidcon1= VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC, .setup_gpio= exynos4_fimd0_gpio_setup_24bpp, }; |
struct s3c_fb_platdata结构原型:
struct s3c_fb_platdata { void(*setup_gpio)(void); struct s3c_fb_pd_win *win[S3C_FB_MAX_WIN]; struct fb_videomode *vtiming; u32 vidcon0; u32 vidcon1; }; |
以上结构填充的一些默认数据在调用tiny4412_fb_init_pdata函数之后会被改掉,修改成s70屏的参数数据。
再分析tiny4412_fb_init_pdata函数的实现:
这个函数实现在Mach-tiny4412.c (linux-3.5\arch\arm\mach-exynos)中。
该函数中把struct s3c_fb_platdata * smdk4x12_lcd0_pdata结构中的的数据设置到s3c_fb_pd_win窗口结构和fb_videomode结构里边去。
代码如下:
static void __init tiny4412_fb_init_pdata(struct s3c_fb_platdata *pd) { struct s3cfb_lcd *lcd; struct s3c_fb_pd_win *win; //把送进来的结构vtiming赋值 struct fb_videomode *mode = pd->vtiming; unsigned long val = 0; u64 pixclk = 1000000000000ULL; u32 div; int i; /* 真的调用tiny4412_get_lcd,获取s70 lcd屏参数, 这个lcd屏参数是通过命令行传递lcd屏型号来找到具体是哪一个参数的 */ lcd = tiny4412_get_lcd(); /* 设置(修改)smdk4x12_lcd0_pdata结构中每个lcd窗口的参数 */ for (i = 0; i < S3C_FB_MAX_WIN; i++) { /* 过滤空的lcd窗口结构 */ if (pd->win[i] == NULL) continue; /* 把lcd参数设置到s3c_fb_pd_win窗口结构中 */ //送进来的结构窗口赋值,修改其中的参数 win = pd->win[i]; win->xres = lcd->width; win->yres = lcd->height; win->default_bpp = lcd->bpp ? : 24; win->virtual_x = win->xres; win->virtual_y = win->yres * CONFIG_FB_S3C_NR_BUFFERS; win->width = lcd->p_width; win->height = lcd->p_height; } /* 把屏的时序设置到fb_videomode结构中,lcd注册需要使用到 */ mode->left_margin = lcd->timing.h_bp; mode->right_margin = lcd->timing.h_fp; mode->upper_margin = lcd->timing.v_bp; mode->lower_margin = lcd->timing.v_fp; mode->hsync_len = lcd->timing.h_sw; mode->vsync_len = lcd->timing.v_sw; mode->xres = lcd->width; mode->yres = lcd->height; /* calculates pixel clock */ div = mode->left_margin + mode->hsync_len + mode->right_margin + mode->xres; div *= mode->upper_margin + mode->vsync_len + mode->lower_margin + mode->yres; /*如果没有自定义频率,则设置为60Hz*/ div *= lcd->freq ? : 60; do_div(pixclk, div); mode->pixclock = pixclk + 386;//386 /* initialize signal polarity of RGB interface */ if (lcd->polarity.rise_vclk) val |= VIDCON1_INV_VCLK; if (lcd->polarity.inv_hsync) val |= VIDCON1_INV_HSYNC; if (lcd->polarity.inv_vsync) val |= VIDCON1_INV_VSYNC; if (lcd->polarity.inv_vden) val |= VIDCON1_INV_VDEN; //修改vidcon1的值 pd->vidcon1 = val; } |
到这里我们需要知道:在平台设备和驱动注册前修改lcd平台数据设置完成。
21.5.8 补充知识:tiny4412_fb_init_pdata涉及到几个结构。
1、屏窗口结构
struct s3c_fb_pd_win { unsigned shortdefault_bpp; unsigned shortmax_bpp; unsigned shortxres; unsigned shortyres; unsigned shortvirtual_x; unsigned shortvirtual_y; unsigned shortwidth; unsigned shortheight; }; |
2、屏的工作时序模式结构
struct fb_videomode { const char *name;/* optional */ u32 refresh;/* optional */ u32 xres; u32 yres; u32 pixclock; u32 left_margin; u32 right_margin; u32 upper_margin; u32 lower_margin; u32 hsync_len; u32 vsync_len; u32 sync; u32 vmode; u32 flag; }; |
到此,S70屏的数据应经存放在smdk4x12_lcd0_pdata结构中,但是谁来用这个已经修改好的smdk4x12_lcd0_pdata屏数据结构呢?
按正常来讲应该是平台设备结构的dev结构成员里边私有数据成员。
接下来我们来找这个私有数据成员。
21.5.9 内核带的平台结构变量s5p_device_fimd0没有初始化平台数据成员
到这里我们还有还有一个问题:内核带的平台数据结构变量没有初始化平台数据成员。
在devs.c中定义如下:
static 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), // 此处应该初始化平台数据,但是没有。??? }, }; |
以上结构根据没有初始化已经修改好的平台数据。
平台设备结构原型:
struct platform_device { const char *name; int id; //这个结构里边有一个void *platform_data成员 struct device dev; u32 num_resources; struct resource*resource; const struct platform_device_id*id_entry; struct mfd_cell *mfd_cell; struct pdev_archdata archdata; }; |
在平台设备结构中成员struct device dev结构原型:
struct device { struct device *parent; struct device_private*p; struct kobject kobj; const char *init_name; /* initial name of the device */ const struct device_type *type; struct mutexmutex;/* mutex to synchronize calls toits driver*/ struct bus_type*bus; /* type of bus device is on */ struct device_driver *driver;/* which driver has allocated this device */ Void *platform_data;/* Platform specific data, device core doesn't touch it */ struct dev_pm_infopower; struct dev_pm_domain *pm_domain; #ifdef CONFIG_NUMA int numa_node; #endif u64 *dma_mask; u64 coherent_dma_mask; struct device_dma_parameters *dma_parms; struct list_headdma_pools; struct dma_coherent_mem*dma_mem; #ifdef CONFIG_CMA struct cma *cma_area; #endif struct dev_archdataarchdata; struct device_node*of_node; dev_tdevt; u32 id; spinlock_t devres_lock; struct list_head devres_head; struct klist_node knode_class; struct class*class; const struct attribute_group **groups; void(*release)(struct device *dev); }; |
没有看到有填充私有数据,那么一定在注册s5p_device_fimd0前使用其他方法把私有数据设置进来,比如调用函数设置了平台数据。
因除了这个结构初始化平台设备数据,没有其他的会做这个事情了。
21.5.10 在lcd数据初始化之后,又对lcd数据进行了设置
搜索 smdk4x12_lcd0_pdata 平台数据结构变量。
查看其他地方私有了这个数据结构。
static void __init smdk4x12_machine_init(void) { …… tiny4412_fb_init_pdata(&smdk4x12_lcd0_pdata); //在lcd数据初始化之后,又对lcd数据进行了设置 s5p_fimd0_set_platdata(&smdk4x12_lcd0_pdata); …… platform_add_devices(smdk4x12_devices, ARRAY_SIZE(smdk4x12_devices)); …… } |
在lcd数据初始化之后,又调用函数s5p_fimd0_set_platdata对lcd数据进行了设置(修改)。
s5p_fimd0_set_platdata函数原型:
void __init s5p_fimd0_set_platdata(struct s3c_fb_platdata *pd) { s3c_set_platdata(pd, sizeof(struct s3c_fb_platdata),&s5p_device_fimd0); } |
注意:
以上的参数s5p_device_fimd0 就是lcd平台设备结构变量。
pd :返推回去就是 smdk4x12_lcd0_pdata。
s3c_set_platdata函数原型:
可以知道pdev 就是 &s5p_device_fimd0。
void __init *s3c_set_platdata(void *pd, //把平台数据结构传进来 size_t pdsize,//平台数据结构的长度 struct platform_device *pdev)//平台设备结构 { void *npd; if (!pd) { printk(KERN_ERR "%s: no platform data supplied\n", pdev->name); return NULL; } npd = kmemdup(pd, pdsize, GFP_KERNEL); if (!npd) { printk(KERN_ERR "%s: cannot clone platform data\n", pdev->name); return NULL; } pdev->dev.platform_data = npd; return npd; } |
该函数先开辟平台数据结构(pdsize)空间,然后把 pd 数据复制到新开辟的空间,返回新的空间地址。
kmemdup函数会开辟一个空间,然后把 pd 指向内存空间中的 pdsize 字节数量复制到新开辟的空间中,返回新开辟空间首地址。
所以,在我们知道该函数先给smdk4x12_lcd0_pdata结构开辟空间;
然后把smdk4x12_lcd0_pdata结构中的数据复制到这个新开辟的空间中;
得到这个存有smdk4x12_lcd0_pdata结构数据的新空间首地址赋值给:
s5p_device_fimd0->dev.platform_data。
此处我们知道s5p_device_fimd0结构的dev结构私有数据就是smdk4x12_lcd0_pdata结构的首地址。
到此,平台设备的数据都已经准备好,只等拿去注册了。
- 小知识:
那有时候会有同学问为什么要这样做?为什么不直接填充到平台结构的私有数据?
出于内存使用合理性考虑。
- 定义在静态存储区,则这个结构的生命周期和系统同在。
只要系统运行着始终会存在,就算驱动模块卸载了,还存在。
2)如果把定义堆空间中,在驱动卸载时候可以把它占用的空间释放。
所以内核中就这样的调用kmemdup函数,在堆空间中开辟一块区域,便于释放。
注意:平台数结构变量加了 __initdata 修饰,这块内存在启动完成后,挂接文件系统后会释放。所以,所在传递给驱动的信息,如果定义时候添加 __initdata 修饰,则需要重新开辟一个空间,把它的值复制到里面去。
下面我通过注册时使用的smdk4x12_devices平台数据结构指针变量反推来查找注册关联。
21.5.11 smdk4x12_devices平台数据结构指针变量定义及填充
就在Mach-tiny4412.c (linux-3.5\arch\arm\mach-exynos)文件中;
定义了注册多平台设备用到的smdk4x12_devices平台数据结构指针变量;
代码如下:
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 }; |
到此,调用注册函数就可以把屏注册进内核了。