From: Ryan M. <ry...@bl...> - 2009-07-17 04:12:42
|
EP93xx framebuffer driver Signed-off-by: Ryan Mallon <ry...@bl...> --- diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 55022f3..bda367f 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2114,6 +2114,17 @@ config FB_MB862XX_LIME ---help--- Framebuffer support for Fujitsu Lime GDC on host CPU bus. +config FB_EP93XX + tristate "EP93XX frame buffer support" + depends on FB && ARCH_EP93XX + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Framebuffer driver for the Cirrus Logic EP93XX series of processors. + This driver is also available as a module. The module will be called + ep93xxfb. + config FB_PRE_INIT_FB bool "Don't reinitialize, use bootloader's GDC/Display configuration" depends on FB_MB862XX_LIME diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 01a819f..6d6fdf3 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -126,6 +126,7 @@ obj-$(CONFIG_FB_OMAP) += omap/ obj-$(CONFIG_XEN_FBDEV_FRONTEND) += xen-fbfront.o obj-$(CONFIG_FB_CARMINE) += carminefb.o obj-$(CONFIG_FB_MB862XX) += mb862xx/ +obj-$(CONFIG_FB_EP93XX) += ep93xx_fb.o # Platform or fallback drivers go here obj-$(CONFIG_FB_UVESA) += uvesafb.o diff --git a/drivers/video/ep93xx_fb.c b/drivers/video/ep93xx_fb.c new file mode 100644 index 0000000..129ba4b --- /dev/null +++ b/drivers/video/ep93xx_fb.c @@ -0,0 +1,646 @@ +/* + * linux/drivers/video/ep93xx_fb.c + * + * Framebuffer support for the EP93xx series. + * + * Copyright (C) 2007 Bluewater Systems + * Author: Ryan Mallon <ry...@bl...> + * + * Copyright (c) 2009 H Hartley Sweeten <hsw...@vi...> + * + * Based on the Cirrus Logic ep93xxfb driver, and various other ep93xxfb + * drivers. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/fb.h> + +#include <mach/fb.h> + +/* Vertical Frame Timing Registers */ +#define EP93XXFB_VLINES_TOTAL 0x0000 /* SW locked */ +#define EP93XXFB_VSYNC 0x0004 /* SW locked */ +#define EP93XXFB_VACTIVE 0x0008 /* SW locked */ +#define EP93XXFB_VBLANK 0x0228 /* SW locked */ +#define EP93XXFB_VCLK 0x000c /* SW locked */ + +/* Horizontal Frame Timing Registers */ +#define EP93XXFB_HCLKS_TOTAL 0x0010 /* SW locked */ +#define EP93XXFB_HSYNC 0x0014 /* SW locked */ +#define EP93XXFB_HACTIVE 0x0018 /* SW locked */ +#define EP93XXFB_HBLANK 0x022c /* SW locked */ +#define EP93XXFB_HCLK 0x001c /* SW locked */ + +/* Frame Buffer Memory Configuration Registers */ +#define EP93XXFB_SCREEN_PAGE 0x0028 +#define EP93XXFB_SCREEN_HPAGE 0x002c +#define EP93XXFB_SCREEN_LINES 0x0030 +#define EP93XXFB_LINE_LENGTH 0x0034 +#define EP93XXFB_VLINE_STEP 0x0038 +#define EP93XXFB_LINE_CARRY 0x003c /* SW locked */ +#define EP93XXFB_EOL_OFFSET 0x0230 + +/* Other Video Registers */ +#define EP93XXFB_BRIGHTNESS 0x0020 +#define EP93XXFB_ATTRIBS 0x0024 /* SW locked */ +#define EP93XXFB_SWLOCK 0x007c /* SW locked */ +#define EP93XXFB_AC_RATE 0x0214 +#define EP93XXFB_FIFO_LEVEL 0x0234 +#define EP93XXFB_PIXELMODE 0x0054 +#define EP93XXFB_PIXELMODE_32BPP (0x7 << 0) +#define EP93XXFB_PIXELMODE_24BPP (0x6 << 0) +#define EP93XXFB_PIXELMODE_16BPP (0x4 << 0) +#define EP93XXFB_PIXELMODE_8BPP (0x2 << 0) +#define EP93XXFB_PIXELMODE_SHIFT_1P_24B (0x0 << 3) +#define EP93XXFB_PIXELMODE_SHIFT_1P_18B (0x1 << 3) +#define EP93XXFB_PIXELMODE_COLOR_LUT (0x0 << 10) +#define EP93XXFB_PIXELMODE_COLOR_888 (0x4 << 10) +#define EP93XXFB_PIXELMODE_COLOR_555 (0x5 << 10) +#define EP93XXFB_PARL_IF_OUT 0x0058 +#define EP93XXFB_PARL_IF_IN 0x005c + +/* Blink Control Registers */ +#define EP93XXFB_BLINK_RATE 0x0040 +#define EP93XXFB_BLINK_MASK 0x0044 +#define EP93XXFB_BLINK_PATTRN 0x0048 +#define EP93XXFB_PATTRN_MASK 0x004c +#define EP93XXFB_BKGRND_OFFSET 0x0050 + +/* Hardware Cursor Registers */ +#define EP93XXFB_CURSOR_ADR_START 0x0060 +#define EP93XXFB_CURSOR_ADR_RESET 0x0064 +#define EP93XXFB_CURSOR_SIZE 0x0068 +#define EP93XXFB_CURSOR_COLOR1 0x006c +#define EP93XXFB_CURSOR_COLOR2 0x0070 +#define EP93XXFB_CURSOR_BLINK_COLOR1 0x021c +#define EP93XXFB_CURSOR_BLINK_COLOR2 0x0220 +#define EP93XXFB_CURSOR_XY_LOC 0x0074 +#define EP93XXFB_CURSOR_DSCAN_HY_LOC 0x0078 +#define EP93XXFB_CURSOR_BLINK_RATE_CTRL 0x0224 + +/* LUT Registers */ +#define EP93XXFB_GRY_SCL_LUTR 0x0080 +#define EP93XXFB_GRY_SCL_LUTG 0x0280 +#define EP93XXFB_GRY_SCL_LUTB 0x0300 +#define EP93XXFB_LUT_SW_CONTROL 0x0218 +#define EP93XXFB_LUT_SW_CONTROL_SWTCH (1 << 0) +#define EP93XXFB_LUT_SW_CONTROL_SSTAT (1 << 1) +#define EP93XXFB_COLOR_LUT 0x0400 + +/* Video Signature Registers */ +#define EP93XXFB_VID_SIG_RSLT_VAL 0x0200 +#define EP93XXFB_VID_SIG_CTRL 0x0204 +#define EP93XXFB_VSIG 0x0208 +#define EP93XXFB_HSIG 0x020c +#define EP93XXFB_SIG_CLR_STR 0x0210 + +/* Minimum / Maximum resolutions supported */ +#define EP93XXFB_MIN_XRES 64 +#define EP93XXFB_MIN_YRES 64 +#define EP93XXFB_MAX_XRES 1024 +#define EP93XXFB_MAX_YRES 768 + +struct ep93xx_fbi { + struct ep93xxfb_mach_info *mach_info; + struct clk *clk; + struct resource *res; + void __iomem *mmio_base; + unsigned int pseudo_palette[256]; +}; + +static int check_screenpage_bug = 1; +module_param(check_screenpage_bug, int, 0644); +MODULE_PARM_DESC(check_screenpage_bug, + "Check for bit 27 screen page bug. Default = 1"); + +static inline unsigned int ep93xxfb_readl(struct ep93xx_fbi *fbi, + unsigned int off) +{ + return __raw_readl(fbi->mmio_base + off); +} + +static inline void ep93xxfb_writel(struct ep93xx_fbi *fbi, unsigned int val, + unsigned int off) +{ + __raw_writel(val, fbi->mmio_base + off); +} + +/* + * Write to one of the locked raster registers. + */ +static inline void ep93xxfb_out_locked(struct ep93xx_fbi *fbi, + unsigned int val, unsigned int reg) +{ + /* + * We don't need a lock or delay here since the raster register + * block will remain unlocked until the next access. + */ + ep93xxfb_writel(fbi, 0xaa, EP93XXFB_SWLOCK); + ep93xxfb_writel(fbi, val, reg); +} + +static void ep93xxfb_set_video_attribs(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int attribs; + + attribs = EP93XXFB_ENABLE; + attribs |= fbi->mach_info->flags; + ep93xxfb_out_locked(fbi, attribs, EP93XXFB_ATTRIBS); +} + +static int ep93xxfb_set_pixelmode(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int val; + + info->var.transp.offset = 0; + info->var.transp.length = 0; + + switch (info->var.bits_per_pixel) { + case 8: + val = EP93XXFB_PIXELMODE_8BPP | EP93XXFB_PIXELMODE_COLOR_LUT | + EP93XXFB_PIXELMODE_SHIFT_1P_18B; + + info->var.red.offset = 0; + info->var.red.length = 8; + info->var.green.offset = 0; + info->var.green.length = 8; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + break; + + case 16: + val = EP93XXFB_PIXELMODE_16BPP | EP93XXFB_PIXELMODE_COLOR_555 | + EP93XXFB_PIXELMODE_SHIFT_1P_18B; + + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + case 24: + val = EP93XXFB_PIXELMODE_24BPP | EP93XXFB_PIXELMODE_COLOR_888 | + EP93XXFB_PIXELMODE_SHIFT_1P_24B; + + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + case 32: + val = EP93XXFB_PIXELMODE_32BPP | EP93XXFB_PIXELMODE_COLOR_888 | + EP93XXFB_PIXELMODE_SHIFT_1P_24B; + + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->fix.visual = FB_VISUAL_TRUECOLOR; + break; + + default: + return -EINVAL; + } + + ep93xxfb_writel(fbi, val, EP93XXFB_PIXELMODE); + return 0; +} + +static void ep93xxfb_set_timing(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int vlines_total, hclks_total, start, stop; + + vlines_total = info->var.yres + info->var.upper_margin + + info->var.lower_margin + info->var.vsync_len - 1; + + hclks_total = info->var.xres + info->var.left_margin + + info->var.right_margin + info->var.hsync_len - 1; + + ep93xxfb_out_locked(fbi, vlines_total, EP93XXFB_VLINES_TOTAL); + ep93xxfb_out_locked(fbi, hclks_total, EP93XXFB_HCLKS_TOTAL); + + start = vlines_total; + stop = vlines_total - info->var.vsync_len; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VSYNC); + + start = vlines_total - info->var.vsync_len - info->var.upper_margin; + stop = info->var.lower_margin - 1; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VBLANK); + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VACTIVE); + + start = vlines_total; + stop = vlines_total + 1; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_VCLK); + + start = hclks_total; + stop = hclks_total - info->var.hsync_len; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HSYNC); + + start = hclks_total - info->var.hsync_len - info->var.left_margin; + stop = info->var.right_margin - 1; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HBLANK); + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HACTIVE); + + start = hclks_total; + stop = hclks_total; + ep93xxfb_out_locked(fbi, start | (stop << 16), EP93XXFB_HCLK); + + ep93xxfb_out_locked(fbi, 0x0, EP93XXFB_LINE_CARRY); +} + +static int ep93xxfb_set_par(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + + clk_set_rate(fbi->clk, 1000 * PICOS2KHZ(info->var.pixclock)); + + ep93xxfb_set_timing(info); + + info->fix.line_length = info->var.xres_virtual * + info->var.bits_per_pixel / 8; + + ep93xxfb_writel(fbi, info->fix.smem_start, EP93XXFB_SCREEN_PAGE); + ep93xxfb_writel(fbi, info->var.yres - 1, EP93XXFB_SCREEN_LINES); + ep93xxfb_writel(fbi, ((info->var.xres * info->var.bits_per_pixel) + / 32) - 1, EP93XXFB_LINE_LENGTH); + ep93xxfb_writel(fbi, info->fix.line_length / 4, EP93XXFB_VLINE_STEP); + ep93xxfb_set_video_attribs(info); + return 0; +} + +static int ep93xxfb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + int err; + + err = ep93xxfb_set_pixelmode(info); + if (err) + return err; + + var->xres = max(var->xres, (unsigned int)EP93XXFB_MIN_XRES); + var->xres = min(var->xres, (unsigned int)EP93XXFB_MAX_XRES); + var->xres_virtual = max(var->xres_virtual, var->xres); + + var->yres = max(var->yres, (unsigned int)EP93XXFB_MIN_YRES); + var->yres = min(var->yres, (unsigned int)EP93XXFB_MAX_YRES); + var->yres_virtual = max(var->yres_virtual, var->yres); + + return 0; +} + +static int ep93xxfb_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + unsigned int offset = vma->vm_pgoff << PAGE_SHIFT; + + if (offset < info->fix.smem_len) { + return dma_mmap_writecombine(info->dev, vma, info->screen_base, + info->fix.smem_start, + info->fix.smem_len); + } + + return -EINVAL; +} + +static int ep93xxfb_blank(int blank_mode, struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int attribs = ep93xxfb_readl(fbi, EP93XXFB_ATTRIBS); + + if (blank_mode) { + if (fbi->mach_info->blank) + fbi->mach_info->blank(blank_mode, info); + ep93xxfb_out_locked(fbi, attribs & ~EP93XXFB_ENABLE, + EP93XXFB_ATTRIBS); + clk_disable(fbi->clk); + } else { + clk_enable(fbi->clk); + ep93xxfb_out_locked(fbi, attribs | EP93XXFB_ENABLE, + EP93XXFB_ATTRIBS); + if (fbi->mach_info->blank) + fbi->mach_info->blank(blank_mode, info); + } + + return 0; +} + +static inline int ep93xxfb_convert_color(int val, int width) +{ + return ((val << width) + 0x7fff - val) >> 16; +} + +static int ep93xxfb_setcolreg(unsigned int regno, unsigned int red, + unsigned int green, unsigned int blue, + unsigned int transp, struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + unsigned int *pal = info->pseudo_palette; + unsigned int ctrl, i, rgb, lut_current, lut_stat; + + switch (info->fix.visual) { + case FB_VISUAL_PSEUDOCOLOR: + rgb = ((red & 0xff00) << 8) | (green & 0xff00) | + ((blue & 0xff00) >> 8); + + pal[regno] = rgb; + ep93xxfb_writel(fbi, rgb, (EP93XXFB_COLOR_LUT + (regno << 2))); + ctrl = ep93xxfb_readl(fbi, EP93XXFB_LUT_SW_CONTROL); + lut_stat = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SSTAT); + lut_current = !!(ctrl & EP93XXFB_LUT_SW_CONTROL_SWTCH); + + if (lut_stat == lut_current) { + for (i = 0; i < 256; i++) { + ep93xxfb_writel(fbi, pal[i], + EP93XXFB_COLOR_LUT + (i << 2)); + } + + ep93xxfb_writel(fbi, + ctrl ^ EP93XXFB_LUT_SW_CONTROL_SWTCH, + EP93XXFB_LUT_SW_CONTROL); + } + break; + + case FB_VISUAL_TRUECOLOR: + if (regno > 16) + return 1; + + red = ep93xxfb_convert_color(red, info->var.red.length); + green = ep93xxfb_convert_color(green, info->var.green.length); + blue = ep93xxfb_convert_color(blue, info->var.blue.length); + transp = ep93xxfb_convert_color(transp, + info->var.transp.length); + + pal[regno] = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + break; + + default: + return 1; + } + + return 0; +} + +static struct fb_ops ep93xxfb_ops = { + .owner = THIS_MODULE, + .fb_check_var = ep93xxfb_check_var, + .fb_set_par = ep93xxfb_set_par, + .fb_blank = ep93xxfb_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_setcolreg = ep93xxfb_setcolreg, + .fb_mmap = ep93xxfb_mmap, +}; + +static int __init ep93xxfb_calc_fbsize(struct ep93xxfb_mach_info *mach_info) +{ + int i, fb_size = 0; + + if (mach_info->num_modes == EP93XXFB_USE_MODEDB) { + fb_size = EP93XXFB_MAX_XRES * EP93XXFB_MAX_YRES * + mach_info->bpp / 8; + } else { + for (i = 0; i < mach_info->num_modes; i++) { + const struct fb_videomode *mode; + int size; + + mode = &mach_info->modes[i]; + size = mode->xres * mode->yres * mach_info->bpp / 8; + if (size > fb_size) + fb_size = size; + } + } + + return fb_size; +} + +static int __init ep93xxfb_alloc_videomem(struct fb_info *info) +{ + struct ep93xx_fbi *fbi = info->par; + char __iomem *virt_addr; + dma_addr_t phys_addr; + unsigned int fb_size; + + fb_size = ep93xxfb_calc_fbsize(fbi->mach_info); + virt_addr = dma_alloc_writecombine(info->dev, fb_size, + &phys_addr, GFP_KERNEL); + if (!virt_addr) + return -ENOMEM; + + /* + * There is a bug in the ep93xx framebuffer which causes problems + * if bit 27 of the physical address is set. + * See: http://marc.info/?l=linux-arm-kernel&m=110061245502000&w=2 + * There does not seem to be any offical errata for this, but I + * have confirmed the problem exists on my hardware (ep9315) at + * least. + */ + if (check_screenpage_bug && phys_addr & (1 << 27)) { + dev_err(info->dev, "ep93xx framebuffer bug. phys addr (0x%x) " + "has bit 27 set: cannot init framebuffer\n", + phys_addr); + + dma_free_coherent(info->dev, fb_size, virt_addr, phys_addr); + return -ENOMEM; + } + + info->fix.smem_start = phys_addr; + info->fix.smem_len = fb_size; + info->screen_base = virt_addr; + + return 0; +} + +static void ep93xxfb_dealloc_videomem(struct fb_info *info) +{ + if (info->screen_base) + dma_free_coherent(info->dev, info->fix.smem_len, + info->screen_base, info->fix.smem_start); +} + +static int __init ep93xxfb_probe(struct platform_device *pdev) +{ + struct ep93xxfb_mach_info *mach_info = pdev->dev.platform_data; + struct fb_info *info; + struct ep93xx_fbi *fbi; + struct resource *res; + char *video_mode; + int err; + + if (!mach_info) + return -EINVAL; + + info = framebuffer_alloc(sizeof(struct ep93xx_fbi), &pdev->dev); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + platform_set_drvdata(pdev, info); + fbi = info->par; + fbi->mach_info = mach_info; + + err = fb_alloc_cmap(&info->cmap, 256, 0); + if (err) + goto failed; + + err = ep93xxfb_alloc_videomem(info); + if (err) + goto failed; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + err = -ENXIO; + goto failed; + } + + res = request_mem_region(res->start, resource_size(res), pdev->name); + if (res == NULL) { + err = -EBUSY; + goto failed; + } + + fbi->res = res; + fbi->mmio_base = ioremap(res->start, resource_size(res)); + if (fbi->mmio_base == NULL) { + err = -ENXIO; + goto failed; + } + + strcpy(info->fix.id, pdev->name); + info->fbops = &ep93xxfb_ops; + info->fix.type = FB_TYPE_PACKED_PIXELS; + info->fix.accel = FB_ACCEL_NONE; + info->var.activate = FB_ACTIVATE_NOW; + info->var.vmode = FB_VMODE_NONINTERLACED; + info->flags = FBINFO_DEFAULT; + info->node = -1; + info->state = FBINFO_STATE_RUNNING; + info->pseudo_palette = &fbi->pseudo_palette; + + fb_get_options("ep93xx-fb", &video_mode); + err = fb_find_mode(&info->var, info, video_mode, + fbi->mach_info->modes, fbi->mach_info->num_modes, + fbi->mach_info->default_mode, fbi->mach_info->bpp); + if (err == 0) { + dev_err(info->dev, "No suitable video mode found\n"); + err = -EINVAL; + goto failed; + } + + if (mach_info->setup) { + err = mach_info->setup(info); + if (err) + return err; + } + + err = ep93xxfb_check_var(&info->var, info); + if (err) + goto failed; + + fbi->clk = clk_get(info->dev, NULL); + if (IS_ERR(fbi->clk)) { + err = PTR_ERR(fbi->clk); + fbi->clk = NULL; + goto failed; + } + + ep93xxfb_set_par(info); + clk_enable(fbi->clk); + + err = register_framebuffer(info); + if (err) + goto failed; + + dev_info(info->dev, "registered. Mode = %dx%d-%d\n", + info->var.xres, info->var.yres, info->var.bits_per_pixel); + return 0; + +failed: + if (fbi->clk) + clk_put(fbi->clk); + if (fbi->mmio_base) + iounmap(fbi->mmio_base); + if (fbi->res) + release_mem_region(fbi->res->start, resource_size(fbi->res)); + ep93xxfb_dealloc_videomem(info); + if (&info->cmap) + fb_dealloc_cmap(&info->cmap); + if (fbi->mach_info->teardown) + fbi->mach_info->teardown(info); + kfree(info); + platform_set_drvdata(pdev, NULL); + + return err; +} + +static int ep93xxfb_remove(struct platform_device *pdev) +{ + struct fb_info *info = platform_get_drvdata(pdev); + struct ep93xx_fbi *fbi = info->par; + + unregister_framebuffer(info); + clk_disable(fbi->clk); + clk_put(fbi->clk); + iounmap(fbi->mmio_base); + release_mem_region(fbi->res->start, resource_size(fbi->res)); + ep93xxfb_dealloc_videomem(info); + fb_dealloc_cmap(&info->cmap); + + if (fbi->mach_info->teardown) + fbi->mach_info->teardown(info); + + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver ep93xxfb_driver = { + .probe = ep93xxfb_probe, + .remove = ep93xxfb_remove, + .driver = { + .name = "ep93xx-fb", + .owner = THIS_MODULE, + }, +}; + +static int __devinit ep93xxfb_init(void) +{ + return platform_driver_register(&ep93xxfb_driver); +} + +static void __exit ep93xxfb_exit(void) +{ + platform_driver_unregister(&ep93xxfb_driver); +} + +module_init(ep93xxfb_init); +module_exit(ep93xxfb_exit); + +MODULE_DESCRIPTION("EP93XX Framebuffer Driver"); +MODULE_ALIAS("platform:ep93xx-fb"); +MODULE_AUTHOR("Ryan Mallon <ryan&bluewatersys.com>, " + "H Hartley Sweeten <hsw...@vi..."); +MODULE_LICENSE("GPL"); |
From: Ryan M. <ry...@bl...> - 2009-07-17 04:03:29
|
Add platform support and video clock for ep93xx framebuffer driver Signed-off-by: Ryan Mallon <ry...@bl...> --- diff --git a/arch/arm/mach-ep93xx/clock.c b/arch/arm/mach-ep93xx/clock.c index b6b5344..4367a09 100644 --- a/arch/arm/mach-ep93xx/clock.c +++ b/arch/arm/mach-ep93xx/clock.c @@ -37,7 +37,7 @@ struct clk { static unsigned long get_uart_rate(struct clk *clk); static int set_keytchclk_rate(struct clk *clk, unsigned long rate); - +static int set_div_rate(struct clk *clk, unsigned long rate); static struct clk clk_uart1 = { .sw_locked = 1, @@ -73,6 +73,13 @@ static struct clk clk_keypad = { .set_rate = set_keytchclk_rate, }; +static struct clk clk_video = { + .enable_reg = EP93XX_SYSCON_VIDCLKDIV, + .enable_mask = EP93XX_SYSCON_CLKDIV_ENABLE, + .sw_locked = 1, + .set_rate = set_div_rate, +}; + /* DMA Clocks */ static struct clk clk_m2p0 = { .enable_reg = EP93XX_SYSCON_PWRCNT, @@ -137,6 +144,7 @@ static struct clk_lookup clocks[] = { INIT_CK(NULL, "pll2", &clk_pll2), INIT_CK("ep93xx-ohci", NULL, &clk_usb_host), INIT_CK("ep93xx-keypad", NULL, &clk_keypad), + INIT_CK("ep93xx-fb", NULL, &clk_video), INIT_CK(NULL, "m2p0", &clk_m2p0), INIT_CK(NULL, "m2p1", &clk_m2p1), INIT_CK(NULL, "m2p2", &clk_m2p2), @@ -232,6 +240,86 @@ static int set_keytchclk_rate(struct clk *clk, unsigned long rate) return 0; } +static unsigned long calc_clk_div(unsigned long rate, int *psel, int *esel, + int *pdiv, int *div) +{ + unsigned long max_rate, best_rate = 0, + actual_rate = 0, mclk_rate = 0, rate_err = -1; + int i, found = 0, __div = 0, __pdiv = 0; + + /* Don't exceed the maximum rate */ + max_rate = max(max(clk_pll1.rate / 4, clk_pll2.rate / 4), + (unsigned long)EP93XX_EXT_CLK_RATE / 4); + rate = min(rate, max_rate); + + /* + * Try the two pll's and the external clock + * Because the valid predividers are 2, 2.5 and 3, we multiply + * all the clocks by 2 to avoid floating point math. + * + * This is based on the algorithm in the ep93xx raster guide: + * http://be-a-maverick.com/en/pubs/appNote/AN269REV1.pdf + * + */ + for (i = 0; i < 3; i++) { + if (i == 0) + mclk_rate = EP93XX_EXT_CLK_RATE * 2; + else if (i == 1) + mclk_rate = clk_pll1.rate * 2; + else if (i == 2) + mclk_rate = clk_pll2.rate * 2; + + /* Try each predivider value */ + for (__pdiv = 4; __pdiv <= 6; __pdiv++) { + __div = mclk_rate / (rate * __pdiv); + if (__div < 2 || __div > 127) + continue; + + actual_rate = mclk_rate / __pdiv * __div; + + if (!found || abs(actual_rate - rate) < rate_err) { + *pdiv = __pdiv - 3; + *div = __div; + *psel = (i == 2); + *esel = (i != 0); + best_rate = actual_rate; + rate_err = actual_rate - rate; + found = 1; + } + } + } + + if (!found) + return 0; + + return best_rate; +} + +static int set_div_rate(struct clk *clk, unsigned long rate) +{ + unsigned long actual_rate; + int psel = 0, esel = 0, pdiv = 0, div = 0; + u32 val; + + actual_rate = calc_clk_div(rate, &psel, &esel, &pdiv, &div); + if (actual_rate == 0) + return -EINVAL; + clk->rate = actual_rate; + + /* Clear the esel, psel, pdiv and div bits */ + val = __raw_readl(clk->enable_reg); + val &= ~0x7fff; + + /* Set the new esel, psel, pdiv and div bits for the new clock rate */ + val |= (esel ? EP93XX_SYSCON_CLKDIV_ESEL : 0) | + (psel ? EP93XX_SYSCON_CLKDIV_PSEL : 0) | + (pdiv << EP93XX_SYSCON_CLKDIV_PDIV_SHIFT) | div; + __raw_writel(0xaa, EP93XX_SYSCON_SWLOCK); + __raw_writel(val, clk->enable_reg); + + return 0; +} + int clk_set_rate(struct clk *clk, unsigned long rate) { if (clk->set_rate) diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c index 8e59bdc..0e96ada 100644 --- a/arch/arm/mach-ep93xx/core.c +++ b/arch/arm/mach-ep93xx/core.c @@ -36,6 +36,7 @@ #include <asm/hardware/vic.h> +#include <mach/fb.h> /************************************************************************* * Static I/O mappings that are needed for all EP93xx platforms @@ -570,6 +571,35 @@ void __init ep93xx_register_i2c(struct i2c_board_info *devices, int num) platform_device_register(&ep93xx_i2c_device); } +/************************************************************************* + * EP93xx video peripheral handling + *************************************************************************/ +static struct ep93xxfb_mach_info ep93xxfb_data; + +static struct resource ep93xx_fb_resource[] = { + { + .start = EP93XX_RASTER_PHYS_BASE, + .end = EP93XX_RASTER_PHYS_BASE + 0x800 - 1, + .flags = IORESOURCE_MEM, + }, +}; + +static struct platform_device ep93xx_fb_device = { + .name = "ep93xx-fb", + .id = -1, + .dev.platform_data = &ep93xxfb_data, + .dev.coherent_dma_mask = DMA_BIT_MASK(32), + .dev.dma_mask = &ep93xx_fb_device.dev.coherent_dma_mask, + .num_resources = ARRAY_SIZE(ep93xx_fb_resource), + .resource = ep93xx_fb_resource, +}; + +void __init ep93xx_register_fb(struct ep93xxfb_mach_info *data) +{ + ep93xxfb_data = *data; + platform_device_register(&ep93xx_fb_device); +} + extern void ep93xx_gpio_init(void); void __init ep93xx_init_devices(void) diff --git a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h index a11ae77..33765fa 100644 --- a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h +++ b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h @@ -70,6 +70,7 @@ #define EP93XX_USB_PHYS_BASE (EP93XX_AHB_PHYS_BASE + 0x00020000) #define EP93XX_USB_BASE EP93XX_AHB_IOMEM(0x00020000) +#define EP93XX_RASTER_PHYS_BASE (EP93XX_AHB_PHYS_BASE + 0x00030000) #define EP93XX_RASTER_BASE EP93XX_AHB_IOMEM(0x00030000) #define EP93XX_GRAPHICS_ACCEL_BASE EP93XX_AHB_IOMEM(0x00040000) @@ -206,13 +207,17 @@ #define EP93XX_SYSCON_DEVCFG_ADCPD (1<<2) #define EP93XX_SYSCON_DEVCFG_KEYS (1<<1) #define EP93XX_SYSCON_DEVCFG_SHENA (1<<0) +#define EP93XX_SYSCON_VIDCLKDIV EP93XX_SYSCON_REG(0x84) +#define EP93XX_SYSCON_CLKDIV_PSEL (1 << 13) +#define EP93XX_SYSCON_CLKDIV_ESEL (1 << 14) +#define EP93XX_SYSCON_CLKDIV_ENABLE (1 << 15) +#define EP93XX_SYSCON_CLKDIV_PDIV_SHIFT 8 #define EP93XX_SYSCON_KEYTCHCLKDIV EP93XX_SYSCON_REG(0x90) #define EP93XX_SYSCON_KEYTCHCLKDIV_TSEN (1<<31) #define EP93XX_SYSCON_KEYTCHCLKDIV_ADIV (1<<16) #define EP93XX_SYSCON_KEYTCHCLKDIV_KEN (1<<15) #define EP93XX_SYSCON_KEYTCHCLKDIV_KDIV (1<<0) #define EP93XX_SYSCON_SWLOCK EP93XX_SYSCON_REG(0xc0) - #define EP93XX_WATCHDOG_BASE EP93XX_APB_IOMEM(0x00140000) diff --git a/arch/arm/mach-ep93xx/include/mach/platform.h b/arch/arm/mach-ep93xx/include/mach/platform.h index 0af0a3b..e8787e0 100644 --- a/arch/arm/mach-ep93xx/include/mach/platform.h +++ b/arch/arm/mach-ep93xx/include/mach/platform.h @@ -5,6 +5,7 @@ #ifndef __ASSEMBLY__ struct i2c_board_info; +struct ep93xxfb_mach_info; struct ep93xx_eth_data { @@ -32,7 +33,7 @@ static inline void ep93xx_devcfg_clear_bits(unsigned int bits) void ep93xx_register_eth(struct ep93xx_eth_data *data, int copy_addr); void ep93xx_register_i2c(struct i2c_board_info *devices, int num); - +void ep93xx_register_fb(struct ep93xxfb_mach_info *data); void ep93xx_init_devices(void); extern struct sys_timer ep93xx_timer; |
From: H H. S. <har...@vi...> - 2009-07-17 17:36:06
|
On Thursday, July 16, 2009 8:58 PM, Ryan Mallon wrote: > Add platform support and video clock for ep93xx framebuffer driver > > Signed-off-by: Ryan Mallon <ry...@bl...> > --- > +#include <mach/fb.h> This new header is not included in either patchset. Thanks, Hartley |
From: H H. S. <har...@vi...> - 2009-07-17 17:17:31
|
On Thursday, July 16, 2009 8:58 PM, Ryan Mallon wrote: > Add platform support and video clock for ep93xx framebuffer driver > > Signed-off-by: Ryan Mallon <ry...@bl...> > --- > > diff --git a/arch/arm/mach-ep93xx/clock.c b/arch/arm/mach-ep93xx/clock.c > index b6b5344..4367a09 100644 > --- a/arch/arm/mach-ep93xx/clock.c > +++ b/arch/arm/mach-ep93xx/clock.c > @@ -37,7 +37,7 @@ struct clk { > static unsigned long get_uart_rate(struct clk *clk); > > static int set_keytchclk_rate(struct clk *clk, unsigned long rate); > - > +static int set_div_rate(struct clk *clk, unsigned long rate); > > static struct clk clk_uart1 = { > .sw_locked = 1, > @@ -73,6 +73,13 @@ static struct clk clk_keypad = { > .set_rate = set_keytchclk_rate, > }; > > +static struct clk clk_video = { > + .enable_reg = EP93XX_SYSCON_VIDCLKDIV, > + .enable_mask = EP93XX_SYSCON_CLKDIV_ENABLE, > + .sw_locked = 1, > + .set_rate = set_div_rate, > +}; Please put .sw_locked before .enable_reg so it's consistent with the other clk's. > + > /* DMA Clocks */ > static struct clk clk_m2p0 = { > .enable_reg = EP93XX_SYSCON_PWRCNT, > @@ -137,6 +144,7 @@ static struct clk_lookup clocks[] = { > INIT_CK(NULL, "pll2", &clk_pll2), > INIT_CK("ep93xx-ohci", NULL, &clk_usb_host), > INIT_CK("ep93xx-keypad", NULL, &clk_keypad), > + INIT_CK("ep93xx-fb", NULL, &clk_video), > INIT_CK(NULL, "m2p0", &clk_m2p0), > INIT_CK(NULL, "m2p1", &clk_m2p1), > INIT_CK(NULL, "m2p2", &clk_m2p2), > @@ -232,6 +240,86 @@ static int set_keytchclk_rate(struct clk *clk, unsigned long rate) > return 0; > } > > +static unsigned long calc_clk_div(unsigned long rate, int *psel, int *esel, > + int *pdiv, int *div) > +{ > + unsigned long max_rate, best_rate = 0, > + actual_rate = 0, mclk_rate = 0, rate_err = -1; > + int i, found = 0, __div = 0, __pdiv = 0; > + > + /* Don't exceed the maximum rate */ > + max_rate = max(max(clk_pll1.rate / 4, clk_pll2.rate / 4), > + (unsigned long)EP93XX_EXT_CLK_RATE / 4); > + rate = min(rate, max_rate); > + > + /* > + * Try the two pll's and the external clock > + * Because the valid predividers are 2, 2.5 and 3, we multiply > + * all the clocks by 2 to avoid floating point math. > + * > + * This is based on the algorithm in the ep93xx raster guide: > + * http://be-a-maverick.com/en/pubs/appNote/AN269REV1.pdf > + * > + */ > + for (i = 0; i < 3; i++) { > + if (i == 0) > + mclk_rate = EP93XX_EXT_CLK_RATE * 2; > + else if (i == 1) > + mclk_rate = clk_pll1.rate * 2; > + else if (i == 2) > + mclk_rate = clk_pll2.rate * 2; > + > + /* Try each predivider value */ > + for (__pdiv = 4; __pdiv <= 6; __pdiv++) { > + __div = mclk_rate / (rate * __pdiv); > + if (__div < 2 || __div > 127) > + continue; > + > + actual_rate = mclk_rate / __pdiv * __div; > + > + if (!found || abs(actual_rate - rate) < rate_err) { > + *pdiv = __pdiv - 3; > + *div = __div; > + *psel = (i == 2); > + *esel = (i != 0); > + best_rate = actual_rate; > + rate_err = actual_rate - rate; > + found = 1; > + } > + } > + } > + > + if (!found) > + return 0; > + > + return best_rate; > +} > + > +static int set_div_rate(struct clk *clk, unsigned long rate) > +{ > + unsigned long actual_rate; > + int psel = 0, esel = 0, pdiv = 0, div = 0; > + u32 val; > + > + actual_rate = calc_clk_div(rate, &psel, &esel, &pdiv, &div); > + if (actual_rate == 0) > + return -EINVAL; > + clk->rate = actual_rate; > + > + /* Clear the esel, psel, pdiv and div bits */ > + val = __raw_readl(clk->enable_reg); > + val &= ~0x7fff; > + > + /* Set the new esel, psel, pdiv and div bits for the new clock rate */ > + val |= (esel ? EP93XX_SYSCON_CLKDIV_ESEL : 0) | > + (psel ? EP93XX_SYSCON_CLKDIV_PSEL : 0) | > + (pdiv << EP93XX_SYSCON_CLKDIV_PDIV_SHIFT) | div; > + __raw_writel(0xaa, EP93XX_SYSCON_SWLOCK); > + __raw_writel(val, clk->enable_reg); ep93xx_syscon_swlocked_write(val, clk->enable_reg); > + > + return 0; > +} > + > int clk_set_rate(struct clk *clk, unsigned long rate) > { > if (clk->set_rate) > diff --git a/arch/arm/mach-ep93xx/core.c b/arch/arm/mach-ep93xx/core.c > index 8e59bdc..0e96ada 100644 > --- a/arch/arm/mach-ep93xx/core.c > +++ b/arch/arm/mach-ep93xx/core.c > @@ -36,6 +36,7 @@ > > #include <asm/hardware/vic.h> > > +#include <mach/fb.h> Please move this include up so it's right after <mach/hardware.h> > > /************************************************************************* > * Static I/O mappings that are needed for all EP93xx platforms > @@ -570,6 +571,35 @@ void __init ep93xx_register_i2c(struct i2c_board_info *devices, int num) > platform_device_register(&ep93xx_i2c_device); > } > > +/************************************************************************* > + * EP93xx video peripheral handling > + *************************************************************************/ > +static struct ep93xxfb_mach_info ep93xxfb_data; > + > +static struct resource ep93xx_fb_resource[] = { > + { > + .start = EP93XX_RASTER_PHYS_BASE, > + .end = EP93XX_RASTER_PHYS_BASE + 0x800 - 1, > + .flags = IORESOURCE_MEM, > + }, > +}; > + > +static struct platform_device ep93xx_fb_device = { > + .name = "ep93xx-fb", > + .id = -1, > + .dev.platform_data = &ep93xxfb_data, > + .dev.coherent_dma_mask = DMA_BIT_MASK(32), > + .dev.dma_mask = &ep93xx_fb_device.dev.coherent_dma_mask, > + .num_resources = ARRAY_SIZE(ep93xx_fb_resource), > + .resource = ep93xx_fb_resource, > +}; Question. Is ".dev.* = " preferred over: > + .dev = { > + .platform_data = &ep93xxfb_data, > + .coherent_dma_mask = DMA_BIT_MASK(32), > + .dma_mask = &ep93xx_fb_device.dev.coherent_dma_mask, > + }, I think the form above is a bit easier to read. > + > +void __init ep93xx_register_fb(struct ep93xxfb_mach_info *data) > +{ > + ep93xxfb_data = *data; > + platform_device_register(&ep93xx_fb_device); > +} > + > extern void ep93xx_gpio_init(void); > > void __init ep93xx_init_devices(void) > diff --git a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h > index a11ae77..33765fa 100644 > --- a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h > +++ b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h > @@ -70,6 +70,7 @@ > #define EP93XX_USB_PHYS_BASE (EP93XX_AHB_PHYS_BASE + 0x00020000) > #define EP93XX_USB_BASE EP93XX_AHB_IOMEM(0x00020000) > > +#define EP93XX_RASTER_PHYS_BASE (EP93XX_AHB_PHYS_BASE + 0x00030000) > #define EP93XX_RASTER_BASE EP93XX_AHB_IOMEM(0x00030000) > > #define EP93XX_GRAPHICS_ACCEL_BASE EP93XX_AHB_IOMEM(0x00040000) > @@ -206,13 +207,17 @@ > #define EP93XX_SYSCON_DEVCFG_ADCPD (1<<2) > #define EP93XX_SYSCON_DEVCFG_KEYS (1<<1) > #define EP93XX_SYSCON_DEVCFG_SHENA (1<<0) > +#define EP93XX_SYSCON_VIDCLKDIV EP93XX_SYSCON_REG(0x84) > +#define EP93XX_SYSCON_CLKDIV_PSEL (1 << 13) > +#define EP93XX_SYSCON_CLKDIV_ESEL (1 << 14) > +#define EP93XX_SYSCON_CLKDIV_ENABLE (1 << 15) > +#define EP93XX_SYSCON_CLKDIV_PDIV_SHIFT 8 > #define EP93XX_SYSCON_KEYTCHCLKDIV EP93XX_SYSCON_REG(0x90) > #define EP93XX_SYSCON_KEYTCHCLKDIV_TSEN (1<<31) > #define EP93XX_SYSCON_KEYTCHCLKDIV_ADIV (1<<16) > #define EP93XX_SYSCON_KEYTCHCLKDIV_KEN (1<<15) > #define EP93XX_SYSCON_KEYTCHCLKDIV_KDIV (1<<0) > #define EP93XX_SYSCON_SWLOCK EP93XX_SYSCON_REG(0xc0) What tree is this patch based on? The KEYTCHCLKDIV defines are not in Russell's devel branch. If (1 << 13) is preferred over (1<<13) we should eventually update this entire file so that the defines are consistent. > - > #define EP93XX_WATCHDOG_BASE EP93XX_APB_IOMEM(0x00140000) > > > diff --git a/arch/arm/mach-ep93xx/include/mach/platform.h b/arch/arm/mach-ep93xx/include/mach/platform.h > index 0af0a3b..e8787e0 100644 > --- a/arch/arm/mach-ep93xx/include/mach/platform.h > +++ b/arch/arm/mach-ep93xx/include/mach/platform.h > @@ -5,6 +5,7 @@ > #ifndef __ASSEMBLY__ > > struct i2c_board_info; > +struct ep93xxfb_mach_info; > > struct ep93xx_eth_data > { > @@ -32,7 +33,7 @@ static inline void ep93xx_devcfg_clear_bits(unsigned int bits) > > void ep93xx_register_eth(struct ep93xx_eth_data *data, int copy_addr); > void ep93xx_register_i2c(struct i2c_board_info *devices, int num); > - > +void ep93xx_register_fb(struct ep93xxfb_mach_info *data); > void ep93xx_init_devices(void); > extern struct sys_timer ep93xx_timer; Please add a whitespace between ep93xx_register_fb and ep93xx_init_devices. The ep93xx_register_* functions are all machine optional but ep93xx_init_devices is required. Regards, Hartley |
From: Ryan M. <ry...@bl...> - 2009-07-18 23:31:17
|
H Hartley Sweeten wrote: > On Thursday, July 16, 2009 8:58 PM, Ryan Mallon wrote: >> diff --git a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h >> index a11ae77..33765fa 100644 >> --- a/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h >> +++ b/arch/arm/mach-ep93xx/include/mach/ep93xx-regs.h >> @@ -70,6 +70,7 @@ >> #define EP93XX_USB_PHYS_BASE (EP93XX_AHB_PHYS_BASE + 0x00020000) >> #define EP93XX_USB_BASE EP93XX_AHB_IOMEM(0x00020000) >> >> +#define EP93XX_RASTER_PHYS_BASE (EP93XX_AHB_PHYS_BASE + 0x00030000) >> #define EP93XX_RASTER_BASE EP93XX_AHB_IOMEM(0x00030000) >> >> #define EP93XX_GRAPHICS_ACCEL_BASE EP93XX_AHB_IOMEM(0x00040000) >> @@ -206,13 +207,17 @@ >> #define EP93XX_SYSCON_DEVCFG_ADCPD (1<<2) >> #define EP93XX_SYSCON_DEVCFG_KEYS (1<<1) >> #define EP93XX_SYSCON_DEVCFG_SHENA (1<<0) >> +#define EP93XX_SYSCON_VIDCLKDIV EP93XX_SYSCON_REG(0x84) >> +#define EP93XX_SYSCON_CLKDIV_PSEL (1 << 13) >> +#define EP93XX_SYSCON_CLKDIV_ESEL (1 << 14) >> +#define EP93XX_SYSCON_CLKDIV_ENABLE (1 << 15) >> +#define EP93XX_SYSCON_CLKDIV_PDIV_SHIFT 8 >> #define EP93XX_SYSCON_KEYTCHCLKDIV EP93XX_SYSCON_REG(0x90) >> #define EP93XX_SYSCON_KEYTCHCLKDIV_TSEN (1<<31) >> #define EP93XX_SYSCON_KEYTCHCLKDIV_ADIV (1<<16) >> #define EP93XX_SYSCON_KEYTCHCLKDIV_KEN (1<<15) >> #define EP93XX_SYSCON_KEYTCHCLKDIV_KDIV (1<<0) >> #define EP93XX_SYSCON_SWLOCK EP93XX_SYSCON_REG(0xc0) > > What tree is this patch based on? The KEYTCHCLKDIV defines are not in Russell's > devel branch. I put the patch to fix the keypad #defines in my tree before rebasing the driver. I think this order is preferred since the keypad defines patch is needed for the tree to build without errors. > If (1 << 13) is preferred over (1<<13) we should eventually update this entire file > so that the defines are consistent. I prefer the spaces, but I'll change them in the header file so it is consistent with the rest of the file. We can do a patch later to change all of the defines in the header file, but I honestly don't think it matters as long as it is consistent. ~Ryan |