LCD(四)平台设备驱动之 platform_device
一 、重要结构体
1、fb_info结构体
struct fb_info {
int node;
int flags;
struct mutex lock; /* 用于open/release/ioctl的锁*/
struct fb_var_screeninfo var;/*LCD可变参数*/
struct fb_fix_screeninfo fix;/*LCD固定参数*/
struct fb_monspecs monspecs; /*LCD显示器标准*/
struct work_struct queue; /*帧缓冲事件队列*/
struct fb_pixmap pixmap; /*图像硬件mapper*/
struct fb_pixmap sprite; /*光标硬件mapper*/
struct fb_cmap cmap; /*当前的颜色表*/
struct fb_videomode *mode; /*当前的显示模式*/
#ifdef CONFIG_FB_BACKLIGHT
struct backlight_device *bl_dev;/*对应的背光设备*/
struct mutex bl_curve_mutex;
u8 bl_curve[FB_BACKLIGHT_LEVELS];/*背光调整*/
#endif
#ifdef CONFIG_FB_DEFERRED_IO
struct delayed_work deferred_work;
struct fb_deferred_io *fbdefio;
#endif
struct fb_ops *fbops; /*对底层硬件操作的函数指针*/
struct device *device;
struct device *dev; /*fb设备*/
int class_flag;
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /*图块Blitting*/
#endif
char __iomem *screen_base; /*虚拟基地址*/
unsigned long screen_size; /*LCD IO映射的虚拟内存大小*/
void *pseudo_palette; /*伪16色颜色表*/
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /*LCD的挂起或恢复状态*/
void *fbcon_par;
void *par;
};
其中,比较重要的成员有struct fb_var_screeninfo var、struct fb_fix_screeninfo fix和struct fb_ops *fbops,他们也都是结构体。
2、struct fb_var_screeninfo
//fb_var_screeninfo结构体主要记录用户可以修改的控制器的参数
struct fb_var_screeninfo {
__u32 xres; /*可见屏幕一行有多少个像素点*/
__u32 yres; /*可见屏幕一列有多少个像素点*/
__u32 xres_virtual; /*虚拟屏幕一行有多少个像素点*/
__u32 yres_virtual; /*虚拟屏幕一列有多少个像素点*/
__u32 xoffset; /*虚拟到可见屏幕之间的行偏移*/
__u32 yoffset; /*虚拟到可见屏幕之间的列偏移*/
__u32 bits_per_pixel; /*每个像素的位数即BPP*/
__u32 grayscale; /*非0时,指的是灰度*/
struct fb_bitfield red; /*fb缓存的R位域*/
struct fb_bitfield green; /*fb缓存的G位域*/
struct fb_bitfield blue; /*fb缓存的B位域*/
struct fb_bitfield transp; /*透明度*/
__u32 nonstd; /* != 0 非标准像素格式*/
__u32 activate;
__u32 height; /*高度*/
__u32 width; /*宽度*/
__u32 accel_flags;
/*定时:除了pixclock本身外,其他的都以像素时钟为单位*/
__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 rotate;
__u32 reserved[5]; /*保留*/
};
3、fb_fix_screeninfo结构体//fb_fix_screeninfo结构体又主要记录用户不可以修改的控制器的参数
struct fb_fix_screeninfo {
char id[16]; /*字符串形式的标示符 */
unsigned long smem_start; /*fb缓存的开始位置 */
__u32 smem_len; /*fb缓存的长度 */
__u32 type; /*看FB_TYPE_* */
__u32 type_aux; /*分界*/
__u32 visual; /*看FB_VISUAL_* */
__u16 xpanstep; /*如果没有硬件panning就赋值为0 */
__u16 ypanstep; /*如果没有硬件panning就赋值为0 */
__u16 ywrapstep; /*如果没有硬件ywrap就赋值为0 */
__u32 line_length; /*一行的字节数 */
unsigned long mmio_start; /*内存映射IO的开始位置*/
__u32 mmio_len; /*内存映射IO的长度*/
__u32 accel;
__u16 reserved[3]; /*保留*/
};
4、fb_ops结构体//fb_ops结构体是对底层硬件操作的函数指针,该结构体中定义了对硬件的操作有:(这里只列出了常用的操作)
struct fb_ops {
struct module *owner;
//检查可变参数并进行设置
int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);
//根据设置的值进行更新,使之有效
int (*fb_set_par)(struct fb_info *info);
//设置颜色寄存器
int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
unsigned blue, unsigned transp, struct fb_info *info);
//显示空白
int (*fb_blank)(int blank, struct fb_info *info);
//矩形填充
void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
//复制数据
void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
//图形填充
void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);
};
二、帧缓冲设备作为平台设备
S5PV210的LCD控制器被集成在芯片内部作为一个相对独立的单元,所以Linux把它看做一个平台设备,故在内核代码/arch/arm/plat-s5p/devs.c中需要定义LCD相关的平台设备及资源,代码如下:
/* LCD Controller */
//LCD控制器的资源信息
static struct resource s5p_lcd_resource[] = {
[0] = {
.start = S5P_PA_LCD, //控制器IO端口开始地址
.end = S5P_PA_LCD+ S5P_SZ_LCD- 1,//控制器IO端口结束地址
.flags = IORESOURCE_MEM,//标识为LCD控制器IO端口,在驱动中引用这个就表示引用IO端口
},
[1] = {
.start = IRQ_LCD1,//LCD中断
.end = IRQ_LCD1,
.flags = IORESOURCE_IRQ,//标识为LCD中断
},
[2] = {
.start = IRQ_LCD0,
.end = IRQ_LCD0,
.flags = IORESOURCE_IRQ,
},
};
static u64 s5p_device_lcd_dmamask= 0xffffffffUL;
struct platform_device s5p_device_lcd = {
.name = "s5pv210-lcd",//作为平台设备的LCD设备名
.id = -1,
.num_resources = ARRAY_SIZE(s5p_lcd_resource),//资源数量
.resource = s5p_lcd_resource,//引用上面定义的资源
.dev = {
.dma_mask = &s5p_device_lcd_dmamask, //设备DMA可以访问的内存范围(0xffffffffUL)
.coherent_dma_mask = 0xffffffffUL //作用于申请一致性的DMA缓冲区,32位系统为0xffffffffUL
}
};
EXPORT_SYMBOL(s5p_device_lcd);//导出定义的LCD平台设备,好在mach-tq210.c的tq210_devices[]中添加到平台设备列表中
/arch/arm/plat-samsung/dev-fb.c中添加如下,s3c_fb_set_platform用于挂接fb平台设备的私有数据
void __init s3c_fb_set_platdata(struct s3c_fb_platdata *pd)
{
struct s3c_platform_fb *npd;
if (!pd) {
printk(KERN_ERR "%s: no platform data\n", __func__);
return;
}
//这里就是将内核中定义的s3c_platform_fb 结构体数据保存到LCD平台数据中,所以在写驱动的时候
//就可以直接在平台数据中获取s3c_platform_fb 结构体的数据(即LCD各种参数信息)进行操作
npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL);
if (!npd)
printk(KERN_ERR "%s: no memory for platform data\n", __func__);
else {
for (i = 0; i < npd->nr_wins; i++)
npd->nr_buffers[i] = 1;
npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS; // 再进一步对数据结构进行填充
s3cfb_get_clk_name(npd->clk_name);
npd->clk_on = s3cfb_clk_on;
npd->clk_off = s3cfb_clk_off;
/* starting physical address of memory region */
npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);
/* size of memory region */
npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1);
s3c_device_fb.dev.platform_data = npd; // 把传进来的 s3c_platform_fb 结构体变量挂载到 s3c_device_fb变量中
}
s5pv210_device_lcd.dev.platform_data = npd;
}
arch/arm/mach-sp5v210/include/mach/map.h中添加相关宏定义
#define S5PV210_PA_LCD (0xF8000000)
#define S5P_PA_LCD S5PV210_PA_LCD
#define S5PV210_SZ_LCD SZ_1M
#define S5P_SZ_LCD S5PV210_SZ_LCD
意外之喜:如果前期配置终端显示LOGO没有成功,配置完以上代码,发现启动后显示有企鹅LOGO。
除此之外,需要在/arch/arm/plat-samsung/include/plat/fb.h中为LCD平台设备定义了一个 s3c_platform_fb结构体,该结构体主要是记录LCD的硬件参数信息(比如该结构体的s3cfb_lcd成员结构中就用于记录LCD的屏幕尺寸、屏幕信息、可变的屏幕参数、LCD配置寄存器等),这样在写驱动的时候就直接使用这个结构体。
#define FB_SWAP_WORD (1 << 24)
#define FB_SWAP_HWORD (1 << 16)
#define FB_SWAP_BYTE (1 << 8)
#define FB_SWAP_BIT (1 << 0)
struct platform_device;
struct clk;
struct s3c_platform_fb {
int hw_ver;
char clk_name[16];
int nr_wins; //虚拟窗口的数量
int nr_buffers[5];
int default_win; //当前默认的窗口
int swap;
phys_addr_t pmem_start[5]; /* starting physical address of memory region */ /显存的物理起始地址
size_t pmem_size[5]; /* size of memory region */ //显存的大小
void *lcd; //指向s3cfb_lcd结构体,主要定义屏幕的参数信息
void (*cfg_gpio)(struct platform_device *dev); //LCD相关GPIO配置
int (*backlight_on)(struct platform_device *dev); //打开背光
int (*backlight_onoff)(struct platform_device *dev, int onoff); //关闭背光
int (*reset_lcd)(struct platform_device *dev); //复位LCD
int (*clk_on)(struct platform_device *pdev, struct clk **s3cfb_clk); //LCD相关时钟打开
int (*clk_off)(struct platform_device *pdev, struct clk **clk); //LCD相关时钟关闭
};
在/drivers/video/samsung/s3cfb.h中定义s3cfb_lcd结构体,本次移植需要直接添加s3cfb.h文件,并在mach-tq210.c文件中添加头文件#include <../../../drivers/video/samsung/s3cfb.h>
struct s3cfb_lcd {
int width;
int height;
int p_width;
int p_height;
int bpp;
int freq; //LCD刷新频率
struct s3cfb_lcd_timing timing; //LCD时序参数
struct s3cfb_lcd_polarity polarity; //LCD电平信号是否进行翻转
void (*init_ldi)(void); //初始化LDI
void (*deinit_ldi)(void);
};
下面,我们来看一下内核是如果使用这个结构体的。在/arch/arm/mach-s5pv210/mach-tq210.c中定义有:
static void EmbedSky_LCD_cfg_gpio(struct platform_device *pdev)
{
int i;
/* 将相应的IO引脚设置为LCD 相关功能 ,禁用上拉、下拉功能 */
for (i = 0; i < 8; i++) {
s3c_gpio_cfgpin(S5PV210_GPF0(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF0(i), S3C_GPIO_PULL_NONE);
}
for (i = 0; i < 8; i++) {
s3c_gpio_cfgpin(S5PV210_GPF1(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF1(i), S3C_GPIO_PULL_NONE);
}
for (i = 0; i < 8; i++) {
s3c_gpio_cfgpin(S5PV210_GPF2(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF2(i), S3C_GPIO_PULL_NONE);
}
for (i = 0; i < 4; i++) {
s3c_gpio_cfgpin(S5PV210_GPF3(i), S3C_GPIO_SFN(2));
s3c_gpio_setpull(S5PV210_GPF3(i), S3C_GPIO_PULL_NONE);
}
/* mDNIe SEL: why we shall write 0x2 ? */
writel(0x2, S5P_MDNIE_SEL);
writel(0xC0, S5PV210_GPF0_BASE + 0xc);
/* 赋予最大的驱动能力 */ // 不懂
/*writel(0xffffffff, S5PV210_GPF0_BASE + 0xc);
writel(0xffffffff, S5PV210_GPF1_BASE + 0xc);
writel(0xffffffff, S5PV210_GPF2_BASE + 0xc);
writel(0x000000ff, S5PV210_GPF3_BASE + 0xc);*/
}
#define S5PV210_GPD_0_0_TOUT_0 (0x2)
#define S5PV210_GPD_0_1_TOUT_1 (0x2 << 4)
#define S5PV210_GPD_0_2_TOUT_2 (0x2 << 8)
#define S5PV210_GPD_0_3_TOUT_3 (0x2 << 12)
static int EmbedSky_LCD_backlight_on(struct platform_device *pdev)
{
int err;
err = gpio_request(S5PV210_GPD0(0), "GPD0");
if (err) {
printk(KERN_ERR "failed to request GPD0 for "
"lcd backlight control\n");
return err;
}
gpio_direction_output(S5PV210_GPD0(0), 1);
#if defined(CONFIG_FB_S3C_VGA640480) || defined(CONFIG_FB_S3C_VGA800600) || defined(CONFIG_FB_S3C_VGA1024X768)
s3c_gpio_cfgpin(S5PV210_GPD0(0), /*S5PV210_GPD_0_0_TOUT_0*/1);
#else
s3c_gpio_cfgpin(S5PV210_GPD0(0), S5PV210_GPD_0_0_TOUT_0); //设置GPD0_0为输出状态
//s3c_gpio_setpull(S5PV210_GPD0(0), S3C_GPIO_PULL_UP); // 使能GPD0_0引脚上拉电阻
//gpio_set_value(S5PV210_GPD0(0), 1); // 将GPD0_0设置成高电平
//s3c_gpio_cfgpin(S5PV210_GPD0(0), S3C_GPIO_SFN(2)); // 将GPD0_0设置为TOUT_0功能
#endif
gpio_free(S5PV210_GPD0(0));
return 0;
}
static int EmbedSky_LCD_backlight_off(struct platform_device *pdev, int onoff)
{
int err;
err = gpio_request(S5PV210_GPD0(0), "GPD0");
if (err) {
printk(KERN_ERR "failed to request GPD0 for "
"lcd backlight control\n");
return err;
}
//s3c_gpio_cfgpin(S5PV210_GPD0(0), S3C_GPIO_OUTPUT);
//s3c_gpio_setpull(S5PV210_GPD0(0), S3C_GPIO_PULL_DOWN);
//gpio_set_value(S5PV210_GPD0(0), 0);
gpio_direction_output(S5PV210_GPD0(0), 0);
gpio_free(S5PV210_GPD0(0));
return 0;
}
static struct s3cfb_lcd A70_TN92 = {
.width = 800, //LCD宽度
.height = 480, //LCD高度
.p_width = 152,
.p_height = 90,
.bpp = 32, //像素位数
.freq = 100, //帧率 怎样获取?
// .clkval_f = 6,
//时序,根据具体的LCD芯片手册设置
.timing = {
.h_fp = 14, // 行数据传输完成后到下一个行同步信号到来的延迟 HFPD+1
.h_bp = 27, // 行同步信号完成后到行数据传输前的延迟 HFPD+1
.h_sw = 20, // 行同步信号的脉宽 HSPW +1
.v_fp = 22, // 帧数据传输完成后到下一个帧同步信号到来的延迟 VFPD+1
.v_fpe = 1, // vertical front porch for even field, ?
.v_bp = 10, // 帧同步信号完成后到帧数据传输前的延迟 VBPD+1
.v_bpe = 1, // vertical back porch for even field, ?
.v_sw = 13, // 帧同步信号的脉宽 VSPW +1
},
//极性,通过对比s5pv210芯片手册中LCD控制器的时序与LCD的数据手册的时序
.polarity = {
.rise_vclk = 0,
.inv_hsync = 1,
.inv_vsync = 1,
.inv_vden = 0,
},
};
static struct s3c_platform_fb EmbedSky_LCD_fb_data __initdata = {
.hw_ver = 0x62, //难道是hardware version ?
.clk_name = "sclk_fimd",
.nr_wins = 5, //number window? s5pv210有5个window
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, //默认的window=0
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
.lcd = &A70_TN92,//记录LCD硬件参数信息
.cfg_gpio = EmbedSky_LCD_cfg_gpio,
.backlight_on = EmbedSky_LCD_backlight_on, //开启背光
.backlight_onoff = EmbedSky_LCD_backlight_off, //关闭背光
.reset_lcd = EmbedSky_LCD_reset_lcd, //重启LCD
};
在 arch/arm/mach-tq210/mach-tq210.c 中的 tq210_devices[]数组中添加了s5pv210_device_lcd:
static struct platform_device *tq210_devices[] __initdata = {
..
&s5pv210_device_lcd,
...
}
而在tq210_machine_init函数中将smdkv210_devices加入了platform,并将ek070tn93设置为s5pv210_device_lcd的platform data:static void __init tq210_machine_init(void)
{
...
// s3c_fb_set_platdata(&tq210_lcd0_pdata);
#if defined(CONFIG_FB_TQ_AUTO_DETECT)
EmbedSky_LCD_fb_data.lcd = get_s5pv210_fb();
#endif
s3c_fb_set_platdata(&EmbedSky_LCD_fb_data);//给平台设备设置私有数据,挂接fb平台设备私有数据
platform_add_devices(tq210_devices, ARRAY_SIZE(tq210_devices));//平台设备注册
...
}
fb的驱动是基于platform平台总线的,所以需要提供platform_device(注册平台设备)和platform_driver(注册平台驱动)。前面讲的是平台驱动部分那么它对应的平台设备的注册在什么地方呢? 答案就是之前说的mach文件中,我这里是 arch\arm\mach-s5pv210\mach-tq210.c 这个文件。
之前说了,这个文件中注册了很多的系统中可能用到的平台设备,将来写驱动的时候,只需要注册平台驱动即可,当然如果没有,可能就需要自己去添加。
这个文件中将所有的平台设备结构体都放在一个 struct platform_device *类型的数组smdkc110_devices中,将所有定义好的platform_device结构体挂接到这个数组中去,
在tq210_machine_init函数中将所有的平台设备都进行了注册。tq210_machine_init这个函数其实是被链接在Linux启动的各个初始化段中的某一个,所以当系统启动的时候,执行了初始化段中的函数时,tq210_machine_init函数就会被调用。