/* * linux/drivers/h1940fb.c * Copyright (c) Arnaud Patard * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for * more details. * * iPAQ h1940/S3C2410 LCD Controller Frame Buffer Driver * based on skeletonfb.c, sa1100fb.c * * ChangeLog * 2004-07-27: Arnaud Patard * - code cleanup * - added a forgotten return in h1940fb_init * * 2004-07-19: Herbert Pötzl * - code cleanup and extended debugging * * 2004-07-15: Arnaud Patard * - First version */ #include #include #include #include #include #include #include #include #include #include #include #include #include "h1940fb.h" #ifdef CONFIG_FB_H1940_FIXED #define H1940_VA_FBMEM 0xE0000000UL #define H1940_PA_FBMEM 0x33F00000UL #define H1940_SZ_FBMEM 0x00100000UL #endif extern void *dma_alloc_writecombine(struct device *dev, size_t size, dma_addr_t *handle, int gfp); static struct h1940fb_info info; #if 1 #define dprintk(msg...) printk(KERN_INFO "h1940fb: " msg) #else #define dprintk(msg...) while (0) { } #endif /* * h1940fb_check_var(): * Get the video params out of 'var'. If a value doesn't fit, round it up, * if it's too big, return -EINVAL. * * Round up in the following order: bits_per_pixel, xres, * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale, * bitfields, horizontal timing, vertical timing. */ static int h1940fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { dprintk("check_var(var=%p, info=%p)\n", var, info); var->red.offset=11; var->green.offset=5; var->blue.offset=0; var->red.length=5; var->green.length=6; var->blue.length=5; return 0; } /** * h1940fb_set_par - Optional function. Alters the hardware state. * @info: frame buffer structure that represents a single frame buffer * */ static int h1940fb_set_par(struct fb_info *info) { struct h1940fb_info *fbi = (struct h1940fb_info *)info; dprintk("set_par(info=%p)\n", info); /* We support only 16BPP true color */ fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR; fbi->fb.fix.line_length = 240*16/8; return 0; } static int h1940fb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, unsigned transp, struct fb_info *info) { struct h1940fb_info *fbi = (struct h1940fb_info *)info; int bpp, m = 0; /* dprintk("setcolreg(reg=%d, RGBA=%d,%d,%d,%d info=%p)\n", regno, red, green, blue, transp, info); */ bpp = fbi->fb.var.bits_per_pixel; m = 1 << bpp; if (regno >= m) { return -EINVAL; } switch (bpp) { case 16: /* RGB 565 */ fbi->pseudo_pal[regno] = ((red & 0xF800) | ((green & 0xFC00) >> 5) | ((blue & 0xF800) >> 11)); break; } return 0; } /** * h1940fb_pan_display - NOT a required function. Pans the display. * @var: frame buffer variable screen structure * @info: frame buffer structure that represents a single frame buffer * * Pan (or wrap, depending on the `vmode' field) the display using the * `xoffset' and `yoffset' fields of the `var' structure. * If the values don't fit, return -EINVAL. * * Returns negative errno on error, or zero on success. */ static int h1940fb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { dprintk("pan_display(var=%p, info=%p)\n", var, info); return 0; } /** * xxxfb_blank - NOT a required function. Blanks the display. * @blank_mode: the blank mode we want. * @info: frame buffer structure that represents a single frame buffer * * Blank the screen if blank_mode != 0, else unblank. Return 0 if * blanking succeeded, != 0 if un-/blanking failed due to e.g. a * video mode which doesn't support it. Implements VESA suspend * and powerdown modes on hardware that supports disabling hsync/vsync: * blank_mode == 2: suspend vsync * blank_mode == 3: suspend hsync * blank_mode == 4: powerdown * * Returns negative errno on error, or zero on success. * */ static int h1940fb_blank(int blank_mode, struct fb_info *info) { dprintk("blank(mode=%d, info=%p)\n", blank_mode, info); return 0; } static struct fb_ops h1940fb_ops = { .owner = THIS_MODULE, .fb_check_var = h1940fb_check_var, .fb_set_par = h1940fb_set_par, .fb_blank = h1940fb_blank, .fb_pan_display = h1940fb_pan_display, .fb_setcolreg = h1940fb_setcolreg, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_cursor = soft_cursor, }; /* Fake monspecs to fill in fbinfo structure */ /* Don't know if the values are important */ static struct fb_monspecs monspecs __initdata = { .hfmin = 30000, .hfmax = 70000, .vfmin = 50, .vfmax = 65, }; /* * h1940fb_map_video_memory(): * Allocates the DRAM memory for the frame buffer. This buffer is * remapped into a non-cached, non-buffered, memory region to * allow palette and pixel writes to occur without flushing the * cache. Once this area is remapped, all virtual memory * access to the video memory should occur at the new region. */ static int __init h1940fb_map_video_memory(struct h1940fb_info *fbi) { dprintk("map_video_memory(fbi=%p)\n", fbi); #ifdef CONFIG_FB_H1940_FIXED fbi->map_cpu = (void *)H1940_VA_FBMEM; fbi->map_dma = H1940_PA_FBMEM; #else fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE); fbi->map_cpu = dma_alloc_writecombine(fbi->dev, fbi->map_size, &fbi->map_dma, GFP_KERNEL); #endif fbi->map_size = fbi->fb.fix.smem_len; if (fbi->map_cpu) { /* prevent initial garbage on screen */ dprintk("map_video_memory: clear %p:%08x\n", fbi->map_cpu, fbi->map_size); memset(fbi->map_cpu, 0xf0, fbi->map_size); #ifdef CONFIG_FB_H1940_FIXED fbi->screen_dma = fbi->map_dma; fbi->fb.screen_base = fbi->map_cpu; #else fbi->fb.screen_base = fbi->map_cpu + PAGE_SIZE; fbi->screen_dma = fbi->map_dma + PAGE_SIZE; #endif fbi->fb.fix.smem_start = fbi->screen_dma; dprintk("map_video_memory: dma=%08x cpu=%p size=%08x\n", fbi->map_dma, fbi->map_cpu, fbi->fb.fix.smem_len); } return fbi->map_cpu ? 0 : -ENOMEM; } int __devinit h1940fb_init_registers(struct h1940fb_info *fbi) { unsigned long saddr1, saddr2, saddr3; /* Initialise LCD with values from haret */ /* __raw_writel( S3C2410_LCDCON1_TFT16BPP | \ S3C2410_LCDCON1_TFT | \ S3C2410_LCDCON1_CLKVAL(0x0C), \ S3C2410_LCDCON1); __raw_writel( S3C2410_LCDCON2_VBPD(7) | \ S3C2410_LCDCON2_LINEVAL(319) | \ S3C2410_LCDCON2_VFPD(6) | \ S3C2410_LCDCON2_VSPW(0), \ S3C2410_LCDCON2); __raw_writel( S3C2410_LCDCON3_HBPD(38) | \ S3C2410_LCDCON3_HOZVAL(239) | \ S3C2410_LCDCON3_HFPD(7), \ S3C2410_LCDCON3); __raw_writel( S3C2410_LCDCON4_MVAL(0) | \ S3C2410_LCDCON4_HSPW(3), \ S3C2410_LCDCON4); __raw_writel( S3C2410_LCDCON5_FRM565 | \ S3C2410_LCDCON5_INVVLINE | \ S3C2410_LCDCON5_HWSWP, \ S3C2410_LCDCON5);*/ __raw_writel(0x035c0c78, S3C2410_LCDCON1); __raw_writel(0x074fc180, S3C2410_LCDCON2); __raw_writel(0x0098ef07, S3C2410_LCDCON3); __raw_writel(0x00000003, S3C2410_LCDCON4); __raw_writel(0x00014a01, S3C2410_LCDCON5); saddr1 = S3C2410_LCDBANK(fbi->fb.fix.smem_start >> 22) | S3C2410_LCDBASEU(fbi->fb.fix.smem_start >> 1); saddr2 = (fbi->fb.fix.smem_start+(320*240*2)) >> 1; saddr3 = S3C2410_OFFSIZE(0) | S3C2410_PAGEWIDTH(240); dprintk("LCDSADDR1 = 0x%08lx\n", saddr1); __raw_writel(saddr1, S3C2410_LCDSADDR1); dprintk("LCDSADDR2 = 0x%08lx\n", saddr2); __raw_writel(saddr2, S3C2410_LCDSADDR2); dprintk("LCDSADDR3 = 0x%08lx\n", saddr3); __raw_writel(saddr3, S3C2410_LCDSADDR3); __raw_writel(0x00000002, S3C2410_LPCSEL); /* Enable video by setting the ENVID bit to 1 */ /* __raw_writel( S3C2410_LCDCON1_ENVID | \ S3C2410_LCDCON1_TFT16BPP | \ S3C2410_LCDCON1_TFT | \ S3C2410_LCDCON1_CLKVAL(0x0C), \ S3C2410_LCDCON1);*/ __raw_writel(0x035c0c79, S3C2410_LCDCON1); return 0; } static struct clk *lcd_clock; int __devinit h1940fb_init(void) { char driver_name[]="h1940fb"; int ret; dprintk("devinit %s ...\n", driver_name); #ifdef CONFIG_FB_H1940_FIXED if (!request_mem_region(H1940_VA_FBMEM, H1940_SZ_FBMEM, "Video Memory")) return -EBUSY; #endif strcpy(info.fb.fix.id, driver_name); info.fb.fix.type = FB_TYPE_PACKED_PIXELS; info.fb.fix.type_aux = 0; info.fb.fix.xpanstep = 0; info.fb.fix.ypanstep = 0; info.fb.fix.ywrapstep = 0; info.fb.fix.accel = FB_ACCEL_NONE; info.fb.var.nonstd = 0; info.fb.var.activate = FB_ACTIVATE_NOW; info.fb.var.height = 320; info.fb.var.width = 240; info.fb.var.accel_flags = 0; info.fb.var.vmode = FB_VMODE_NONINTERLACED; info.fb.fbops = &h1940fb_ops; info.fb.flags = FBINFO_FLAG_DEFAULT; info.fb.monspecs = monspecs; info.fb.currcon = -1; info.fb.pseudo_palette = &info.pseudo_pal; info.fb.var.xres = 240; info.fb.var.xres_virtual = 240; info.fb.var.yres = 320; info.fb.var.yres_virtual = 320; info.fb.var.bits_per_pixel = 16; info.fb.var.red.offset = 11; info.fb.var.green.offset = 5; info.fb.var.blue.offset = 0; info.fb.var.transp.offset = 0; info.fb.var.red.length = 5; info.fb.var.green.length = 6; info.fb.var.blue.length = 5; info.fb.var.transp.length = 0; info.fb.fix.smem_len = info.fb.var.xres * info.fb.var.yres * info.fb.var.bits_per_pixel / 8; if (!request_mem_region(S3C2410_VA_LCD, SZ_1M, "LCD")) return -EBUSY; dprintk("got LCD region\n"); /* Initialize video memory */ ret = h1940fb_map_video_memory(&info); if (ret) { printk("Failed to allocate video RAM: %d\n", ret); ret = -ENOMEM; goto failed; } dprintk("got video memory\n"); ret = h1940fb_init_registers(&info); ret = h1940fb_check_var(&info.fb.var, &info.fb); ret = register_framebuffer(&info.fb); if (ret < 0) { printk("Failed to register framebuffer device: %d\n", ret); goto failed; } lcd_clock = clk_get(NULL, "LCD"); if (!lcd_clock) { printk(KERN_INFO "failed to get lcd clock source\n"); return -ENOENT; } clk_use(lcd_clock); clk_enable(lcd_clock); printk(KERN_INFO "fb%d: %s frame buffer device\n", info.fb.node, info.fb.fix.id); return 0; failed: release_mem_region(S3C2410_VA_LCD, SZ_1M); #ifdef CONFIG_FB_H1940_FIXED release_mem_region(0H1940_VA_FBMEM, H1940_SZ_FBMEM); #endif return ret; } /* * Cleanup */ static void __exit h1940fb_cleanup(void) { if (lcd_clock) { clk_disable(lcd_clock); clk_unuse(lcd_clock); clk_put(lcd_clock); lcd_clock = NULL; } unregister_framebuffer(&info.fb); release_mem_region(S3C2410_VA_LCD, SZ_1M); #ifdef CONFIG_FB_H1940_FIXED release_mem_region(0H1940_VA_FBMEM, H1940_SZ_FBMEM); #endif } #ifndef MODULE int __devinit h1940fb_setup(char *options) { dprintk("setup(%s)\n", options); return 0; } #else module_init(h1940fb_init); module_exit(h1940fb_cleanup); #endif MODULE_AUTHOR("Arnaud Patard "); MODULE_DESCRIPTION("Framebuffer driver for the iPAQ h1940"); MODULE_LICENSE("GPL");