[PATCH] fb: SM501 framebuffer driver
Changes
3 files changed · +1805 −0
| File | Status | Add | Del | Content |
|---|---|---|---|---|
| drivers/video/Kconfig | modified | +18 | −0 | View file |
| drivers/video/Makefile | modified | +1 | −0 | View file |
| drivers/video/sm501fb.c | added | +1786 | −0 | View file |
Only the first 600 diff lines are shown. Download the complete patch.
drivers/video/Kconfig
View filediff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index f8bc43c..c1536d7 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -1573,6 +1573,24 @@ config FB_S3C2410_DEBUG
Turn on debugging messages. Note that you can set/unset at run time
through sysfs
+config FB_SM501
+ tristate "Silicon Motion SM501 framebuffer support"
+ depends on FB && MFD_SM501
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ ---help---
+ Frame buffer driver for the CRT and LCD controllers in the Silicon
+ Motion SM501.
+
+ This driver is also available as a module ( = code which can be
+ inserted and removed from the running kernel whenever you want). The
+ module will be called sm501fb. If you want to compile it as a module,
+ say M here and read <file:Documentation/modules.txt>.
+
+ If unsure, say N.
+
+
config FB_PNX4008_DUM
tristate "Display Update Module support on Philips PNX4008 board"
depends on FB && ARCH_PNX4008
drivers/video/Makefile
View filediff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 1b79a6f..760305c 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -98,6 +98,7 @@ obj-$(CONFIG_FB_PNX4008_DUM) += pnx4008/
obj-$(CONFIG_FB_PNX4008_DUM_RGB) += pnx4008/
obj-$(CONFIG_FB_IBM_GXT4500) += gxt4500.o
obj-$(CONFIG_FB_PS3) += ps3fb.o
+obj-$(CONFIG_FB_SM501) += sm501fb.o
# Platform or fallback drivers go here
obj-$(CONFIG_FB_VESA) += vesafb.o
drivers/video/sm501fb.c
View filediff --git a/drivers/video/sm501fb.c b/drivers/video/sm501fb.c
new file mode 100644
index 0000000..02b290c
--- /dev/null
+++ b/drivers/video/sm501fb.c
@@ -0,0 +1,1786 @@
+/* linux/drivers/video/sm501fb.c
+ *
+ * Copyright (c) 2006 Simtec Electronics
+ * Vincent Sanders <[email protected]>
+ * Ben Dooks <[email protected]>
+ *
+ * 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.
+ *
+ * Framebuffer driver for the Silicon Motion SM501
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/tty.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/fb.h>
+#include <linux/init.h>
+#include <linux/vmalloc.h>
+#include <linux/dma-mapping.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/wait.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/div64.h>
+
+#ifdef CONFIG_PM
+#include <linux/pm.h>
+#endif
+
+#include <linux/sm501.h>
+#include <linux/sm501-regs.h>
+
+#define NR_PALETTE 256
+
+enum sm501_controller {
+ HEAD_CRT = 0,
+ HEAD_PANEL = 1,
+};
+
+/* SM501 memory adress */
+struct sm501_mem {
+ unsigned long size;
+ unsigned long sm_addr;
+ void __iomem *k_addr;
+};
+
+/* private data that is shared between all frambuffers* */
+struct sm501fb_info {
+ struct device *dev;
+ struct fb_info *fb[2]; /* fb info for both heads */
+ struct resource *fbmem_res; /* framebuffer resource */
+ struct resource *regs_res; /* registers resource */
+ struct sm501_platdata_fb *pdata; /* our platform data */
+
+ int irq;
+ int swap_endian; /* set to swap rgb=>bgr */
+ void __iomem *regs; /* remapped registers */
+ void __iomem *fbmem; /* remapped framebuffer */
+ size_t fbmem_len; /* length of remapped region */
+};
+
+/* per-framebuffer private data */
+struct sm501fb_par {
+ u32 pseudo_palette[16];
+
+ enum sm501_controller head;
+ struct sm501_mem cursor;
+ struct sm501_mem screen;
+ struct fb_ops ops;
+
+ void *store_fb;
+ void *store_cursor;
+ void __iomem *cursor_regs;
+ struct sm501fb_info *info;
+};
+
+/* Helper functions */
+
+static inline int h_total(struct fb_var_screeninfo *var)
+{
+ return var->xres + var->left_margin +
+ var->right_margin + var->hsync_len;
+}
+
+static inline int v_total(struct fb_var_screeninfo *var)
+{
+ return var->yres + var->upper_margin +
+ var->lower_margin + var->vsync_len;
+}
+
+/* sm501fb_sync_regs()
+ *
+ * This call is mainly for PCI bus systems where we need to
+ * ensure that any writes to the bus are completed before the
+ * next phase, or after completing a function.
+*/
+
+static inline void sm501fb_sync_regs(struct sm501fb_info *info)
+{
+ readl(info->regs);
+}
+
+/* sm501_alloc_mem
+ *
+ * This is an attempt to lay out memory for the two framebuffers and
+ * everything else
+ *
+ * |fbmem_res->start fbmem_res->end|
+ * | |
+ * |fb[0].fix.smem_start | |fb[1].fix.smem_start | 2K |
+ * |-> fb[0].fix.smem_len <-| spare |-> fb[1].fix.smem_len <-|-> cursors <-|
+ *
+ * The "spare" space is for the 2d engine data
+ * the fixed is space for the cursors (2x1Kbyte)
+ *
+ * we need to allocate memory for the 2D acceleration engine
+ * command list and the data for the engine to deal with.
+ *
+ * - all allocations must be 128bit aligned
+ * - cursors are 64x64x2 bits (1Kbyte)
+ *
+ */
+
+#define SM501_MEMF_CURSOR (1)
+#define SM501_MEMF_PANEL (2)
+#define SM501_MEMF_CRT (4)
+#define SM501_MEMF_ACCEL (8)
+
+int sm501_alloc_mem(struct sm501fb_info *inf, struct sm501_mem *mem,
+ unsigned int why, size_t size)
+{
+ unsigned int ptr = 0;
+
+ switch (why) {
+ case SM501_MEMF_CURSOR:
+ ptr = inf->fbmem_len - size;
+ inf->fbmem_len = ptr;
+ break;
+
+ case SM501_MEMF_PANEL:
+ ptr = inf->fbmem_len - size;
+ if (ptr < inf->fb[0]->fix.smem_len)
+ return -ENOMEM;
+
+ break;
+
+ case SM501_MEMF_CRT:
+ ptr = 0;
+ break;
+
+ case SM501_MEMF_ACCEL:
+ ptr = inf->fb[0]->fix.smem_len;
+
+ if ((ptr + size) >
+ (inf->fb[1]->fix.smem_start - inf->fbmem_res->start))
+ return -ENOMEM;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ mem->size = size;
+ mem->sm_addr = ptr;
+ mem->k_addr = inf->fbmem + ptr;
+
+ dev_dbg(inf->dev, "%s: result %08lx, %p - %u, %zd\n",
+ __func__, mem->sm_addr, mem->k_addr, why, size);
+
+ return 0;
+}
+
+/* sm501fb_ps_to_hz
+ *
+ * Converts a period in picoseconds to Hz.
+ *
+ * Note, we try to keep this in Hz to minimise rounding with
+ * the limited PLL settings on the SM501.
+*/
+
+static unsigned long sm501fb_ps_to_hz(unsigned long psvalue)
+{
+ unsigned long long numerator=1000000000000ULL;
+
+ /* 10^12 / picosecond period gives frequency in Hz */
+ do_div(numerator, psvalue);
+ return (unsigned long)numerator;
+}
+
+/* sm501fb_hz_to_ps is identical to the oposite transform */
+
+#define sm501fb_hz_to_ps(x) sm501fb_ps_to_hz(x)
+
+/* sm501fb_setup_gamma
+ *
+ * Programs a linear 1.0 gamma ramp in case the gamma
+ * correction is enabled without programming anything else.
+*/
+
+static void sm501fb_setup_gamma(struct sm501fb_info *fbi,
+ unsigned long palette)
+{
+ unsigned long value = 0;
+ int offset;
+
+ /* set gamma values */
+ for (offset = 0; offset < 256 * 4; offset += 4) {
+ writel(value, fbi->regs + palette + offset);
+ value += 0x010101; /* Advance RGB by 1,1,1.*/
+ }
+}
+
+/* sm501fb_check_var
+ *
+ * check common variables for both panel and crt
+*/
+
+static int sm501fb_check_var(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct sm501fb_par *par = info->par;
+ struct sm501fb_info *sm = par->info;
+ unsigned long tmp;
+
+ /* check we can fit these values into the registers */
+
+ if (var->hsync_len > 255 || var->vsync_len > 255)
+ return -EINVAL;
+
+ if ((var->xres + var->right_margin) >= 4096)
+ return -EINVAL;
+
+ if ((var->yres + var->lower_margin) > 2048)
+ return -EINVAL;
+
+ /* hard limits of device */
+
+ if (h_total(var) > 4096 || v_total(var) > 2048)
+ return -EINVAL;
+
+ /* check our line length is going to be 128 bit aligned */
+
+ tmp = (var->xres * var->bits_per_pixel) / 8;
+ if ((tmp & 15) != 0)
+ return -EINVAL;
+
+ /* check the virtual size */
+
+ if (var->xres_virtual > 4096 || var->yres_virtual > 2048)
+ return -EINVAL;
+
+ /* can cope with 8,16 or 32bpp */
+
+ if (var->bits_per_pixel <= 8)
+ var->bits_per_pixel = 8;
+ else if (var->bits_per_pixel <= 16)
+ var->bits_per_pixel = 16;
+ else if (var->bits_per_pixel == 24)
+ var->bits_per_pixel = 32;
+
+ /* set r/g/b positions and validate bpp */
+ switch(var->bits_per_pixel) {
+ case 8:
+ var->red.length = var->bits_per_pixel;
+ var->red.offset = 0;
+ var->green.length = var->bits_per_pixel;
+ var->green.offset = 0;
+ var->blue.length = var->bits_per_pixel;
+ var->blue.offset = 0;
+ var->transp.length = 0;
+
+ break;
+
+ case 16:
+ if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) {
+ var->red.offset = 11;
+ var->green.offset = 5;
+ var->blue.offset = 0;
+ } else {
+ var->blue.offset = 11;
+ var->green.offset = 5;
+ var->red.offset = 0;
+ }
+
+ var->red.length = 5;
+ var->green.length = 6;
+ var->blue.length = 5;
+ var->transp.length = 0;
+ break;
+
+ case 32:
+ if (sm->pdata->flags & SM501_FBPD_SWAP_FB_ENDIAN) {
+ var->transp.offset = 0;
+ var->red.offset = 8;
+ var->green.offset = 16;
+ var->blue.offset = 24;
+ } else {
+ var->transp.offset = 24;
+ var->red.offset = 16;
+ var->green.offset = 8;
+ var->blue.offset = 0;
+ }
+
+ var->red.length = 8;
+ var->green.length = 8;
+ var->blue.length = 8;
+ var->transp.length = 0;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/*
+ * sm501fb_check_var_crt():
+ *
+ * check the parameters for the CRT head, and either bring them
+ * back into range, or return -EINVAL.
+*/
+
+static int sm501fb_check_var_crt(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ return sm501fb_check_var(var, info);
+}
+
+/* sm501fb_check_var_pnl():
+ *
+ * check the parameters for the CRT head, and either bring them
+ * back into range, or return -EINVAL.
+*/
+
+static int sm501fb_check_var_pnl(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ return sm501fb_check_var(var, info);
+}
+
+/* sm501fb_set_par_common
+ *
+ * set common registers for framebuffers
+*/
+
+static int sm501fb_set_par_common(struct fb_info *info,
+ struct fb_var_screeninfo *var)
+{
+ struct sm501fb_par *par = info->par;
+ struct sm501fb_info *fbi = par->info;
+ unsigned long pixclock; /* pixelclock in Hz */
+ unsigned long sm501pixclock; /* pixelclock the 501 can achive in Hz */
+ unsigned int mem_type;
+ unsigned int clock_type;
+ unsigned int head_addr;
+
+ dev_dbg(fbi->dev, "%s: %dx%d, bpp = %d, virtual %dx%d\n",
+ __func__, var->xres, var->yres, var->bits_per_pixel,
+ var->xres_virtual, var->yres_virtual);
+
+ switch (par->head) {
+ case HEAD_CRT:
+ mem_type = SM501_MEMF_CRT;
+ clock_type = SM501_CLOCK_V2XCLK;
+ head_addr = SM501_DC_CRT_FB_ADDR;
+ break;
+
+ case HEAD_PANEL:
+ mem_type = SM501_MEMF_PANEL;
+ clock_type = SM501_CLOCK_P2XCLK;
+ head_addr = SM501_DC_PANEL_FB_ADDR;
+ break;
+
+ default:
+ mem_type = 0; /* stop compiler warnings */
+ head_addr = 0;
+ clock_type = 0;
+ }
+
+ switch (var->bits_per_pixel) {
+ case 8:
+ info->fix.visual = FB_VISUAL_PSEUDOCOLOR;
+ break;
+
+ case 16:
+ info->fix.visual = FB_VISUAL_DIRECTCOLOR;
+ break;
+
+ case 32:
+ info->fix.visual = FB_VISUAL_TRUECOLOR;
+ break;
+ }
+
+ /* allocate fb memory within 501 */
+ info->fix.line_length = (var->xres_virtual * var->bits_per_pixel)/8;
+ info->fix.smem_len = info->fix.line_length * var->yres_virtual;
+
+ dev_dbg(fbi->dev, "%s: line length = %u\n", __func__,
+ info->fix.line_length);
+
+ if (sm501_alloc_mem(fbi, &par->screen, mem_type,
+ info->fix.smem_len)) {
+ dev_err(fbi->dev, "no memory available\n");
+ return -ENOMEM;
+ }
+
+ info->fix.smem_start = fbi->fbmem_res->start + par->screen.sm_addr;
+
+ info->screen_base = fbi->fbmem + par->screen.sm_addr;
+ info->screen_size = info->fix.smem_len;
+
+ /* set start of framebuffer to the screen */
+
+ writel(par->screen.sm_addr | SM501_ADDR_FLIP, fbi->regs + head_addr);
+
+ /* program CRT clock */
+
+ pixclock = sm501fb_ps_to_hz(var->pixclock);
+
+ sm501pixclock = sm501_set_clock(fbi->dev->parent, clock_type,
+ pixclock);
+
+ /* update fb layer with actual clock used */
+ var->pixclock = sm501fb_hz_to_ps(sm501pixclock);
+
+ dev_dbg(fbi->dev, "%s: pixclock(ps) = %u, pixclock(Hz) = %lu, "
+ "sm501pixclock = %lu, error = %ld%%\n",
+ __func__, var->pixclock, pixclock, sm501pixclock,
+ ((pixclock - sm501pixclock)*100)/pixclock);
+
+ return 0;
+}
+
+/* sm501fb_set_par_geometry
+ *
+ * set the geometry registers for specified framebuffer.
+*/
+
+static void sm501fb_set_par_geometry(struct fb_info *info,
+ struct fb_var_screeninfo *var)
+{
+ struct sm501fb_par *par = info->par;
+ struct sm501fb_info *fbi = par->info;
+ void __iomem *base = fbi->regs;
+ unsigned long reg;
+
+ if (par->head == HEAD_CRT)
+ base += SM501_DC_CRT_H_TOT;
+ else
+ base += SM501_DC_PANEL_H_TOT;
+
+ /* set framebuffer width and display width */
+
+ reg = info->fix.line_length;
+ reg |= ((var->xres * var->bits_per_pixel)/8) << 16;
+
+ writel(reg, fbi->regs + (par->head == HEAD_CRT ?
+ SM501_DC_CRT_FB_OFFSET : SM501_DC_PANEL_FB_OFFSET));
+
+ /* program horizontal total */
+
+ reg = (h_total(var) - 1) << 16;
+ reg |= (var->xres - 1);
+
+ writel(reg, base + SM501_OFF_DC_H_TOT);
+
+ /* program horizontal sync */
+
+ reg = var->hsync_len << 16;
+ reg |= var->xres + var->right_margin - 1;
+
+ writel(reg, base + SM501_OFF_DC_H_SYNC);
+
+ /* program vertical total */
+
+ reg = (v_total(var) - 1) << 16;
+ reg |= (var->yres - 1);
+
+ writel(reg, base + SM501_OFF_DC_V_TOT);
+
+ /* program vertical sync */
+ reg = var->vsync_len << 16;
+ reg |= var->yres + var->lower_margin - 1;
+
+ writel(reg, base + SM501_OFF_DC_V_SYNC);
+}
+
+/* sm501fb_pan_crt
+ *
+ * pan the CRT display output within an virtual framebuffer
+*/
+
+static int sm501fb_pan_crt(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct sm501fb_par *par = info->par;
+ struct sm501fb_info *fbi = par->info;
+ unsigned int bytes_pixel = var->bits_per_pixel / 8;
+ unsigned long reg;
+ unsigned long xoffs;
+
+ xoffs = var->xoffset * bytes_pixel;
+
+ reg = readl(fbi->regs + SM501_DC_CRT_CONTROL);
+
+ reg &= ~SM501_DC_CRT_CONTROL_PIXEL_MASK;
+ reg |= ((xoffs & 15) / bytes_pixel) << 4;
+ writel(reg, fbi->regs + SM501_DC_CRT_CONTROL);
+
+ reg = (par->screen.sm_addr + xoffs +
+ var->yoffset * info->fix.line_length);
+ writel(reg | SM501_ADDR_FLIP, fbi->regs + SM501_DC_CRT_FB_ADDR);
+
+ sm501fb_sync_regs(fbi);
+ return 0;
+}
+
+/* sm501fb_pan_pnl
+ *
+ * pan the panel display output within an virtual framebuffer
+*/
+
+static int sm501fb_pan_pnl(struct fb_var_screeninfo *var,
+ struct fb_info *info)
+{
+ struct sm501fb_par *par = info->par;
+ struct sm501fb_info *fbi = par->info;
+ unsigned long reg;
+
+ reg = var->xoffset | (var->xres_virtual << 16);
+ writel(reg, fbi->regs + SM501_DC_PANEL_FB_WIDTH);
+
+ reg = var->yoffset | (var->yres_virtual << 16);
+ writel(reg, fbi->regs + SM501_DC_PANEL_FB_HEIGHT);
+
+ sm501fb_sync_regs(fbi);
+ return 0;
+}
+
+/* sm501fb_set_par_crt
+ *
+ * Set the CRT video mode from the fb_info structure
This diff is incomplete. Download the complete patch.