lcd整个架构是已platform_form总线的形式注册在系统中的,platform_form总线有device和driver,下面分别从lcd的device和driver两部分来分析tiny210 lcd架构。
linux很多总线的device一般在开机过程中由系统去注册好,driver部分的注册则在驱动中进行注册,像spi、i2c等等都是这样。Lcd是通过虚拟总线进行注册的,所以也是在系统中去注册device,具体流程图如下所示:
在linux-3.0.8/arch/arm/mach-s5pv210/mach-mini210.c中的mini210_machine_init函数定义在MACHINE_START中,在系统启动后,会直接运行改函数,mini210_machine_init函数中有注册device设备:
注册完成后,会去设置获取一些lcd参数、地址资源:
mini210_get_lcd定义在linux-3.0.8/arch/arm/mach-s5pv210/mini210-lcds.c中:
struct s3cfb_lcd *mini210_get_lcd(void)
{
return panel_lcd_list[lcd_idx].lcd;
}
会去获取一些定义好的lcd参数,如分辨率、频率、lcd name等等。
调用s3c_fb_set_platdata函数时会用mini210_fb_data作为参数,mini210_fb_data定义如下所示:
static struct s3c_platform_fb mini210_fb_data __initdata = {
.hw_ver = 0x62,
.clk_name = "sclk_fimd", //clk 名字
.nr_wins = 5, //默认定义窗口
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW,
.swap = FB_SWAP_WORD | FB_SWAP_HWORD,
.cfg_gpio = lcd_cfg_gpio, // 初始化配置GPIO
.backlight_on = lcd_backlight_on, //打开背光
.backlight_onoff= lcd_backlight_off, //关闭背光
.reset_lcd = lcd_reset_lcd, // reset资源
};
s3c_fb_set_platdata函数在linux-3.0.8/arch/arm/plat-samsung/ dev-fb.c中,如下所示:
static struct resource s3cfb_resource[] = {
[0] = {
.start = S5P_PA_LCD, //资源地址
.end = S5P_PA_LCD + S5P_SZ_LCD - 1,
.flags = IORESOURCE_MEM, //driver会获取这个资源
},
[1] = {
.start = IRQ_LCD1, //lcd中断1
.end = IRQ_LCD1,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_LCD0, //lcd中断0
.end = IRQ_LCD0,
.flags = IORESOURCE_IRQ,
},
};
static u64 fb_dma_mask = 0xffffffffUL; //dma 掩码
struct platform_device s3c_device_fb = { //fb设备定义和描述
.name = "s3cfb", //设备名字
.id = -1,
.num_resources = ARRAY_SIZE(s3cfb_resource),
.resource = s3cfb_resource,
.dev = {
.dma_mask = &fb_dma_mask,
.coherent_dma_mask = 0xffffffffUL
}
};
static struct s3c_platform_fb default_fb_data __initdata = {
#if defined(CONFIG_CPU_S5PV210_EVT0)
.hw_ver = 0x60,
#else
.hw_ver = 0x62,
#endif
.nr_wins = 5, //支持的窗口数
.default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, //指定默认显示窗口
.swap = FB_SWAP_WORD | FB_SWAP_HWORD, //数据字交换
};
void __init s3c_fb_set_platdata(struct s3c_platform_fb *pd)
{
struct s3c_platform_fb *npd;
struct s3cfb_lcd *lcd;
phys_addr_t pmem_start;
int i, default_win, num_overlay_win;
int frame_size;
if (!pd) //判断传入的s3c_platform_fb是否有定义,没有定义使用默认定义
pd = &default_fb_data;
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 < NR_BUFFERS; i++) {
npd->nr_buffers[i] = 1; //初始化每个窗口
}
default_win = npd->default_win;
num_overlay_win = CONFIG_FB_S3C_NUM_OVLY_WIN;
if (num_overlay_win >= default_win) {
printk(KERN_WARNING "%s: NUM_OVLY_WIN should be less than default \
window number. set to 0.\n", __func__);
num_overlay_win = 0;
}
lcd = (struct s3cfb_lcd *)npd->lcd;
frame_size = ALIGN(lcd->width * lcd->height * 4, PAGE_SIZE);
s3cfb_get_clk_name(npd->clk_name); //获取时钟
npd->backlight_onoff = NULL;
npd->clk_on = s3cfb_clk_on; //使能lcd设备
npd->clk_off = s3cfb_clk_off; //关闭lcd 设备
/* set starting physical address & size of memory region for
* overlay window and default window */
pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1);
printk("fimd at 0x%08x\n", pmem_start);
for (i = 0; i < num_overlay_win; i++) {
npd->nr_buffers[i] = CONFIG_FB_S3C_NUM_BUF_OVLY_WIN;
npd->pmem_start[i] = pmem_start;
npd->pmem_size[i] = frame_size * npd->nr_buffers[i];
pmem_start += npd->pmem_size[i];
}
npd->nr_buffers[default_win] = CONFIG_FB_S3C_NR_BUFFERS;
npd->pmem_start[default_win] = pmem_start;
npd->pmem_size[default_win] = frame_size * npd->nr_buffers[default_win];
#if defined(CONFIG_MACH_MINI210)
npd->pmem_size[default_win] += ALIGN(1280*720, PAGE_SIZE) * 3;
npd->pmem_size[default_win] += ALIGN(1280*360, PAGE_SIZE) * 3 + PAGE_SIZE;
if (frame_size > 0x200000) {
pmem_start += npd->pmem_size[default_win];
for (; i < npd->nr_wins; i++) {
if (i != default_win) {
npd->nr_buffers[i] = 2;
npd->pmem_start[i] = pmem_start;
npd->pmem_size[i] = frame_size * npd->nr_buffers[i];
break;
}
}
}
#endif
s3c_device_fb.dev.platform_data = npd;
}
}
lcd从应用层到硬件驱动流程如下所示:
对于lcd框架,我们主要关注的是后面三部分,Framebuffer子系统内部框架、LCD控制器编程、LCD硬件驱动,前两部分已经被抽象并实现在Linux driver发布源码中了,我们只需要理解framebuffer内部框架和接口即可,第三部分,主要是对具体的硬件平台SOC和具体的LCD(焊接连接到该SOC的引脚上)来进行的寄存器设置。
Lcd整天框架如下所示:
由上图可以看出 lcd的应用层 通过内核的fbmem接口再调用驱动xxxfb.c的内容,而fbmem接口是内核提供的,所有驱动设计人员主要的任务就是定义一个fb_info 结构体(该结构由内核提供),然后填充结构体中的内容做好相应的初始化后,提交给内核就可以了。
Tiny210开发板驱动在linux-3.0.8/drivers/video/samsung目录下面:
Framebuffer类似于input一样的子系统,在fbmem_init中通过register_chrdev接口向系统注册一个主设备号位29的字符设备驱动。通过class_create创建graphics设备类,配合mdev机制生成供用户访问的设备文件(位于/dev目录)。
static int __init
fbmem_init(void)
{
//向proc文件系统报告驱动状态和参数
proc_create("fb", 0, NULL, &fb_proc_fops);
//注册字符设备驱动,主设备号29
if (register_chrdev(FB_MAJOR,"fb",&fb_fops))
printk("unable to get major %d for fb devs\n", FB_MAJOR);
//创建/sys/class/graphics设备类,配合mdev生成设备问题
fb_class = class_create(THIS_MODULE, "graphics");
if (IS_ERR(fb_class)) {
printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class));
fb_class = NULL;
}
return 0;
}
static const struct file_operations fb_fops = {
.owner = THIS_MODULE,
.read = fb_read,
.write = fb_write, //二次拷贝
.unlocked_ioctl = fb_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = fb_compat_ioctl,
#endif
.mmap = fb_mmap, // 映射 一次拷贝
.open = fb_open,
.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA
.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO
.fsync = fb_deferred_io_fsync,
#endif
.llseek = default_llseek,
};
Framebuffer注册就是一个字符设备注册,应用层通过open(“/dev/fb0”,…)访问framebuffer设备,然后通过read、ioctl、mmap等方式获取lcd信息。在linux设备驱动中,所有的显示缓存设备均由framebuffer子系统内部管理,即linux设备驱动框架只认识一个主设备号为29的framebuffer设备。应用层所有针对显示缓存(最多32个)的访问均会推送给fb_fops进行进一步分发操作。
fbmem.c除了注册设备节点外,还提供了注册framebuff接口,驱动加载时候,可以通过register_framebuffer接口从framebuffer子系统中注册自己的接口,我们先看几个比较重要的结构体:
fb_info结构体代表单个显示缓存从设备,在调用register_framebuffer接口之前,必须要初始化其中的重要数据成员。其定义如下:
struct fb_info {
atomic_t count;
int node; //次设备号
int flags;
struct mutex lock; /* Lock for open/release/ioctl funcs */
struct mutex mm_lock; /* Lock for fb_mmap and smem_* fields */
struct fb_var_screeninfo var; /* Current var */ // LCD可变参数结构体
struct fb_fix_screeninfo fix; /* Current fix */ // LCD固定参数结构体
struct fb_monspecs monspecs; /* Current Monitor specs */
struct work_struct queue; /* Framebuffer event queue */ //帧缓存事件队列
struct fb_pixmap pixmap; /* Image hardware mapper */
struct fb_pixmap sprite; /* Cursor hardware mapper */
struct fb_cmap cmap; /* Current cmap */
struct list_head modelist; /* mode list */
struct fb_videomode *mode; /* current mode */ //显示模式
#ifdef CONFIG_FB_BACKLIGHT
/* assigned backlight device */
/* set before framebuffer registration,
remove after unregister */
struct backlight_device *bl_dev;
/* Backlight level curve */
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; //LCD底层硬件操作接口
struct device *device; /* This is the parent */ //设备驱动模型
struct device *dev; /* This is this fb device */
int class_flag; /* private sysfs flags */
#ifdef CONFIG_FB_TILEBLITTING
struct fb_tile_ops *tileops; /* Tile Blitting */
#endif
char __iomem *screen_base; /* Virtual address */ //显示内存虚拟地址
unsigned long screen_size; /* Amount of ioremapped VRAM or 0 */ //显示内存大小
void *pseudo_palette; /* Fake palette of 16 colors */
#define FBINFO_STATE_RUNNING 0
#define FBINFO_STATE_SUSPENDED 1
u32 state; /* Hardware state i.e suspend */
void *fbcon_par; /* fbcon use-only private area */
/* From here on everything is device dependent */
void *par;
/* we need the PCI or similar aperture base/size not
smem_start/size as smem_start may just be an object
allocated inside the aperture so may not actually overlap */
struct apertures_struct {
unsigned int count;
struct aperture {
resource_size_t base;
resource_size_t size;
} ranges[0];
} *apertures;
}
其中,fb_var_screeninfo和fb_fix_screeninfo两个结构体跟LCD硬件属性相关,fb_var_screeninfo代表可修改的LCD显示参数,如分辨率和像素比特数;fb_fix_screeninfo代表不可修改的LCD属性参数,如显示内存的物理地址和长度等。另外一个非常重要的成员是fb_ops,其是LCD底层硬件操作接口集。
fb_ops硬件操作接口集包含很多接口,如设置可变参数fb_set_par、设置颜色寄存器fb_setcolreg、清屏接口fb_blank、画位图接口fb_imageblit、内存映射fb_mmap等等。
struct fb_var_screeninfo {
__u32 xres; /* visible resolution */ //LCD分辨率
__u32 yres;
__u32 xres_virtual; /* virtual resolution */
__u32 yres_virtual;
__u32 xoffset; /* offset from virtual to visible */
__u32 yoffset; /* resolution */
__u32 bits_per_pixel; /* guess what */
__u32 grayscale; /* != 0 Graylevels instead of colors */
struct fb_bitfield red; /* bitfield in fb mem if true color, */
struct fb_bitfield green; /* else only length is significant */
struct fb_bitfield blue;
struct fb_bitfield transp; /* transparency */
__u32 nonstd; /* != 0 Non standard pixel format */
__u32 activate; /* see FB_ACTIVATE_* */
__u32 height; /* height of picture in mm */
__u32 width; /* width of picture in mm */
__u32 accel_flags; /* (OBSOLETE) see fb_info.flags */
/* Timing: All values in pixclocks, except pixclock (of course) */
__u32 pixclock; /* pixel clock in ps (pico seconds) */
__u32 left_margin; /* time from sync to picture */
__u32 right_margin; /* time from picture to sync */
__u32 upper_margin; /* time from sync to picture */
__u32 lower_margin;
__u32 hsync_len; /* length of horizontal sync */
__u32 vsync_len; /* length of vertical sync */
__u32 sync; /* see FB_SYNC_* */
__u32 vmode; /* see FB_VMODE_* */
__u32 rotate; /* angle we rotate counter clockwise */
__u32 reserved[5]; /* Reserved for future compatibility */
};
struct fb_fix_screeninfo {
char id[16]; /* identification string eg "TT Builtin" */
unsigned long smem_start; /* Start of frame buffer mem */
/* (physical address) */
__u32 smem_len; /* Length of frame buffer mem */
__u32 type; /* see FB_TYPE_* */
__u32 type_aux; /* Interleave for interleaved Planes */
__u32 visual; /* see FB_VISUAL_* */
__u16 xpanstep; /* zero if no hardware panning */
__u16 ypanstep; /* zero if no hardware panning */
__u16 ywrapstep; /* zero if no hardware ywrap */
__u32 line_length; /* length of a line in bytes */
unsigned long mmio_start; /* Start of Memory Mapped I/O */
/* (physical address) */
__u32 mmio_len; /* Length of Memory Mapped I/O */
__u32 accel; /* Indicate to driver which */
/* specific chip/card we have */
__u16 reserved[3]; /* Reserved for future compatibility */
};
然后,在看看framebuffer注册过程:
int
register_framebuffer(struct fb_info *fb_info)
{
int ret;
mutex_lock(®istration_lock);
ret = do_register_framebuffer(fb_info);
mutex_unlock(®istration_lock);
return ret;
}
static int do_register_framebuffer(struct fb_info *fb_info)
{
int i;
struct fb_event event;
struct fb_videomode mode;
if (fb_check_foreignness(fb_info))
return -ENOSYS;
do_remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id,
fb_is_primary_device(fb_info));
//如果注册的帧缓冲设备超过了FB_MAX(目前定义为32),
//则返回为-ENXIO,注册成功则返回为0。
if (num_registered_fb == FB_MAX)
return -ENXIO;
num_registered_fb++; //已经注册了的帧缓冲区硬件设备个数
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i]) //registered_fb[i]保存所有已经注册了的帧缓冲区硬件设备
break;
fb_info->node = i;
atomic_set(&fb_info->count, 1);
mutex_init(&fb_info->lock);
mutex_init(&fb_info->mm_lock);
//在/sys/class/graphics下创建fbx设备,用于设备文件的创建
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
if (IS_ERR(fb_info->dev)) {
/* Not fatal */
printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev));
fb_info->dev = NULL;
} else
fb_init_device(fb_info);
if (fb_info->pixmap.addr == NULL) {
fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
if (fb_info->pixmap.addr) {
fb_info->pixmap.size = FBPIXMAPSIZE;
fb_info->pixmap.buf_align = 1;
fb_info->pixmap.scan_align = 1;
fb_info->pixmap.access_align = 32;
fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
}
}
fb_info->pixmap.offset = 0;
if (!fb_info->pixmap.blit_x)
fb_info->pixmap.blit_x = ~(u32)0;
if (!fb_info->pixmap.blit_y)
fb_info->pixmap.blit_y = ~(u32)0;
if (!fb_info->modelist.prev || !fb_info->modelist.next)
INIT_LIST_HEAD(&fb_info->modelist);
fb_var_to_videomode(&mode, &fb_info->var);
fb_add_videomode(&mode, &fb_info->modelist);
registered_fb[i] = fb_info;
event.info = fb_info;
if (!lock_fb_info(fb_info))
return -ENODEV;
//通知帧缓冲区控制台,有一个新的帧缓冲区设备被注册到内核中来了
fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event);
unlock_fb_info(fb_info);
return 0;
}
每个调用register_framebuffer注册的从设备都要传递一个fb_info结构体指针,其即代表单个显示缓存设备。从中,可以看到fb_info最终会存储到全局数组struct fb_info*registered_fb[FB_MAX]中,FB_MAX是32,从这里我们也可以看出,framebuffer最多支持32个从设备。另外,每个从设备注册还会在/sys/class/graphics/设备类中创建一个设备,最终由mdev在/dev/目录中生成对应的设备文件。假设M个从设备调用register_framebuffer接口,即会在/dev中生成M个设备文件,如/dev/fb0、/dev/fb1、/dev/fb2等等。这M个设备的主设备号都是29,从设备则是0、1、2等等。
看完了framebuffer的注册,在来看看s3cfb.c中驱动框架的注册,驱动框架的注册也是设备驱动模型,注册的是platform总线:
static struct platform_driver s3cfb_driver = {
.probe = s3cfb_probe,
.remove = __devexit_p(s3cfb_remove),
.driver = {
.name = S3CFB_NAME,
.owner = THIS_MODULE,
},
};
static int __init s3cfb_register(void)
{
platform_driver_register(&s3cfb_driver);
return 0;
}
static void __exit s3cfb_unregister(void)
{
platform_driver_unregister(&s3cfb_driver);
}
module_init(s3cfb_register);
module_exit(s3cfb_unregister);
s3cfb_probe探测函数是重点,我们重点来看这个函数,s3cfb_probe主要完成以下工作:
static int __devinit s3cfb_probe(struct platform_device *pdev)
{
struct s3c_platform_fb *pdata; //s3c平台资源结构体
//lcd driver全局指针结构体指针,是分析驱动的核心指针
struct s3cfb_global *fbdev;
struct resource *res; //资源指针
int i, j, ret = 0;
fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL);
if (!fbdev) {
dev_err(fbdev->dev, "failed to allocate for "
"global fb structure\n");
ret = -ENOMEM;
goto err_global;
}
fbdev->dev = &pdev->dev;
fbdev->regulator = regulator_get(&pdev->dev, "pd"); //获取pm供电资源
if (IS_ERR(fbdev->regulator)) {
dev_err(fbdev->dev, "failed to get regulator\n");
ret = -EINVAL;
goto err_regulator;
}
ret = regulator_enable(fbdev->regulator); //使能供电
if (ret < 0) {
dev_err(fbdev->dev, "failed to enable regulator\n");
ret = -EINVAL;
goto err_regulator;
}
#ifdef RITESH
fbdev->vcc_lcd = regulator_get(&pdev->dev, "vcc_lcd");
if (IS_ERR(fbdev->vcc_lcd)) {
dev_err(fbdev->dev, "failed to get vcc_lcd\n");
ret = -EINVAL;
goto err_vcc_lcd;
}
ret = regulator_enable(fbdev->vcc_lcd);
if (ret < 0) {
dev_err(fbdev->dev, "failed to enable vcc_lcd\n");
ret = -EINVAL;
goto err_vcc_lcd;
}
fbdev->vlcd = regulator_get(&pdev->dev, "vlcd");
if (IS_ERR(fbdev->vlcd)) {
dev_err(fbdev->dev, "failed to get vlcd\n");
ret = -EINVAL;
goto err_vlcd;
}
ret = regulator_enable(fbdev->vlcd);
if (ret < 0) {
dev_err(fbdev->dev, "failed to enable vlcd\n");
ret = -EINVAL;
goto err_vlcd;
}
#endif
/* 获取devcie注册资源,这里得到s3c_fb_set_platdata函数填充的资源*/
pdata = to_fb_plat(&pdev->dev);
if (!pdata) {
dev_err(fbdev->dev, "failed to get platform data\n");
ret = -EINVAL;
goto err_pdata;
}
fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev); //初始化io
if (pdata->clk_on)
pdata->clk_on(pdev, &fbdev->clock);
//从平台设备中获取io资源,申请io资源,映射io资源
//即可得到一个IO内存资源节点指针, 包括了地址的开始,结束地址等
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(fbdev->dev, "failed to get io memory region\n");
ret = -EINVAL;
goto err_io;
}
res = request_mem_region(res->start,
res->end - res->start + 1, pdev->name);
if (!res) {
dev_err(fbdev->dev, "failed to request io memory region\n");
ret = -EINVAL;
goto err_io;
}
//映射出寄存器操作基地址
fbdev->regs = ioremap(res->start, res->end - res->start + 1);
if (!fbdev->regs) {
dev_err(fbdev->dev, "failed to remap io region\n");
ret = -EINVAL;
goto err_mem;
}
//初始化屏操作 硬件的初始化
s3cfb_set_vsync_interrupt(fbdev, 1);
s3cfb_set_global_interrupt(fbdev, 1);
s3cfb_init_global(fbdev);
//申请fb_info资源
/*这个函数的作用是申请struct fb_info 结构体,初始化fb_info 结构体的信息*/
/*fb_info 结构体 上与内核接口耦合的关系,
我们写lcd驱动就是除了初始化相关硬件以外*/
/*就是把fb_info 结构体初始化后 注册到内核,
供 fb_mem 调用,上层才能跟底层结合起来*/
if (s3cfb_alloc_framebuffer(fbdev)) {
ret = -ENOMEM;
goto err_alloc;
}
//注册framebuff
if (s3cfb_register_framebuffer(fbdev)) {
ret = -EINVAL;
goto err_register;
}
/* 设置时钟、选择windows通道,使能wins窗口*/
s3cfb_set_clock(fbdev);
s3cfb_set_window(fbdev, pdata->default_win, 1);
s3cfb_display_on(fbdev);
fbdev->irq = platform_get_irq(pdev, 0);//从平台驱动获取frame中断, 申请注册中断
if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED,
pdev->name, fbdev)) {
dev_err(fbdev->dev, "request_irq failed\n");
ret = -EINVAL;
goto err_irq;
}
#ifdef CONFIG_FB_S3C_LCD_INIT
if (pdata->backlight_on)
pdata->backlight_on(pdev); //使能打开背光
if (!bootloaderfb && pdata->reset_lcd)
pdata->reset_lcd(pdev);
#endif
#ifdef CONFIG_HAS_EARLYSUSPEND
fbdev->early_suspend.suspend = s3cfb_early_suspend;
fbdev->early_suspend.resume = s3cfb_late_resume;
fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
register_early_suspend(&fbdev->early_suspend);
#endif
/*在/sys/class/下创建一个属性文件*/
ret = device_create_file(&(pdev->dev), &dev_attr_win_power);
if (ret < 0)
dev_err(fbdev->dev, "failed to add sysfs entries\n");
dev_info(fbdev->dev, "registered successfully\n");
#if defined(CONFIG_LOGO)
if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) {
printk("Start display and show logo\n");
/* Start display and show logo on boot */
fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]);
fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);
}
#endif
return 0;
err_irq:
s3cfb_display_off(fbdev);
s3cfb_set_window(fbdev, pdata->default_win, 0);
for (i = pdata->default_win;
i < pdata->nr_wins + pdata->default_win; i++) {
j = i % pdata->nr_wins;
unregister_framebuffer(fbdev->fb[j]);
}
err_register:
for (i = 0; i < pdata->nr_wins; i++) {
if (i == pdata->default_win)
s3cfb_unmap_default_video_memory(fbdev->fb[i]);
framebuffer_release(fbdev->fb[i]);
}
kfree(fbdev->fb);
err_alloc:
iounmap(fbdev->regs);
err_mem:
release_mem_region(res->start, res->end - res->start + 1);
err_io:
pdata->clk_off(pdev, &fbdev->clock);
err_pdata:
#ifdef RITESH
if (!IS_ERR(fbdev->vlcd))
regulator_disable(fbdev->vlcd);
err_vlcd:
if (!IS_ERR(fbdev->vcc_lcd))
regulator_disable(fbdev->vcc_lcd);
err_vcc_lcd:
#endif
regulator_disable(fbdev->regulator);
err_regulator:
kfree(fbdev);
err_global:
return ret;
}
cfg_gpio是调用到linux-3.0.8\arch\arm\mach-s5pv210 \ mach-mini210.c 中的lcd_cfg_gpio函数:
static void lcd_cfg_gpio(struct platform_device *pdev)
{
int i;
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);
/* drive strength to 1x ....(max for smdkv210) */
writel(0x00000080, S5PV210_GPF0_BASE + 0xc);
writel(0x00000000, S5PV210_GPF1_BASE + 0xc);
writel(0x00000000, S5PV210_GPF2_BASE + 0xc);
writel(0x00000000, S5PV210_GPF3_BASE + 0xc);
}
该函数是将GFP0(0-7)、GPF1(0-7)、GPF2(0-7)、GPF3(0-3)等引脚设置成lcd模式(参考数据手册P151):
int s3cfb_set_vsync_interrupt(struct s3cfb_global *ctrl, int enable)
{
u32 cfg = 0;
cfg = readl(ctrl->regs + S3C_VIDINTCON0); //Specifies video interrupt control register
cfg &= ~S3C_VIDINTCON0_FRAMESEL0_MASK;
if (enable) {
dev_dbg(ctrl->dev, "vsync interrupt is on\n");
cfg |= S3C_VIDINTCON0_FRAMESEL0_VSYNC;
} else {
dev_dbg(ctrl->dev, "vsync interrupt is off\n");
cfg &= ~S3C_VIDINTCON0_FRAMESEL0_VSYNC;
}
writel(cfg, ctrl->regs + S3C_VIDINTCON0);
return 0;
}
int s3cfb_set_global_interrupt(struct s3cfb_global *ctrl, int enable)
{
u32 cfg = 0;
cfg = readl(ctrl->regs + S3C_VIDINTCON0);
cfg &= ~(S3C_VIDINTCON0_INTFRMEN_ENABLE | S3C_VIDINTCON0_INT_ENABLE);
if (enable) {
dev_dbg(ctrl->dev, "video interrupt is on\n");
cfg |= (S3C_VIDINTCON0_INTFRMEN_ENABLE |
S3C_VIDINTCON0_INT_ENABLE);
} else {
dev_dbg(ctrl->dev, "video interrupt is off\n");
cfg |= (S3C_VIDINTCON0_INTFRMEN_DISABLE |
S3C_VIDINTCON0_INT_DISABLE);
}
writel(cfg, ctrl->regs + S3C_VIDINTCON0);
return 0;
}
s3cfb_set_vsync_interrupt用来设置VSYNC中断,s3cfb_set_global_interrupt用来设置全局中断(P1260)。
static int s3cfb_init_global(struct s3cfb_global *ctrl)
{
ctrl->output = OUTPUT_RGB; //指定了输出格式
ctrl->rgb_mode = MODE_RGB_P; //指定了rgb模式
init_completion(&ctrl->fb_complete); //初始化信号量
mutex_init(&ctrl->lock);
s3cfb_set_output(ctrl);//设置输出格式
s3cfb_set_display_mode(ctrl);//设置模式
s3cfb_set_polarity(ctrl);//设置引脚极性
s3cfb_set_timing(ctrl);//设置时序
s3cfb_set_lcd_size(ctrl);//设置lcd大小
return 0;
}
s3cfb_init_global是对硬件的初始化,主要是进行一些寄存设置;
a、s3cfb_set_output(ctrl)设置输出格式
指定输出RGB数据类型,支持格式如下:
enum s3cfb_output_t {
OUTPUT_RGB,
OUTPUT_ITU,
OUTPUT_I80LDI0,
OUTPUT_I80LDI1,
OUTPUT_WB_RGB,
OUTPUT_WB_I80LDI0,
OUTPUT_WB_I80LDI1,
};
int s3cfb_set_output(struct s3cfb_global *ctrl)
{
u32 cfg;
cfg = readl(ctrl->regs + S3C_VIDCON0);
cfg &= ~S3C_VIDCON0_VIDOUT_MASK; //清空VIDCON0寄存器28-26位
if (ctrl->output == OUTPUT_RGB)
cfg |= S3C_VIDCON0_VIDOUT_RGB;
else if (ctrl->output == OUTPUT_ITU)
cfg |= S3C_VIDCON0_VIDOUT_ITU;
else if (ctrl->output == OUTPUT_I80LDI0)
cfg |= S3C_VIDCON0_VIDOUT_I80LDI0;
else if (ctrl->output == OUTPUT_I80LDI1)
cfg |= S3C_VIDCON0_VIDOUT_I80LDI1;
else if (ctrl->output == OUTPUT_WB_RGB)
cfg |= S3C_VIDCON0_VIDOUT_WB_RGB;
else if (ctrl->output == OUTPUT_WB_I80LDI0)
cfg |= S3C_VIDCON0_VIDOUT_WB_I80LDI0;
else if (ctrl->output == OUTPUT_WB_I80LDI1)
cfg |= S3C_VIDCON0_VIDOUT_WB_I80LDI1;
else {
dev_err(ctrl->dev, "invalid output type: %d\n", ctrl->output);
return -EINVAL;
}
writel(cfg, ctrl->regs + S3C_VIDCON0); //写入VIDCON0寄存器
cfg = readl(ctrl->regs + S3C_VIDCON2);
cfg &= ~(S3C_VIDCON2_WB_MASK | S3C_VIDCON2_TVFORMATSEL_MASK | \
S3C_VIDCON2_TVFORMATSEL_YUV_MASK); //清楚VIDCON2寄存器
if (ctrl->output == OUTPUT_RGB)
cfg |= S3C_VIDCON2_WB_DISABLE;
else if (ctrl->output == OUTPUT_ITU)
cfg |= S3C_VIDCON2_WB_DISABLE;
else if (ctrl->output == OUTPUT_I80LDI0)
cfg |= S3C_VIDCON2_WB_DISABLE;
else if (ctrl->output == OUTPUT_I80LDI1)
cfg |= S3C_VIDCON2_WB_DISABLE;
else if (ctrl->output == OUTPUT_WB_RGB)
cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
S3C_VIDCON2_TVFORMATSEL_YUV444);
else if (ctrl->output == OUTPUT_WB_I80LDI0)
cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
S3C_VIDCON2_TVFORMATSEL_YUV444);
else if (ctrl->output == OUTPUT_WB_I80LDI1)
cfg |= (S3C_VIDCON2_WB_ENABLE | S3C_VIDCON2_TVFORMATSEL_SW | \
S3C_VIDCON2_TVFORMATSEL_YUV444);
else {
dev_err(ctrl->dev, "invalid output type: %d\n", ctrl->output);
return -EINVAL;
}
writel(cfg, ctrl->regs + S3C_VIDCON2); //写入VIDCON2寄存器
return 0;
}
b、s3cfb_set_display_mode(ctrl)设置输出数据模式
RGB模式:
enum s3cfb_rgb_mode_t {
MODE_RGB_P = 0,
MODE_BGR_P = 1,
MODE_RGB_S = 2,
MODE_BGR_S = 3,
};
int s3cfb_set_display_mode(struct s3cfb_global *ctrl)
{
u32 cfg;
cfg = readl(ctrl->regs + S3C_VIDCON0);
cfg &= ~S3C_VIDCON0_PNRMODE_MASK; //清空18 、17为
cfg |= (ctrl->rgb_mode << S3C_VIDCON0_PNRMODE_SHIFT);
writel(cfg, ctrl->regs + S3C_VIDCON0); //写入数据
return 0;
}
c、s3cfb_set_polarity(fbdev)设置引脚极性
s3cfb_set_polarity函数主要是对vden(使能信号)、vsynsc(垂直同步信号)、hsync(行同步信号)、vclk (时钟clk)进行使能设置。
static struct s3cfb_lcd wvga_s70 = {
………
.polarity = {
.rise_vclk = 0,
.inv_hsync = 1,
.inv_vsync = 1,
.inv_vden = 0,
},
};
int s3cfb_set_polarity(struct s3cfb_global *ctrl)
{
struct s3cfb_lcd_polarity *pol;
u32 cfg;
pol = &ctrl->lcd->polarity;
cfg = (ctrl->lcd->args & 0xf0);
if (pol->rise_vclk)
cfg |= S3C_VIDCON1_IVCLK_RISING_EDGE;
if (pol->inv_hsync)
cfg |= S3C_VIDCON1_IHSYNC_INVERT;
if (pol->inv_vsync)
cfg |= S3C_VIDCON1_IVSYNC_INVERT;
if (pol->inv_vden)
cfg |= S3C_VIDCON1_IVDEN_INVERT;
writel(cfg, ctrl->regs + S3C_VIDCON1);
return 0;
}
vden、vsynsc、hsync、vclk参数的设置是通过主控datashee和LCD datasheet来确定的。下图为Tiny210主控时序(P1207):
从上图可以看出,Tiny210 vden高电平,vsynsc、hsync都是高脉冲触发,vclk触发方式待指定。
LCD时序图如下所示:
而LCD时序可以看到,vden高电平,vsynsc、hsync都是低脉冲触发,vclk触发方式待指定。
所以,比较两边时序,vden、vclk可以不需要调整,vsynsc、hsync需要反转,所以参数如下:
.polarity = {
.rise_vclk = 0,
.inv_hsync = 1,
.inv_vsync = 1,
.inv_vden = 0,
},
d、s3cfb_set_timing(ctrl)设置时序
int s3cfb_set_lcd_size(struct s3cfb_global *ctrl)
{
u32 cfg = 0;
#if defined(CONFIG_FB_S3C_101WA01S)
cfg |= S3C_VIDTCON2_HOZVAL(1366 - 1);
cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);
#elif defined(CONFIG_FB_S3C_TL2796)
cfg |= S3C_VIDTCON2_HOZVAL(1280 - 1);
cfg |= S3C_VIDTCON2_LINEVAL(800 - 1);
#else
cfg |= S3C_VIDTCON2_HOZVAL(ctrl->lcd->width - 1);
cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);
#endif
writel(cfg, ctrl->regs + S3C_VIDTCON2);
return 0;
}
这个函数主要供能是设置各种时序和脉冲宽度,我们先看寄存器:
而对应LCD的时序如下所示:
HSPW :HS pulse width 水平同步信号脉宽
HBPD :HS Blanking水平同步信号前肩
HFPD :HS Front Porch水平同步信号后肩
VSPW :VS pulse width垂直同步信号脉宽
VBPD :VS Blanking垂直同步信号前肩
VFPD :VS Front Potch垂直同步信号后肩
e、s3cfb_set_lcd_size设置lcd大小
int s3cfb_set_lcd_size(struct s3cfb_global *ctrl)
{
u32 cfg = 0;
#if defined(CONFIG_FB_S3C_101WA01S)
cfg |= S3C_VIDTCON2_HOZVAL(1366 - 1);
cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);
#elif defined(CONFIG_FB_S3C_TL2796)
cfg |= S3C_VIDTCON2_HOZVAL(1280 - 1);
cfg |= S3C_VIDTCON2_LINEVAL(800 - 1);
#else
cfg |= S3C_VIDTCON2_HOZVAL(ctrl->lcd->width - 1);
cfg |= S3C_VIDTCON2_LINEVAL(ctrl->lcd->height - 1);
#endif
writel(cfg, ctrl->regs + S3C_VIDTCON2);
return 0;
}
这里是设置分辨率大小,将LCD分辨率写入相对应的寄存器。
所以,我们看到,s3cfb_init_global函数主要完成的设置为:
output—–指定输出格式为RGB
display —-指定输出的rgb格式为normal ,且是并行输出
polartiy —-设置触发极性
timing —– 设置时间参数
lcd_size —–设置大小参数
f、s3cfb_alloc_framebuffer和s3cfb_register_framebuffer
上面我们已经介绍了fb_info、 fb_fix_screeninfo、fb_var_screeninfo结构体,s3cfb_alloc_framebuffer函数的作用是申请fb_info 结构体,初始化fb_info 结构体的信息。
static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
int ret, i;
// 根据wins 个数开辟fb_info 空间的指针,这个在platform device中指定了
ctrl->fb = kmalloc(pdata->nr_wins *
sizeof(*(ctrl->fb)), GFP_KERNEL);
if (!ctrl->fb) {
dev_err(ctrl->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 */
ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb), ctrl->dev);
if (!ctrl->fb[i]) {
dev_err(ctrl->dev, "not enough memory\n");
ret = -ENOMEM;
goto err_alloc_fb;
}
/*主要是初始化fb_info 结构体,对var ,fix进行填充等....*/
s3cfb_init_fbinfo(ctrl, i);
if (i == pdata->default_win) {
/*主要是为窗体分配存放RGB数据的空间。(该分配一般用DMA)*/
if (s3cfb_map_video_memory(ctrl->fb[i])) {
dev_err(ctrl->dev,
"failed to map video memory "
"for default window (%d)\n", i);
ret = -ENOMEM;
goto err_map_video_mem;
}
}
}
return 0;
err_alloc_fb:
while (--i >= 0) {
if (i == pdata->default_win)
s3cfb_unmap_default_video_memory(ctrl->fb[i]);
err_map_video_mem:
framebuffer_release(ctrl->fb[i]);
}
kfree(ctrl->fb);
err_alloc:
return ret;
}
s3cfb_init_fbinfo函数主要初始化fbinfo结构体,并填充相关内容,参数如下:
static void s3cfb_init_fbinfo(struct s3cfb_global *ctrl, int id)
{
struct fb_info *fb = ctrl->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 = ctrl->lcd;
struct s3cfb_lcd_timing *timing = &lcd->timing;
memset(win, 0, sizeof(*win));
platform_set_drvdata(to_platform_device(ctrl->dev), ctrl);
strcpy(fix->id, S3CFB_NAME);
/* 指定窗体id,窗体数据路径,dma burst ,win的win->power_state 状态,设置透明度方式*/
win->id = id;
/*选择数据来源DMA*/
win->path = DATA_PATH_DMA;
/*设置dma_burst ,大小范围可以根据数据数据手册决定*/
win->dma_burst = 16;
alpha->mode = PLANE_BLENDING;
//设置fb_open、fb_read等接口,s3cfb_ops是个全局接口
fb->fbops = &s3cfb_ops;
fb->flags = FBINFO_FLAG_DEFAULT; // 然后是设置FBINFO
fb->pseudo_palette = &win->pseudo_pal; //设置虚拟的调色板地址
#if (CONFIG_FB_S3C_NR_BUFFERS != 1)
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分辨率
var->xres = lcd->width;
var->yres = lcd->height;
/*设置虚拟分辨率,嵌入式设备一般不该分辨率*/
#if defined(CONFIG_FB_S3C_VIRTUAL)
var->xres_virtual = CONFIG_FB_S3C_X_VRES;
var->yres_virtual = CONFIG_FB_S3C_Y_VRES * CONFIG_FB_S3C_NR_BUFFERS;
#else
var->xres_virtual = var->xres;
var->yres_virtual = var->yres * CONFIG_FB_S3C_NR_BUFFERS;
#endif
/* 设置成 32 bpp 的分辨率*/
var->bits_per_pixel = 32;
/*设置xoffset ,yoffset 偏移 都为0*/
/*不为0 的话:var->xoffset = var->xres_virtual - var->xres - 1*/
/*不为0的话:var->yoffset = var->yres_virtual - var->yres - 1;*/
var->xoffset = 0;
var->yoffset = 0;
var->width = lcd->p_width;
var->height = lcd->p_height;
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_fp;
var->right_margin = timing->h_bp;
var->upper_margin = timing->v_fp;
var->lower_margin = timing->v_bp;
//根据相应参数计算pixclock的值,
ctrl->pixclock_hz = lcd->freq * (var->left_margin + var->right_margin +
#if defined(CONFIG_FB_S3C_101WA01S)
var->hsync_len + 1366) *
(var->upper_margin + var->lower_margin +
var->vsync_len + var->yres);
#elif defined(CONFIG_FB_S3C_TL2796)
var->hsync_len + 1280) *
(var->upper_margin + var->lower_margin +
var->vsync_len + 800);
#else
var->hsync_len + var->xres) *
(var->upper_margin + var->lower_margin +
var->vsync_len + var->yres);
#endif
#ifdef CONFIG_FB_S3C_LTE480WV
ctrl->pixclock_hz = 2 * ctrl->pixclock_hz;
#endif
var->pixclock = KHZ2PICOS(ctrl->pixclock_hz / 1000);
dev_dbg(ctrl->dev, "pixclock: %d\n", var->pixclock);
//设置fb的R/G/B位域
s3cfb_set_bitfield(var);
/*
设置透明度 模式
设置channel---0 通道
设置value -------最大值不透明
*/
s3cfb_set_alpha_info(var, win);
}
调用s3cfb_alloc_framebuffer申请fb_info 结构体后,然后就是用s3cfb_register_framebuffer向内核注册lcd设备。
static int s3cfb_register_framebuffer(struct s3cfb_global *ctrl)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
int ret, i, j;
for (i = pdata->default_win;
i < pdata->nr_wins + pdata->default_win; i++) {
j = i % pdata->nr_wins;
ret = register_framebuffer(ctrl->fb[j]); //像内核注册lcd设置,注册成功会有/dev/fbx节点
if (ret) {
dev_err(ctrl->dev, "failed to register "
"framebuffer device\n");
ret = -EINVAL;
goto err_register_fb;
}
#ifndef CONFIG_FRAMEBUFFER_CONSOLE
if (j == pdata->default_win) {
// 检查参数
s3cfb_check_var(&ctrl->fb[j]->var, ctrl->fb[j]);
//设置参数
s3cfb_set_par(ctrl->fb[j]);
//显示log
s3cfb_draw_logo(ctrl->fb[j]);
}
#endif
}
return 0;
err_register_fb:
while (--i >= pdata->default_win) {
j = i % pdata->nr_wins;
unregister_framebuffer(ctrl->fb[j]);
}
return ret;
}
g、s3cfb_set_clock、s3cfb_set_window、s3cfb_display_on设置时钟、选择windows通道,使能wins窗口
/*
该函数的主要作用是获得时钟,计算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;
maxclk = 86 * 1000000;
/* fixed clock source: hclk */
cfg = readl(ctrl->regs + S3C_VIDCON0);
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_INFO "FIMD src sclk = %d\n", src_clk);
} else {
cfg |= S3C_VIDCON0_CLKSEL_HCLK;
src_clk = ctrl->clock->parent->rate;
printk(KERN_INFO "FIMD src hclk = %d\n", src_clk);
}
vclk = ctrl->pixclock_hz;
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)
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(div - 1);
writel(cfg, ctrl->regs + S3C_VIDCON0);
dev_dbg(ctrl->dev, "parent clock: %d, vclk: %d, vclk div: %d\n",
src_clk, vclk, div);
return 0;
}
s3cfb_set_window选择windows通道:
static void s3cfb_set_window(struct s3cfb_global *ctrl, int id, int enable)
{
struct s3cfb_window *win = ctrl->fb[id]->par;
if (enable) {
s3cfb_window_on(ctrl, id); //打开窗口
win->enabled = 1;
} else {
s3cfb_window_off(ctrl, id); // 关闭窗口
win->enabled = 0;
}
}
int s3cfb_window_on(struct s3cfb_global *ctrl, int id)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
u32 cfg;
cfg = readl(ctrl->regs + S3C_WINCON(id));
cfg |= S3C_WINCON_ENWIN_ENABLE;
writel(cfg, ctrl->regs + S3C_WINCON(id));
if (pdata->hw_ver == 0x62) {
cfg = readl(ctrl->regs + S3C_WINSHMAP);
cfg |= S3C_WINSHMAP_CH_ENABLE(id);
writel(cfg, ctrl->regs + S3C_WINSHMAP);
}
dev_dbg(ctrl->dev, "[fb%d] turn on\n", id);
return 0;
}
int s3cfb_window_off(struct s3cfb_global *ctrl, int id)
{
struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev);
u32 cfg;
cfg = readl(ctrl->regs + S3C_WINCON(id));
cfg &= ~(S3C_WINCON_ENWIN_ENABLE | S3C_WINCON_DATAPATH_MASK);
cfg |= S3C_WINCON_DATAPATH_DMA;
writel(cfg, ctrl->regs + S3C_WINCON(id));
if (pdata->hw_ver == 0x62) {
cfg = readl(ctrl->regs + S3C_WINSHMAP);
cfg &= ~S3C_WINSHMAP_CH_DISABLE(id);
writel(cfg, ctrl->regs + S3C_WINSHMAP);
}
dev_dbg(ctrl->dev, "[fb%d] turn off\n", id);
return 0;
}
而s3cfb_display_on真正使能lcd:
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;
}
至此,LCD框架的probe函数基本结束,还有像注册irq中断,在/sys/class/下创建一个属性文件之类的跟一般驱动中的类似,这里不着重介绍了。