(若转载,请注明出处,若有错误请指正,谢谢)
(以下分析皆基于:itop4412精英板设备和代码资源)
(内核为:iTop4412_Kernel_3.0提供)
(看客需要一定的linux平台驱动基础,和lcd操作基础)
lcd的工作,在kernel 中有device 和driver两个描述,这也是必然
在第二节中我们详解介绍了 s3cfb_main.c ——-probe函数的框架。
回顾一下probe函数的作用:
1. 获取平台设备 device中的资源
2. 对设备做了一下相应的初始化
3. 申请了fb_info ,根据要求进行了填充
4. 向内核提交了fb_info
5. 使能设备等
6. 创建属性文件
在上一节中,我们对probe函数中的部分接口进行了阐述,现在接着来看剩下的。
(请与第二节probe函数框架对应分析)
看这一节之前请务必 先了解该三个结构体:
struct fb_info ;
struct fb_fix_screeninfo ;
struct fb_var_screeninfo ;
1 .probe 之 s3cfb_alloc_framebuffer(fbdev[i], i);
该函数的作用在第二节中提到:
/* alloc fb_info */
/* 这个函数在s3cfb_ops.c 文件中 -------fb 操作集合*/
/*这个函数的作用是申请struct fb_info 结构体,初始化fb_info 结构体的信息*/
/*fb_info 结构体 上与内核接口耦合的关系,我们写lcd驱动就是除了初始化相关硬件以外*/
/*就是把fb_info 结构体初始化后 注册到内核,供 fb_mem 调用,上层才能跟底层结合起来*/
/*该函数具体内容将在接下来介绍*/
函数原型如下:
int s3cfb_alloc_framebuffer(struct s3cfb_global *fbdev, int fimd_id)
{
struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);
int ret = 0;
int i;
// 根据wins 个数开辟fb_info 空间的指针,这个在platform device中指定了
//这里并没有申请fb_info空间,数据结构体框图请参见第二节的介绍
/*
static struct s3c_platform_fb default_fb_data __initdata = {
#if defined(CONFIG_ARCH_EXYNOS4)
.hw_ver = 0x70,
#else
.hw_ver = 0x62,
#endif
.nr_wins = 5, //就是它
#if defined(CONFIG_FB_S5P_DEFAULT_WINDOW)
.default_win = CONFIG_FB_S5P_DEFAULT_WINDOW,
#else
.default_win = 0,
#endif
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
};
*/
fbdev->fb = kmalloc(pdata->nr_wins *
sizeof(struct fb_info *), GFP_KERNEL);
if (!fbdev->fb) {
dev_err(fbdev->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc;
}
//接下来就是为每个fb_info 申请空间,进行初始化了
for (i = 0; i < pdata->nr_wins; i++) {
/*这个函数的功能是:开辟 fb_info s3cfb_windows 空间
并且把 fb_info 中的dev 指向 fbdev->dev
把 fb_info->par 指向 s3cfb_windows,可以参考下面的图*/
fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window),
fbdev->dev);
if (!fbdev->fb[i]) {
dev_err(fbdev->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc_fb;
}
/*主要是初始化fb_info 结构体,对var ,fix进行填充等....*/
ret = s3cfb_init_fbinfo(fbdev, i);
if (ret) {
dev_err(fbdev->dev,
"failed to allocate memory for fb%d\n", i);
ret = -ENOMEM;
goto err_alloc_fb;
}
#ifdef CONFIG_FB_S5P_SOFTBUTTON_UI
if (i == pdata->default_win || i == 4)
#else
if (i == pdata->default_win)
#endif
/*主要是为窗体分配存放RGB数据的空间。(该分配一般用DMA)*/
if (s3cfb_map_default_video_memory(fbdev,
fbdev->fb[i], fimd_id)) {
dev_err(fbdev->dev,
"failed to map video memory "
"for default window (%d)\n", i);
ret = -ENOMEM;
goto err_alloc_fb;
}
}
return 0;
err_alloc_fb:
for (i = 0; i < pdata->nr_wins; i++) {
if (fbdev->fb[i])
framebuffer_release(fbdev->fb[i]);
}
kfree(fbdev->fb);
err_alloc:
return ret;
}
fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window),
fbdev->dev);函数的作用开辟 fb_info s3cfb_windows 空间
并且把 fb_info 中的dev 指向 fbdev->dev
把 fb_info->par 指向 s3cfb_windows
现在来看看s3cfb_init_fbinfo函数
int s3cfb_init_fbinfo(struct s3cfb_global *fbdev, int id)
{
/*
对刚刚开辟的fb_info 空间中的相应地址 进行提取,方便接下来的填充
(对fb_info 中相关成员的信息,请查阅相关资料,这里不做阐述)
(所这里需要读者具有一定的功底)
*/
struct fb_info *fb = fbdev->fb[id];
struct fb_fix_screeninfo *fix = &fb->fix;
struct fb_var_screeninfo *var = &fb->var;
struct s3cfb_window *win = fb->par;
struct s3cfb_alpha *alpha = &win->alpha;
struct s3cfb_lcd *lcd = fbdev->lcd;
struct s3cfb_lcd_timing *timing = &lcd->timing;
/*对窗体进行清空,由此可见 fb->par 的作用。设置fix ->id */
memset(win, 0, sizeof(struct s3cfb_window));
platform_set_drvdata(to_platform_device(fbdev->dev), fb);
strcpy(fix->id, S3CFB_NAME);
/* 指定窗体id,窗体数据路径,dma burst ,win的win->power_state 状态,设置透明度方式*/
/* fimd specific */
win->id = id;
/*选择数据来源*/
/*enum s3cfb_data_path_t {
DATA_PATH_FIFO = 0,
DATA_PATH_DMA = 1,
DATA_PATH_IPC = 2,
};*/
win->path = DATA_PATH_DMA;
/*设置dma_burst ,大小范围可以根据数据数据手册决定,请看下图*/
win->dma_burst = 16;
s3cfb_update_power_state(fbdev, win->id, FB_BLANK_POWERDOWN);
alpha->mode = PLANE_BLENDING;
/* fbinfo */
/* 设置fb->fbops = &s3cfb_ops; s3cfb_ops 是一个全局变量在,s3cfb_main 中指定:*/
fb->fbops = &s3cfb_ops;
fb->flags = FBINFO_FLAG_DEFAULT;// 然后是设置FBINFO
fb->pseudo_palette = &win->pseudo_pal;//设置虚拟的调色板地址
#if (CONFIG_FB_S5P_NR_BUFFERS != 1) // 设置 偏移: 一般为0
fix->xpanstep = 2;
fix->ypanstep = 1;
#else
fix->xpanstep = 0;
fix->ypanstep = 0;
#endif
/* 设置type --- FB_TYPE_PACKED_PIXELS -----像素与内存对应,TFT就是基于这个管理内存。根据设备需求不同选择*/
fix->type = FB_TYPE_PACKED_PIXELS;
fix->accel = FB_ACCEL_NONE;//无此设备
/*-----设置显示格式真彩,当然还有黑白*/
/*索引等显示方式,请查阅相关资料*/
fix->visual = FB_VISUAL_TRUECOLOR;
/*设置可变参数宽高,lcd 是一个指针,指向了wa101 结构体,(还记得否?)*/
var->xres = lcd->width;
var->yres = lcd->height;
/*设置虚拟分辨率,嵌入式设备一般不该分辨率*/
/*所以通常设置成 xres和yres 一样*/
/*这里是为了驱动兼容*/
#if defined(CONFIG_FB_S5P_VIRTUAL)
var->xres_virtual = CONFIG_FB_S5P_X_VRES;
var->yres_virtual = CONFIG_FB_S5P_Y_VRES * CONFIG_FB_S5P_NR_BUFFERS;
#else
var->xres_virtual = var->xres;
var->yres_virtual = var->yres * CONFIG_FB_S5P_NR_BUFFERS;
#endif
/* 设置成 32 bpp 的分辨率,这里啰嗦一句: 分辨率由 WINCONx 寄存器中BBPMODE决定,你可以看到这里支持的格式有很多。而代码写死了(可惜,也可能是跟其他硬件兼容,也可能是为了一口气开辟最大的空间,低bpp可以不用
重新申请空间,bpp向下兼容(猜测)
*/
var->bits_per_pixel = 32;
/*设置xoffset ,yoffset 偏移 都为0*/
/*不为0 的话:var->xoffset = var->xres_virtual - var->xres - 1*/
var->xoffset = 0;
/*不为0的话:var->yoffset = var->yres_virtual - var->yres - 1;*/
var->yoffset = 0;
var->width = 0;
var->height = 0;
var->transp.length = 0;
fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
fix->smem_len = fix->line_length * var->yres_virtual;
var->nonstd = 0;//----标准格式 ,!=0则为非标准格式
var->activate = FB_ACTIVATE_NOW;//----完全应用
var->vmode = FB_VMODE_NONINTERLACED;//-----正常扫描
// 以下是设置timing
var->hsync_len = timing->h_sw;
var->vsync_len = timing->v_sw;
var->left_margin = timing->h_bp;
var->right_margin = timing->h_fp;
var->upper_margin = timing->v_bp;
var->lower_margin = timing->v_fp;
//根据相应参数计算pixclock的值,
var->pixclock = (lcd->freq *
(var->left_margin + var->right_margin
+ var->hsync_len + var->xres) *
(var->upper_margin + var->lower_margin
+ var->vsync_len + var->yres));
var->pixclock = KHZ2PICOS(var->pixclock/1000);
//设置fb的R/G/B位域,-----也就是RGB的格式,这里非常简单,不做描述
s3cfb_set_bitfield(var);
/*
设置透明度 模式
设置channel---0 通道
设置value -------最大值不透明
*/
s3cfb_set_alpha_info(var, win);
return 0;
}
dma burst 参数参考
exynos 所能支持的bpp:由寄存器决定
这里是WINCON0 的参数:
接下来看:
if (s3cfb_map_default_video_memory(fbdev,
fbdev->fb[i], fimd_id)) {
dev_err(fbdev->dev,
"failed to map video memory "
"for default window (%d)\n", i);
ret = -ENOMEM;
goto err_alloc_fb;
}
s3cfb_map_default_video_memory 主要是为窗体分配存放RGB数据的空间。(该分配一般用DMA)
函数太长,功能单一,这里就简单的阐述一下就可以了
并且让 fix->smem_start ------指向开辟空间的物理空间 (空间长度有上面fix->mem_len 指定了)
fb->screen_base ------指向开辟空间的虚拟地址
2 probe 之 s3cfb_register_framebuffer(fbdev[i])) ./* register fb_info */
代码如下 终于向内核提交fb_info 了
int s3cfb_register_framebuffer(struct s3cfb_global *fbdev)
{
struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev);
int ret, i, j;
/* on registering framebuffer, framebuffer of default window is registered at first. */
for (i = pdata->default_win; i < pdata->nr_wins + pdata->default_win; i++) {
j = i % pdata->nr_wins;
ret = register_framebuffer(fbdev->fb[j]); //向内核注册lcd设备,成功过后就
//在/dev/fbx的节点 (创建不是drive做的,有兴趣的可以分析该函数)
if (ret) {
dev_err(fbdev->dev, "failed to register \
framebuffer device\n");
return -EINVAL;
}
#ifdef CONFIG_FB_S5P_SOFTBUTTON_UI /* Add Menu UI Window 4 */
if(j==4){
dev_info(fbdev->dev, " set parameters for win4");
s3cfb_check_var_window(fbdev, &fbdev->fb[j]->var, fbdev->fb[j]);
s3cfb_set_par_window(fbdev, fbdev->fb[j]);
}
#endif
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
//lcd 控制台
if (j == pdata->default_win) {
//fops /* 检查参数和设置参数将在后面涉及中&s3cfb_ops;讲到 */
// 检查参数
s3cfb_check_var_window(fbdev, &fbdev->fb[j]->var,
fbdev->fb[j]);
// 设置参数
s3cfb_set_par_window(fbdev, fbdev->fb[j]);
// 显示logo
s3cfb_draw_logo(fbdev->fb[j]);
}
#endif
}
return 0;
}
好了返回probe
接下来:这两个都是硬件相关函数:
3.probe 之s3cfb_set_clock(fbdev[i]);
/*
该函数的主要作用是获得时钟,计算clk,向VIDCON0 配置相应的时钟参数
*/
int s3cfb_set_clock(struct s3cfb_global *ctrl)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
u32 cfg, maxclk, src_clk, vclk, div;
/* spec is under 100MHz */
maxclk = 100 * 1000000;
cfg = readl(ctrl->regs + S3C_VIDCON0);
if (pdata->hw_ver == 0x70) {
cfg &= ~(S3C_VIDCON0_CLKVALUP_MASK |
S3C_VIDCON0_VCLKEN_MASK);
cfg |= (S3C_VIDCON0_CLKVALUP_ALWAYS |
S3C_VIDCON0_VCLKEN_NORMAL);
src_clk = clk_get_rate(ctrl->clock);
printk(KERN_DEBUG "FIMD src sclk = %d\n", src_clk);
} else {
cfg &= ~(S3C_VIDCON0_CLKSEL_MASK |
S3C_VIDCON0_CLKVALUP_MASK |
S3C_VIDCON0_VCLKEN_MASK |
S3C_VIDCON0_CLKDIR_MASK);
cfg |= (S3C_VIDCON0_CLKVALUP_ALWAYS |
S3C_VIDCON0_VCLKEN_NORMAL |
S3C_VIDCON0_CLKDIR_DIVIDED);
if (strcmp(pdata->clk_name, "sclk_fimd") == 0) {
cfg |= S3C_VIDCON0_CLKSEL_SCLK;
src_clk = clk_get_rate(ctrl->clock);
printk(KERN_DEBUG "FIMD src sclk = %d\n", src_clk);
} else {
cfg |= S3C_VIDCON0_CLKSEL_HCLK;
src_clk = ctrl->clock->parent->rate;
printk(KERN_DEBUG "FIMD src hclk = %d\n", src_clk);
}
}
vclk = PICOS2KHZ(ctrl->fb[pdata->default_win]->var.pixclock) * 1000;
if (vclk > maxclk) {
dev_info(ctrl->dev, "vclk(%d) should be smaller than %d\n",
vclk, maxclk);
/* vclk = maxclk; */
}
div = src_clk / vclk;
if (src_clk % vclk) {
if ((src_clk % vclk) > (vclk/2))
div++;
}
if ((src_clk/div) > maxclk)
dev_info(ctrl->dev, "vclk(%d) should be smaller than %d Hz\n",
src_clk/div, maxclk);
cfg &= ~S3C_VIDCON0_CLKVAL_F(0xff);
cfg |= S3C_VIDCON0_CLKVAL_F(div - 1);
writel(cfg, ctrl->regs + S3C_VIDCON0);
//dev_dbg(ctrl->dev,
printk("parent clock: %d, vclk: %d, vclk div: %d\n",
src_clk, vclk, div);
return 0;
}
probe 函数之 s3cfb_enable_window
int s3cfb_enable_window(struct s3cfb_global *fbdev, int id)
{
struct s3cfb_window *win = fbdev->fb[id]->par;
if (!win->enabled)
atomic_inc(&fbdev->enabled_win);
if (s3cfb_window_on(fbdev, id)) { //使能窗口,具体操作如下
win->enabled = 0;
return -EFAULT;
} else {
win->enabled = 1;
return 0;
}
}
int s3cfb_window_on(struct s3cfb_global *ctrl, int id)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
u32 cfg;
if ((pdata->hw_ver == 0x62) || (pdata->hw_ver == 0x70)) {
cfg = readl(ctrl->regs + S3C_WINSHMAP);
cfg |= S3C_WINSHMAP_CH_ENABLE(id); //选择通道
writel(cfg, ctrl->regs + S3C_WINSHMAP);
}
cfg = readl(ctrl->regs + S3C_WINCON(id));
cfg |= S3C_WINCON_ENWIN_ENABLE; //使能数据,可以查WINCONx ,第0位
writel(cfg, ctrl->regs + S3C_WINCON(id));
dev_dbg(ctrl->dev, "[fb%d] turn on\n", id);
return 0;
}
SHADOWCON 使能
Base Address = 0x11C0_0000
Address = Base Address + 0x0034, Reset Value = 0x0000_0000
WINCON0 使能窗口数据手册
1probe 函数之
//设置窗口状态,由程序员标记
s3cfb_update_power_state(fbdev[i], pdata->default_win,
FB_BLANK_UNBLANK);
//着重看这儿,真正开启了lcd设备
s3cfb_display_on(fbdev[i]);
int s3cfb_display_on(struct s3cfb_global *ctrl)
{
u32 cfg;
cfg = readl(ctrl->regs + S3C_VIDCON0);
cfg |= (S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE);
writel(cfg, ctrl->regs + S3C_VIDCON0);
dev_dbg(ctrl->dev, "global display is on\n");
return 0;
}
至此probe函数就介绍的差不多了,主要功能就这些,其他就略过。
剩下就是要介绍下面的操作函数 (还有win的尺寸,buf地址,坐标,画图等都还没 介绍)
——未完待续