From d6ff7d0fe22cdf3ea41c48b50da9a9181500d1bf Mon Sep 17 00:00:00 2001 From: Alexey Charkov Date: Tue, 9 Nov 2010 02:42:39 +0300 Subject: [PATCH 01/55] ARM: Add support for the display controllers in VT8500 and WM8505 This adds drivers for the LCD controller found in VIA VT8500 SoC, GOVR display controller found in WonderMedia WM8505 SoC and for the Graphics Engine present in both of them that provides hardware accelerated raster operations (used for copyarea and fillrect). Signed-off-by: Alexey Charkov Signed-off-by: Paul Mundt --- drivers/video/Kconfig | 26 ++ drivers/video/Makefile | 3 + drivers/video/vt8500lcdfb.c | 447 ++++++++++++++++++++++++++++++++++ drivers/video/vt8500lcdfb.h | 34 +++ drivers/video/wm8505fb.c | 422 ++++++++++++++++++++++++++++++++ drivers/video/wm8505fb_regs.h | 76 ++++++ drivers/video/wmt_ge_rops.c | 192 +++++++++++++++ drivers/video/wmt_ge_rops.h | 5 + 8 files changed, 1205 insertions(+) create mode 100644 drivers/video/vt8500lcdfb.c create mode 100644 drivers/video/vt8500lcdfb.h create mode 100644 drivers/video/wm8505fb.c create mode 100644 drivers/video/wm8505fb_regs.h create mode 100644 drivers/video/wmt_ge_rops.c create mode 100644 drivers/video/wmt_ge_rops.h diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 27c1fb4b1e0d..954f6e9d8d5a 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -186,6 +186,14 @@ config FB_SYS_FOPS depends on FB default n +config FB_WMT_GE_ROPS + tristate + depends on FB + default n + ---help--- + Include functions for accelerated rectangle filling and area + copying using WonderMedia Graphics Engine operations. + config FB_DEFERRED_IO bool depends on FB @@ -1722,6 +1730,24 @@ config FB_AU1200 various panels and CRTs by passing in kernel cmd line option au1200fb:panel=. +config FB_VT8500 + bool "VT8500 LCD Driver" + depends on (FB = y) && ARM && ARCH_VT8500 && VTWM_VERSION_VT8500 + select FB_WMT_GE_ROPS + select FB_SYS_IMAGEBLIT + help + This is the framebuffer driver for VIA VT8500 integrated LCD + controller. + +config FB_WM8505 + bool "WM8505 frame buffer support" + depends on (FB = y) && ARM && ARCH_VT8500 && VTWM_VERSION_WM8505 + select FB_WMT_GE_ROPS + select FB_SYS_IMAGEBLIT + help + This is the framebuffer driver for WonderMedia WM8505 + integrated LCD controller. + source "drivers/video/geode/Kconfig" config FB_HIT diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 485e8ed1318c..8d916dcb379f 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_FB_SVGALIB) += svgalib.o obj-$(CONFIG_FB_MACMODES) += macmodes.o obj-$(CONFIG_FB_DDC) += fb_ddc.o obj-$(CONFIG_FB_DEFERRED_IO) += fb_defio.o +obj-$(CONFIG_FB_WMT_GE_ROPS) += wmt_ge_rops.o # Hardware specific drivers go first obj-$(CONFIG_FB_AMIGA) += amifb.o c2p_planar.o @@ -104,6 +105,8 @@ obj-$(CONFIG_FB_W100) += w100fb.o obj-$(CONFIG_FB_TMIO) += tmiofb.o obj-$(CONFIG_FB_AU1100) += au1100fb.o obj-$(CONFIG_FB_AU1200) += au1200fb.o +obj-$(CONFIG_FB_VT8500) += vt8500lcdfb.o +obj-$(CONFIG_FB_WM8505) += wm8505fb.o obj-$(CONFIG_FB_PMAG_AA) += pmag-aa-fb.o obj-$(CONFIG_FB_PMAG_BA) += pmag-ba-fb.o obj-$(CONFIG_FB_PMAGB_B) += pmagb-b-fb.o diff --git a/drivers/video/vt8500lcdfb.c b/drivers/video/vt8500lcdfb.c new file mode 100644 index 000000000000..7617f12e4fd7 --- /dev/null +++ b/drivers/video/vt8500lcdfb.c @@ -0,0 +1,447 @@ +/* + * linux/drivers/video/vt8500lcdfb.c + * + * Copyright (C) 2010 Alexey Charkov + * + * Based on skeletonfb.c and pxafb.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "vt8500lcdfb.h" +#include "wmt_ge_rops.h" + +#define to_vt8500lcd_info(__info) container_of(__info, \ + struct vt8500lcd_info, fb) + +static int vt8500lcd_set_par(struct fb_info *info) +{ + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + int reg_bpp = 5; /* 16bpp */ + int i; + unsigned long control0; + + if (!fbi) + return -EINVAL; + + if (info->var.bits_per_pixel <= 8) { + /* palettized */ + info->var.red.offset = 0; + info->var.red.length = info->var.bits_per_pixel; + info->var.red.msb_right = 0; + + info->var.green.offset = 0; + info->var.green.length = info->var.bits_per_pixel; + info->var.green.msb_right = 0; + + info->var.blue.offset = 0; + info->var.blue.length = info->var.bits_per_pixel; + info->var.blue.msb_right = 0; + + info->var.transp.offset = 0; + info->var.transp.length = 0; + info->var.transp.msb_right = 0; + + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; + info->fix.line_length = info->var.xres_virtual / + (8/info->var.bits_per_pixel); + } else { + /* non-palettized */ + info->var.transp.offset = 0; + info->var.transp.length = 0; + info->var.transp.msb_right = 0; + + if (info->var.bits_per_pixel == 16) { + /* RGB565 */ + info->var.red.offset = 11; + info->var.red.length = 5; + info->var.red.msb_right = 0; + info->var.green.offset = 5; + info->var.green.length = 6; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 5; + info->var.blue.msb_right = 0; + } else { + /* Equal depths per channel */ + info->var.red.offset = info->var.bits_per_pixel + * 2 / 3; + info->var.red.length = info->var.bits_per_pixel / 3; + info->var.red.msb_right = 0; + info->var.green.offset = info->var.bits_per_pixel / 3; + info->var.green.length = info->var.bits_per_pixel / 3; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = info->var.bits_per_pixel / 3; + info->var.blue.msb_right = 0; + } + + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.bits_per_pixel > 16 ? + info->var.xres_virtual << 2 : + info->var.xres_virtual << 1; + } + + for (i = 0; i < 8; i++) { + if (bpp_values[i] == info->var.bits_per_pixel) { + reg_bpp = i; + continue; + } + } + + control0 = readl(fbi->regbase) & ~0xf; + writel(0, fbi->regbase); + while (readl(fbi->regbase + 0x38) & 0x10) + /* wait */; + writel((((info->var.hsync_len - 1) & 0x3f) << 26) + | ((info->var.left_margin & 0xff) << 18) + | (((info->var.xres - 1) & 0x3ff) << 8) + | (info->var.right_margin & 0xff), fbi->regbase + 0x4); + writel((((info->var.vsync_len - 1) & 0x3f) << 26) + | ((info->var.upper_margin & 0xff) << 18) + | (((info->var.yres - 1) & 0x3ff) << 8) + | (info->var.lower_margin & 0xff), fbi->regbase + 0x8); + writel((((info->var.yres - 1) & 0x400) << 2) + | ((info->var.xres - 1) & 0x400), fbi->regbase + 0x10); + writel(0x80000000, fbi->regbase + 0x20); + writel(control0 | (reg_bpp << 1) | 0x100, fbi->regbase); + + return 0; +} + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int vt8500lcd_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) { + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + int ret = 1; + unsigned int val; + if (regno >= 256) + return -EINVAL; + + if (info->var.grayscale) + red = green = blue = + (19595 * red + 38470 * green + 7471 * blue) >> 16; + + switch (fbi->fb.fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + u32 *pal = fbi->fb.pseudo_palette; + + val = chan_to_field(red, &fbi->fb.var.red); + val |= chan_to_field(green, &fbi->fb.var.green); + val |= chan_to_field(blue, &fbi->fb.var.blue); + + pal[regno] = val; + ret = 0; + } + break; + + case FB_VISUAL_STATIC_PSEUDOCOLOR: + case FB_VISUAL_PSEUDOCOLOR: + writew((red & 0xf800) + | ((green >> 5) & 0x7e0) + | ((blue >> 11) & 0x1f), + fbi->palette_cpu + sizeof(u16) * regno); + break; + } + + return ret; +} + +static int vt8500lcd_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + int ret = 0; + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + + if (cmd == FBIO_WAITFORVSYNC) { + /* Unmask End of Frame interrupt */ + writel(0xffffffff ^ (1 << 3), fbi->regbase + 0x3c); + ret = wait_event_interruptible_timeout(fbi->wait, + readl(fbi->regbase + 0x38) & (1 << 3), HZ / 10); + /* Mask back to reduce unwanted interrupt traffic */ + writel(0xffffffff, fbi->regbase + 0x3c); + if (ret < 0) + return ret; + if (ret == 0) + return -ETIMEDOUT; + } + + return ret; +} + +static int vt8500lcd_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + unsigned pixlen = info->fix.line_length / info->var.xres_virtual; + unsigned off = pixlen * var->xoffset + + info->fix.line_length * var->yoffset; + struct vt8500lcd_info *fbi = to_vt8500lcd_info(info); + + writel((1 << 31) + | (((var->xres_virtual - var->xres) * pixlen / 4) << 20) + | (off >> 2), fbi->regbase + 0x20); + return 0; +} + +static struct fb_ops vt8500lcd_ops = { + .owner = THIS_MODULE, + .fb_set_par = vt8500lcd_set_par, + .fb_setcolreg = vt8500lcd_setcolreg, + .fb_fillrect = wmt_ge_fillrect, + .fb_copyarea = wmt_ge_copyarea, + .fb_imageblit = sys_imageblit, + .fb_sync = wmt_ge_sync, + .fb_ioctl = vt8500lcd_ioctl, + .fb_pan_display = vt8500lcd_pan_display, +}; + +static irqreturn_t vt8500lcd_handle_irq(int irq, void *dev_id) +{ + struct vt8500lcd_info *fbi = dev_id; + + if (readl(fbi->regbase + 0x38) & (1 << 3)) + wake_up_interruptible(&fbi->wait); + + writel(0xffffffff, fbi->regbase + 0x38); + return IRQ_HANDLED; +} + +static int __devinit vt8500lcd_probe(struct platform_device *pdev) +{ + struct vt8500lcd_info *fbi; + struct resource *res; + struct vt8500fb_platform_data *pdata = pdev->dev.platform_data; + void *addr; + int irq, ret; + + ret = -ENOMEM; + fbi = NULL; + + fbi = kzalloc(sizeof(struct vt8500lcd_info) + sizeof(u32) * 16, + GFP_KERNEL); + if (!fbi) { + dev_err(&pdev->dev, "Failed to initialize framebuffer device\n"); + ret = -ENOMEM; + goto failed; + } + + strcpy(fbi->fb.fix.id, "VT8500 LCD"); + + fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fb.fix.xpanstep = 0; + fbi->fb.fix.ypanstep = 1; + fbi->fb.fix.ywrapstep = 0; + fbi->fb.fix.accel = FB_ACCEL_NONE; + + fbi->fb.var.nonstd = 0; + fbi->fb.var.activate = FB_ACTIVATE_NOW; + fbi->fb.var.height = -1; + fbi->fb.var.width = -1; + fbi->fb.var.vmode = FB_VMODE_NONINTERLACED; + + fbi->fb.fbops = &vt8500lcd_ops; + fbi->fb.flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_YPAN + | FBINFO_VIRTFB + | FBINFO_PARTIAL_PAN_OK; + fbi->fb.node = -1; + + addr = fbi; + addr = addr + sizeof(struct vt8500lcd_info); + fbi->fb.pseudo_palette = addr; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + ret = -ENODEV; + goto failed_fbi; + } + + res = request_mem_region(res->start, resource_size(res), "vt8500lcd"); + if (res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto failed_fbi; + } + + fbi->regbase = ioremap(res->start, resource_size(res)); + if (fbi->regbase == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + ret = -EBUSY; + goto failed_free_res; + } + + fbi->fb.fix.smem_start = pdata->video_mem_phys; + fbi->fb.fix.smem_len = pdata->video_mem_len; + fbi->fb.screen_base = pdata->video_mem_virt; + + fbi->palette_size = PAGE_ALIGN(512); + fbi->palette_cpu = dma_alloc_coherent(&pdev->dev, + fbi->palette_size, + &fbi->palette_phys, + GFP_KERNEL); + if (fbi->palette_cpu == NULL) { + dev_err(&pdev->dev, "Failed to allocate palette buffer\n"); + ret = -ENOMEM; + goto failed_free_io; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "no IRQ defined\n"); + ret = -ENODEV; + goto failed_free_palette; + } + + ret = request_irq(irq, vt8500lcd_handle_irq, IRQF_DISABLED, "LCD", fbi); + if (ret) { + dev_err(&pdev->dev, "request_irq failed: %d\n", ret); + ret = -EBUSY; + goto failed_free_palette; + } + + init_waitqueue_head(&fbi->wait); + + if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) { + dev_err(&pdev->dev, "Failed to allocate color map\n"); + ret = -ENOMEM; + goto failed_free_irq; + } + + fb_videomode_to_var(&fbi->fb.var, &pdata->mode); + fbi->fb.var.bits_per_pixel = pdata->bpp; + fbi->fb.var.xres_virtual = pdata->xres_virtual; + fbi->fb.var.yres_virtual = pdata->yres_virtual; + + ret = vt8500lcd_set_par(&fbi->fb); + if (ret) { + dev_err(&pdev->dev, "Failed to set parameters\n"); + goto failed_free_cmap; + } + + writel(fbi->fb.fix.smem_start >> 22, fbi->regbase + 0x1c); + writel((fbi->palette_phys & 0xfffffe00) | 1, fbi->regbase + 0x18); + + platform_set_drvdata(pdev, fbi); + + ret = register_framebuffer(&fbi->fb); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register framebuffer device: %d\n", ret); + goto failed_free_cmap; + } + + /* + * Ok, now enable the LCD controller + */ + writel(readl(fbi->regbase) | 1, fbi->regbase); + + return 0; + +failed_free_cmap: + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); +failed_free_irq: + free_irq(irq, fbi); +failed_free_palette: + dma_free_coherent(&pdev->dev, fbi->palette_size, + fbi->palette_cpu, fbi->palette_phys); +failed_free_io: + iounmap(fbi->regbase); +failed_free_res: + release_mem_region(res->start, resource_size(res)); +failed_fbi: + platform_set_drvdata(pdev, NULL); + kfree(fbi); +failed: + return ret; +} + +static int __devexit vt8500lcd_remove(struct platform_device *pdev) +{ + struct vt8500lcd_info *fbi = platform_get_drvdata(pdev); + struct resource *res; + int irq; + + unregister_framebuffer(&fbi->fb); + + writel(0, fbi->regbase); + + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); + + irq = platform_get_irq(pdev, 0); + free_irq(irq, fbi); + + dma_free_coherent(&pdev->dev, fbi->palette_size, + fbi->palette_cpu, fbi->palette_phys); + + iounmap(fbi->regbase); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(fbi); + + return 0; +} + +static struct platform_driver vt8500lcd_driver = { + .probe = vt8500lcd_probe, + .remove = __devexit_p(vt8500lcd_remove), + .driver = { + .owner = THIS_MODULE, + .name = "vt8500-lcd", + }, +}; + +static int __init vt8500lcd_init(void) +{ + return platform_driver_register(&vt8500lcd_driver); +} + +static void __exit vt8500lcd_exit(void) +{ + platform_driver_unregister(&vt8500lcd_driver); +} + +module_init(vt8500lcd_init); +module_exit(vt8500lcd_exit); + +MODULE_AUTHOR("Alexey Charkov "); +MODULE_DESCRIPTION("LCD controller driver for VIA VT8500"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/vt8500lcdfb.h b/drivers/video/vt8500lcdfb.h new file mode 100644 index 000000000000..36ca3ca09d83 --- /dev/null +++ b/drivers/video/vt8500lcdfb.h @@ -0,0 +1,34 @@ +/* + * linux/drivers/video/vt8500lcdfb.h + * + * Copyright (C) 2010 Alexey Charkov + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +struct vt8500lcd_info { + struct fb_info fb; + void __iomem *regbase; + void __iomem *palette_cpu; + dma_addr_t palette_phys; + size_t palette_size; + wait_queue_head_t wait; +}; + +static int bpp_values[] = { + 1, + 2, + 4, + 8, + 12, + 16, + 18, + 24, +}; diff --git a/drivers/video/wm8505fb.c b/drivers/video/wm8505fb.c new file mode 100644 index 000000000000..e37251b792c9 --- /dev/null +++ b/drivers/video/wm8505fb.c @@ -0,0 +1,422 @@ +/* + * WonderMedia WM8505 Frame Buffer device driver + * + * Copyright (C) 2010 Ed Spiridonov + * Based on vt8500lcdfb.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "wm8505fb_regs.h" +#include "wmt_ge_rops.h" + +#define DRIVER_NAME "wm8505-fb" + +#define to_wm8505fb_info(__info) container_of(__info, \ + struct wm8505fb_info, fb) +struct wm8505fb_info { + struct fb_info fb; + void __iomem *regbase; + unsigned int contrast; +}; + + +static int wm8505fb_init_hw(struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + int i; + + /* I know the purpose only of few registers, so clear unknown */ + for (i = 0; i < 0x200; i += 4) + writel(0, fbi->regbase + i); + + /* Set frame buffer address */ + writel(fbi->fb.fix.smem_start, fbi->regbase + WMT_GOVR_FBADDR); + writel(fbi->fb.fix.smem_start, fbi->regbase + WMT_GOVR_FBADDR1); + + /* Set in-memory picture format to RGB 32bpp */ + writel(0x1c, fbi->regbase + WMT_GOVR_COLORSPACE); + writel(1, fbi->regbase + WMT_GOVR_COLORSPACE1); + + /* Virtual buffer size */ + writel(info->var.xres, fbi->regbase + WMT_GOVR_XRES); + writel(info->var.xres_virtual, fbi->regbase + WMT_GOVR_XRES_VIRTUAL); + + /* black magic ;) */ + writel(0xf, fbi->regbase + WMT_GOVR_FHI); + writel(4, fbi->regbase + WMT_GOVR_DVO_SET); + writel(1, fbi->regbase + WMT_GOVR_MIF_ENABLE); + writel(1, fbi->regbase + WMT_GOVR_REG_UPDATE); + + return 0; +} + +static int wm8505fb_set_timing(struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + int h_start = info->var.left_margin; + int h_end = h_start + info->var.xres; + int h_all = h_end + info->var.right_margin; + int h_sync = info->var.hsync_len; + + int v_start = info->var.upper_margin; + int v_end = v_start + info->var.yres; + int v_all = v_end + info->var.lower_margin; + int v_sync = info->var.vsync_len + 1; + + writel(0, fbi->regbase + WMT_GOVR_TG); + + writel(h_start, fbi->regbase + WMT_GOVR_TIMING_H_START); + writel(h_end, fbi->regbase + WMT_GOVR_TIMING_H_END); + writel(h_all, fbi->regbase + WMT_GOVR_TIMING_H_ALL); + writel(h_sync, fbi->regbase + WMT_GOVR_TIMING_H_SYNC); + + writel(v_start, fbi->regbase + WMT_GOVR_TIMING_V_START); + writel(v_end, fbi->regbase + WMT_GOVR_TIMING_V_END); + writel(v_all, fbi->regbase + WMT_GOVR_TIMING_V_ALL); + writel(v_sync, fbi->regbase + WMT_GOVR_TIMING_V_SYNC); + + writel(1, fbi->regbase + WMT_GOVR_TG); + + return 0; +} + + +static int wm8505fb_set_par(struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + if (!fbi) + return -EINVAL; + + if (info->var.bits_per_pixel == 32) { + info->var.red.offset = 16; + info->var.red.length = 8; + info->var.red.msb_right = 0; + info->var.green.offset = 8; + info->var.green.length = 8; + info->var.green.msb_right = 0; + info->var.blue.offset = 0; + info->var.blue.length = 8; + info->var.blue.msb_right = 0; + info->fix.visual = FB_VISUAL_TRUECOLOR; + info->fix.line_length = info->var.xres_virtual << 2; + } + + wm8505fb_set_timing(info); + + writel(fbi->contrast<<16 | fbi->contrast<<8 | fbi->contrast, + fbi->regbase + WMT_GOVR_CONTRAST); + + return 0; +} + +static ssize_t contrast_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + return sprintf(buf, "%d\n", fbi->contrast); +} + +static ssize_t contrast_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fb_info *info = dev_get_drvdata(dev); + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + unsigned long tmp; + + if (strict_strtoul(buf, 10, &tmp) || (tmp > 0xff)) + return -EINVAL; + fbi->contrast = tmp; + + wm8505fb_set_par(info); + + return count; +} + +static DEVICE_ATTR(contrast, 0644, contrast_show, contrast_store); + +static inline u_int chan_to_field(u_int chan, struct fb_bitfield *bf) +{ + chan &= 0xffff; + chan >>= 16 - bf->length; + return chan << bf->offset; +} + +static int wm8505fb_setcolreg(unsigned regno, unsigned red, unsigned green, + unsigned blue, unsigned transp, + struct fb_info *info) { + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + int ret = 1; + unsigned int val; + if (regno >= 256) + return -EINVAL; + + if (info->var.grayscale) + red = green = blue = + (19595 * red + 38470 * green + 7471 * blue) >> 16; + + switch (fbi->fb.fix.visual) { + case FB_VISUAL_TRUECOLOR: + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + val = chan_to_field(red, &fbi->fb.var.red); + val |= chan_to_field(green, &fbi->fb.var.green); + val |= chan_to_field(blue, &fbi->fb.var.blue); + + pal[regno] = val; + ret = 0; + } + break; + } + + return ret; +} + +static int wm8505fb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + writel(var->xoffset, fbi->regbase + WMT_GOVR_XPAN); + writel(var->yoffset, fbi->regbase + WMT_GOVR_YPAN); + return 0; +} + +static int wm8505fb_blank(int blank, struct fb_info *info) +{ + struct wm8505fb_info *fbi = to_wm8505fb_info(info); + + switch (blank) { + case FB_BLANK_UNBLANK: + wm8505fb_set_timing(info); + break; + default: + writel(0, fbi->regbase + WMT_GOVR_TIMING_V_SYNC); + break; + } + + return 0; +} + +static struct fb_ops wm8505fb_ops = { + .owner = THIS_MODULE, + .fb_set_par = wm8505fb_set_par, + .fb_setcolreg = wm8505fb_setcolreg, + .fb_fillrect = wmt_ge_fillrect, + .fb_copyarea = wmt_ge_copyarea, + .fb_imageblit = sys_imageblit, + .fb_sync = wmt_ge_sync, + .fb_pan_display = wm8505fb_pan_display, + .fb_blank = wm8505fb_blank, +}; + +static int __devinit wm8505fb_probe(struct platform_device *pdev) +{ + struct wm8505fb_info *fbi; + struct resource *res; + void *addr; + struct vt8500fb_platform_data *pdata; + int ret; + + pdata = pdev->dev.platform_data; + + ret = -ENOMEM; + fbi = NULL; + + fbi = kzalloc(sizeof(struct wm8505fb_info) + sizeof(u32) * 16, + GFP_KERNEL); + if (!fbi) { + dev_err(&pdev->dev, "Failed to initialize framebuffer device\n"); + ret = -ENOMEM; + goto failed; + } + + strcpy(fbi->fb.fix.id, DRIVER_NAME); + + fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS; + fbi->fb.fix.xpanstep = 1; + fbi->fb.fix.ypanstep = 1; + fbi->fb.fix.ywrapstep = 0; + fbi->fb.fix.accel = FB_ACCEL_NONE; + + fbi->fb.fbops = &wm8505fb_ops; + fbi->fb.flags = FBINFO_DEFAULT + | FBINFO_HWACCEL_COPYAREA + | FBINFO_HWACCEL_FILLRECT + | FBINFO_HWACCEL_XPAN + | FBINFO_HWACCEL_YPAN + | FBINFO_VIRTFB + | FBINFO_PARTIAL_PAN_OK; + fbi->fb.node = -1; + + addr = fbi; + addr = addr + sizeof(struct wm8505fb_info); + fbi->fb.pseudo_palette = addr; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + ret = -ENODEV; + goto failed_fbi; + } + + res = request_mem_region(res->start, resource_size(res), "wm8505fb"); + if (res == NULL) { + dev_err(&pdev->dev, "failed to request I/O memory\n"); + ret = -EBUSY; + goto failed_fbi; + } + + fbi->regbase = ioremap(res->start, resource_size(res)); + if (fbi->regbase == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + ret = -EBUSY; + goto failed_free_res; + } + + fb_videomode_to_var(&fbi->fb.var, &pdata->mode); + + fbi->fb.var.nonstd = 0; + fbi->fb.var.activate = FB_ACTIVATE_NOW; + + fbi->fb.var.height = -1; + fbi->fb.var.width = -1; + fbi->fb.var.xres_virtual = pdata->xres_virtual; + fbi->fb.var.yres_virtual = pdata->yres_virtual; + fbi->fb.var.bits_per_pixel = pdata->bpp; + + fbi->fb.fix.smem_start = pdata->video_mem_phys; + fbi->fb.fix.smem_len = pdata->video_mem_len; + fbi->fb.screen_base = pdata->video_mem_virt; + fbi->fb.screen_size = pdata->video_mem_len; + + if (fb_alloc_cmap(&fbi->fb.cmap, 256, 0) < 0) { + dev_err(&pdev->dev, "Failed to allocate color map\n"); + ret = -ENOMEM; + goto failed_free_io; + } + + wm8505fb_init_hw(&fbi->fb); + + fbi->contrast = 0x80; + ret = wm8505fb_set_par(&fbi->fb); + if (ret) { + dev_err(&pdev->dev, "Failed to set parameters\n"); + goto failed_free_cmap; + } + + platform_set_drvdata(pdev, fbi); + + ret = register_framebuffer(&fbi->fb); + if (ret < 0) { + dev_err(&pdev->dev, + "Failed to register framebuffer device: %d\n", ret); + goto failed_free_cmap; + } + + ret = device_create_file(&pdev->dev, &dev_attr_contrast); + if (ret < 0) { + printk(KERN_WARNING "fb%d: failed to register attributes (%d)\n", + fbi->fb.node, ret); + } + + printk(KERN_INFO "fb%d: %s frame buffer at 0x%lx-0x%lx\n", + fbi->fb.node, fbi->fb.fix.id, fbi->fb.fix.smem_start, + fbi->fb.fix.smem_start + fbi->fb.fix.smem_len - 1); + + return 0; + +failed_free_cmap: + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); +failed_free_io: + iounmap(fbi->regbase); +failed_free_res: + release_mem_region(res->start, resource_size(res)); +failed_fbi: + platform_set_drvdata(pdev, NULL); + kfree(fbi); +failed: + return ret; +} + +static int __devexit wm8505fb_remove(struct platform_device *pdev) +{ + struct wm8505fb_info *fbi = platform_get_drvdata(pdev); + struct resource *res; + + device_remove_file(&pdev->dev, &dev_attr_contrast); + + unregister_framebuffer(&fbi->fb); + + writel(0, fbi->regbase); + + if (fbi->fb.cmap.len) + fb_dealloc_cmap(&fbi->fb.cmap); + + iounmap(fbi->regbase); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(res->start, resource_size(res)); + + kfree(fbi); + + return 0; +} + +static struct platform_driver wm8505fb_driver = { + .probe = wm8505fb_probe, + .remove = __devexit_p(wm8505fb_remove), + .driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + }, +}; + +static int __init wm8505fb_init(void) +{ + return platform_driver_register(&wm8505fb_driver); +} + +static void __exit wm8505fb_exit(void) +{ + platform_driver_unregister(&wm8505fb_driver); +} + +module_init(wm8505fb_init); +module_exit(wm8505fb_exit); + +MODULE_AUTHOR("Ed Spiridonov "); +MODULE_DESCRIPTION("Framebuffer driver for WMT WM8505"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/wm8505fb_regs.h b/drivers/video/wm8505fb_regs.h new file mode 100644 index 000000000000..4dd41668c6d1 --- /dev/null +++ b/drivers/video/wm8505fb_regs.h @@ -0,0 +1,76 @@ +/* + * GOVR registers list for WM8505 chips + * + * Copyright (C) 2010 Ed Spiridonov + * Based on VIA/WonderMedia wm8510-govrh-reg.h + * http://github.com/projectgus/kernel_wm8505/blob/wm8505_2.6.29/ + * drivers/video/wmt/register/wm8510/wm8510-govrh-reg.h + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef _WM8505FB_REGS_H +#define _WM8505FB_REGS_H + +/* + * Color space select register, default value 0x1c + * BIT0 GOVRH_DVO_YUV2RGB_ENABLE + * BIT1 GOVRH_VGA_YUV2RGB_ENABLE + * BIT2 GOVRH_RGB_MODE + * BIT3 GOVRH_DAC_CLKINV + * BIT4 GOVRH_BLANK_ZERO + */ +#define WMT_GOVR_COLORSPACE 0x1e4 +/* + * Another colorspace select register, default value 1 + * BIT0 GOVRH_DVO_RGB + * BIT1 GOVRH_DVO_YUV422 + */ +#define WMT_GOVR_COLORSPACE1 0x30 + +#define WMT_GOVR_CONTRAST 0x1b8 +#define WMT_GOVR_BRGHTNESS 0x1bc /* incompatible with RGB? */ + +/* Framubeffer address */ +#define WMT_GOVR_FBADDR 0x90 +#define WMT_GOVR_FBADDR1 0x94 /* UV offset in YUV mode */ + +/* Offset of visible window */ +#define WMT_GOVR_XPAN 0xa4 +#define WMT_GOVR_YPAN 0xa0 + +#define WMT_GOVR_XRES 0x98 +#define WMT_GOVR_XRES_VIRTUAL 0x9c + +#define WMT_GOVR_MIF_ENABLE 0x80 +#define WMT_GOVR_FHI 0xa8 +#define WMT_GOVR_REG_UPDATE 0xe4 + +/* + * BIT0 GOVRH_DVO_OUTWIDTH + * BIT1 GOVRH_DVO_SYNC_POLAR + * BIT2 GOVRH_DVO_ENABLE + */ +#define WMT_GOVR_DVO_SET 0x148 + +/* Timing generator? */ +#define WMT_GOVR_TG 0x100 + +/* Timings */ +#define WMT_GOVR_TIMING_H_ALL 0x108 +#define WMT_GOVR_TIMING_V_ALL 0x10c +#define WMT_GOVR_TIMING_V_START 0x110 +#define WMT_GOVR_TIMING_V_END 0x114 +#define WMT_GOVR_TIMING_H_START 0x118 +#define WMT_GOVR_TIMING_H_END 0x11c +#define WMT_GOVR_TIMING_V_SYNC 0x128 +#define WMT_GOVR_TIMING_H_SYNC 0x12c + +#endif /* _WM8505FB_REGS_H */ diff --git a/drivers/video/wmt_ge_rops.c b/drivers/video/wmt_ge_rops.c new file mode 100644 index 000000000000..f31883f8eaf7 --- /dev/null +++ b/drivers/video/wmt_ge_rops.c @@ -0,0 +1,192 @@ +/* + * linux/drivers/video/wmt_ge_rops.c + * + * Accelerators for raster operations using WonderMedia Graphics Engine + * + * Copyright (C) 2010 Alexey Charkov + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include "fb_draw.h" + +#define GE_COMMAND_OFF 0x00 +#define GE_DEPTH_OFF 0x04 +#define GE_HIGHCOLOR_OFF 0x08 +#define GE_ROPCODE_OFF 0x14 +#define GE_FIRE_OFF 0x18 +#define GE_SRCBASE_OFF 0x20 +#define GE_SRCDISPW_OFF 0x24 +#define GE_SRCDISPH_OFF 0x28 +#define GE_SRCAREAX_OFF 0x2c +#define GE_SRCAREAY_OFF 0x30 +#define GE_SRCAREAW_OFF 0x34 +#define GE_SRCAREAH_OFF 0x38 +#define GE_DESTBASE_OFF 0x3c +#define GE_DESTDISPW_OFF 0x40 +#define GE_DESTDISPH_OFF 0x44 +#define GE_DESTAREAX_OFF 0x48 +#define GE_DESTAREAY_OFF 0x4c +#define GE_DESTAREAW_OFF 0x50 +#define GE_DESTAREAH_OFF 0x54 +#define GE_PAT0C_OFF 0x88 /* Pattern 0 color */ +#define GE_ENABLE_OFF 0xec +#define GE_INTEN_OFF 0xf0 +#define GE_STATUS_OFF 0xf8 + +static void __iomem *regbase; + +void wmt_ge_fillrect(struct fb_info *p, const struct fb_fillrect *rect) +{ + unsigned long fg, pat; + + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fix.visual == FB_VISUAL_TRUECOLOR || + p->fix.visual == FB_VISUAL_DIRECTCOLOR) + fg = ((u32 *) (p->pseudo_palette))[rect->color]; + else + fg = rect->color; + + pat = pixel_to_pat(p->var.bits_per_pixel, fg); + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + writel(p->var.bits_per_pixel == 32 ? 3 : + (p->var.bits_per_pixel == 8 ? 0 : 1), regbase + GE_DEPTH_OFF); + writel(p->var.bits_per_pixel == 15 ? 1 : 0, regbase + GE_HIGHCOLOR_OFF); + writel(p->fix.smem_start, regbase + GE_DESTBASE_OFF); + writel(p->var.xres_virtual - 1, regbase + GE_DESTDISPW_OFF); + writel(p->var.yres_virtual - 1, regbase + GE_DESTDISPH_OFF); + writel(rect->dx, regbase + GE_DESTAREAX_OFF); + writel(rect->dy, regbase + GE_DESTAREAY_OFF); + writel(rect->width - 1, regbase + GE_DESTAREAW_OFF); + writel(rect->height - 1, regbase + GE_DESTAREAH_OFF); + + writel(pat, regbase + GE_PAT0C_OFF); + writel(1, regbase + GE_COMMAND_OFF); + writel(rect->rop == ROP_XOR ? 0x5a : 0xf0, regbase + GE_ROPCODE_OFF); + writel(1, regbase + GE_FIRE_OFF); +} +EXPORT_SYMBOL_GPL(wmt_ge_fillrect); + +void wmt_ge_copyarea(struct fb_info *p, const struct fb_copyarea *area) +{ + if (p->state != FBINFO_STATE_RUNNING) + return; + + if (p->fbops->fb_sync) + p->fbops->fb_sync(p); + + writel(p->var.bits_per_pixel > 16 ? 3 : + (p->var.bits_per_pixel > 8 ? 1 : 0), regbase + GE_DEPTH_OFF); + + writel(p->fix.smem_start, regbase + GE_SRCBASE_OFF); + writel(p->var.xres_virtual - 1, regbase + GE_SRCDISPW_OFF); + writel(p->var.yres_virtual - 1, regbase + GE_SRCDISPH_OFF); + writel(area->sx, regbase + GE_SRCAREAX_OFF); + writel(area->sy, regbase + GE_SRCAREAY_OFF); + writel(area->width - 1, regbase + GE_SRCAREAW_OFF); + writel(area->height - 1, regbase + GE_SRCAREAH_OFF); + + writel(p->fix.smem_start, regbase + GE_DESTBASE_OFF); + writel(p->var.xres_virtual - 1, regbase + GE_DESTDISPW_OFF); + writel(p->var.yres_virtual - 1, regbase + GE_DESTDISPH_OFF); + writel(area->dx, regbase + GE_DESTAREAX_OFF); + writel(area->dy, regbase + GE_DESTAREAY_OFF); + writel(area->width - 1, regbase + GE_DESTAREAW_OFF); + writel(area->height - 1, regbase + GE_DESTAREAH_OFF); + + writel(0xcc, regbase + GE_ROPCODE_OFF); + writel(1, regbase + GE_COMMAND_OFF); + writel(1, regbase + GE_FIRE_OFF); +} +EXPORT_SYMBOL_GPL(wmt_ge_copyarea); + +int wmt_ge_sync(struct fb_info *p) +{ + int loops = 5000000; + while ((readl(regbase + GE_STATUS_OFF) & 4) && --loops) + cpu_relax(); + return loops > 0 ? 0 : -EBUSY; +} +EXPORT_SYMBOL_GPL(wmt_ge_sync); + +static int __devinit wmt_ge_rops_probe(struct platform_device *pdev) +{ + struct resource *res; + int ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + dev_err(&pdev->dev, "no I/O memory resource defined\n"); + ret = -ENODEV; + goto error; + } + + /* Only one ROP engine is presently supported. */ + if (unlikely(regbase)) { + WARN_ON(1); + return -EBUSY; + } + + regbase = ioremap(res->start, resource_size(res)); + if (regbase == NULL) { + dev_err(&pdev->dev, "failed to map I/O memory\n"); + ret = -EBUSY; + goto error; + } + + writel(1, regbase + GE_ENABLE_OFF); + printk(KERN_INFO "Enabled support for WMT GE raster acceleration\n"); + + return 0; + +error: + return ret; +} + +static int __devexit wmt_ge_rops_remove(struct platform_device *pdev) +{ + iounmap(regbase); + return 0; +} + +static struct platform_driver wmt_ge_rops_driver = { + .probe = wmt_ge_rops_probe, + .remove = __devexit_p(wmt_ge_rops_remove), + .driver = { + .owner = THIS_MODULE, + .name = "wmt_ge_rops", + }, +}; + +static int __init wmt_ge_rops_init(void) +{ + return platform_driver_register(&wmt_ge_rops_driver); +} + +static void __exit wmt_ge_rops_exit(void) +{ + platform_driver_unregister(&wmt_ge_rops_driver); +} + +module_init(wmt_ge_rops_init); +module_exit(wmt_ge_rops_exit); + +MODULE_AUTHOR("Alexey Charkov Date: Thu, 28 Oct 2010 18:23:01 +0100 Subject: [PATCH 02/55] viafb: General power management infrastructure Multiple devices need S/R hooks (framebuffer, GPIO, camera). Add infrastructure and convert existing framebuffer code to the new model. This patch should create no functional change. Based on earlier work by Jonathan Corbet. Signed-off-by: Daniel Drake Acked-by: Jonathan Corbet Signed-off-by: Florian Tobias Schandinat --- drivers/video/via/via-core.c | 79 +++++++++++++++++++++++++++++++++++- drivers/video/via/viafbdev.c | 34 +++++++--------- drivers/video/via/viafbdev.h | 2 - include/linux/via-core.h | 15 +++++++ 4 files changed, 107 insertions(+), 23 deletions(-) diff --git a/drivers/video/via/via-core.c b/drivers/video/via/via-core.c index 31e30338e893..42be3d955887 100644 --- a/drivers/video/via/via-core.c +++ b/drivers/video/via/via-core.c @@ -15,6 +15,8 @@ #include #include #include +#include +#include /* * The default port config. @@ -563,6 +565,78 @@ static void via_teardown_subdevs(void) } } +/* + * Power management functions + */ +#ifdef CONFIG_PM +static LIST_HEAD(viafb_pm_hooks); +static DEFINE_MUTEX(viafb_pm_hooks_lock); + +void viafb_pm_register(struct viafb_pm_hooks *hooks) +{ + INIT_LIST_HEAD(&hooks->list); + + mutex_lock(&viafb_pm_hooks_lock); + list_add_tail(&hooks->list, &viafb_pm_hooks); + mutex_unlock(&viafb_pm_hooks_lock); +} +EXPORT_SYMBOL_GPL(viafb_pm_register); + +void viafb_pm_unregister(struct viafb_pm_hooks *hooks) +{ + mutex_lock(&viafb_pm_hooks_lock); + list_del(&hooks->list); + mutex_unlock(&viafb_pm_hooks_lock); +} +EXPORT_SYMBOL_GPL(viafb_pm_unregister); + +static int via_suspend(struct pci_dev *pdev, pm_message_t state) +{ + struct viafb_pm_hooks *hooks; + + if (state.event != PM_EVENT_SUSPEND) + return 0; + /* + * "I've occasionally hit a few drivers that caused suspend + * failures, and each and every time it was a driver bug, and + * the right thing to do was to just ignore the error and suspend + * anyway - returning an error code and trying to undo the suspend + * is not what anybody ever really wants, even if our model + *_allows_ for it." + * -- Linus Torvalds, Dec. 7, 2009 + */ + mutex_lock(&viafb_pm_hooks_lock); + list_for_each_entry_reverse(hooks, &viafb_pm_hooks, list) + hooks->suspend(hooks->private); + mutex_unlock(&viafb_pm_hooks_lock); + + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, pci_choose_state(pdev, state)); + return 0; +} + +static int via_resume(struct pci_dev *pdev) +{ + struct viafb_pm_hooks *hooks; + + /* Get the bus side powered up */ + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + if (pci_enable_device(pdev)) + return 0; + + pci_set_master(pdev); + + /* Now bring back any subdevs */ + mutex_lock(&viafb_pm_hooks_lock); + list_for_each_entry(hooks, &viafb_pm_hooks, list) + hooks->resume(hooks->private); + mutex_unlock(&viafb_pm_hooks_lock); + + return 0; +} +#endif /* CONFIG_PM */ static int __devinit via_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) @@ -572,6 +646,7 @@ static int __devinit via_pci_probe(struct pci_dev *pdev, ret = pci_enable_device(pdev); if (ret) return ret; + /* * Global device initialization. */ @@ -651,8 +726,8 @@ static struct pci_driver via_driver = { .probe = via_pci_probe, .remove = __devexit_p(via_pci_remove), #ifdef CONFIG_PM - .suspend = viafb_suspend, - .resume = viafb_resume, + .suspend = via_suspend, + .resume = via_resume, #endif }; diff --git a/drivers/video/via/viafbdev.c b/drivers/video/via/viafbdev.c index d298cfccd6fc..289edd519527 100644 --- a/drivers/video/via/viafbdev.c +++ b/drivers/video/via/viafbdev.c @@ -1672,31 +1672,19 @@ static int parse_mode(const char *str, u32 *xres, u32 *yres) #ifdef CONFIG_PM -int viafb_suspend(struct pci_dev *pdev, pm_message_t state) +static int viafb_suspend(void *unused) { - if (state.event == PM_EVENT_SUSPEND) { - acquire_console_sem(); - fb_set_suspend(viafbinfo, 1); - - viafb_sync(viafbinfo); - - pci_save_state(pdev); - pci_disable_device(pdev); - pci_set_power_state(pdev, pci_choose_state(pdev, state)); - release_console_sem(); - } + acquire_console_sem(); + fb_set_suspend(viafbinfo, 1); + viafb_sync(viafbinfo); + release_console_sem(); return 0; } -int viafb_resume(struct pci_dev *pdev) +static int viafb_resume(void *unused) { acquire_console_sem(); - pci_set_power_state(pdev, PCI_D0); - pci_restore_state(pdev); - if (pci_enable_device(pdev)) - goto fail; - pci_set_master(pdev); if (viaparinfo->shared->vdev->engine_mmio) viafb_reset_engine(viaparinfo); viafb_set_par(viafbinfo); @@ -1704,11 +1692,15 @@ int viafb_resume(struct pci_dev *pdev) viafb_set_par(viafbinfo1); fb_set_suspend(viafbinfo, 0); -fail: release_console_sem(); return 0; } +static struct viafb_pm_hooks viafb_fb_pm_hooks = { + .suspend = viafb_suspend, + .resume = viafb_resume +}; + #endif @@ -1899,6 +1891,10 @@ int __devinit via_fb_pci_probe(struct viafb_dev *vdev) viafb_init_proc(viaparinfo->shared); viafb_init_dac(IGA2); + +#ifdef CONFIG_PM + viafb_pm_register(&viafb_fb_pm_hooks); +#endif return 0; out_fb_unreg: diff --git a/drivers/video/via/viafbdev.h b/drivers/video/via/viafbdev.h index 4960e3da6645..d66f963e930e 100644 --- a/drivers/video/via/viafbdev.h +++ b/drivers/video/via/viafbdev.h @@ -108,6 +108,4 @@ void via_fb_pci_remove(struct pci_dev *pdev); /* Temporary */ int viafb_init(void); void viafb_exit(void); -int viafb_suspend(struct pci_dev *pdev, pm_message_t state); -int viafb_resume(struct pci_dev *pdev); #endif /* __VIAFBDEV_H__ */ diff --git a/include/linux/via-core.h b/include/linux/via-core.h index 7ffb521e1a7a..a4327a0c8efc 100644 --- a/include/linux/via-core.h +++ b/include/linux/via-core.h @@ -59,6 +59,21 @@ struct via_port_cfg { u8 ioport_index; }; +/* + * Allow subdevs to register suspend/resume hooks. + */ +#ifdef CONFIG_PM +struct viafb_pm_hooks { + struct list_head list; + int (*suspend)(void *private); + int (*resume)(void *private); + void *private; +}; + +void viafb_pm_register(struct viafb_pm_hooks *hooks); +void viafb_pm_unregister(struct viafb_pm_hooks *hooks); +#endif /* CONFIG_PM */ + /* * This is the global viafb "device" containing stuff needed by * all subdevs. From 67eb6f9617a24dfb033b584d6b1b42b39cc9297a Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Thu, 28 Oct 2010 18:23:21 +0100 Subject: [PATCH 03/55] viafb: suspend/resume for GPIOs Be sure to re-enable GPIO lines on resume. Users still have to be sure to set them properly. Signed-off-by: Jonathan Corbet Signed-off-by: Daniel Drake Signed-off-by: Florian Tobias Schandinat --- drivers/video/via/via-gpio.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/video/via/via-gpio.c b/drivers/video/via/via-gpio.c index 39acb37e7a1d..c2a0a1cfd3b3 100644 --- a/drivers/video/via/via-gpio.c +++ b/drivers/video/via/via-gpio.c @@ -172,6 +172,28 @@ static void viafb_gpio_disable(struct viafb_gpio *gpio) via_write_reg_mask(VIASR, gpio->vg_port_index, 0, 0x02); } +#ifdef CONFIG_PM + +static int viafb_gpio_suspend(void *private) +{ + return 0; +} + +static int viafb_gpio_resume(void *private) +{ + int i; + + for (i = 0; i < gpio_config.gpio_chip.ngpio; i += 2) + viafb_gpio_enable(gpio_config.active_gpios[i]); + return 0; +} + +static struct viafb_pm_hooks viafb_gpio_pm_hooks = { + .suspend = viafb_gpio_suspend, + .resume = viafb_gpio_resume +}; +#endif /* CONFIG_PM */ + /* * Look up a specific gpio and return the number it was assigned. */ @@ -236,6 +258,9 @@ static __devinit int viafb_gpio_probe(struct platform_device *platdev) printk(KERN_ERR "viafb: failed to add gpios (%d)\n", ret); gpio_config.gpio_chip.ngpio = 0; } +#ifdef CONFIG_PM + viafb_pm_register(&viafb_gpio_pm_hooks); +#endif return ret; } @@ -245,6 +270,10 @@ static int viafb_gpio_remove(struct platform_device *platdev) unsigned long flags; int ret = 0, i; +#ifdef CONFIG_PM + viafb_pm_unregister(&viafb_gpio_pm_hooks); +#endif + /* * Get unregistered. */ From b884a94ed16d1a633e76f80fb1bd75d0e7373ce3 Mon Sep 17 00:00:00 2001 From: Daniel Drake Date: Tue, 21 Sep 2010 16:37:26 +0100 Subject: [PATCH 04/55] viafb: Add OLPC XO-1.5 port configs The OLPC XO-1.5 does not use the standard port wiring suggested in the viafb driver. This is required for the upcoming OLPC DCON and via-camera drivers, to be submitted soon. Signed-off-by: Daniel Drake [fts: removed useless ifdef's and corrected comment] Signed-off-by: Florian Tobias Schandinat --- drivers/video/via/via-core.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/drivers/video/via/via-core.c b/drivers/video/via/via-core.c index 42be3d955887..86bd7197f366 100644 --- a/drivers/video/via/via-core.c +++ b/drivers/video/via/via-core.c @@ -17,6 +17,7 @@ #include #include #include +#include /* * The default port config. @@ -30,6 +31,19 @@ static struct via_port_cfg adap_configs[] = { { 0, 0, 0, 0 } }; +/* + * The OLPC XO-1.5 puts the camera power and reset lines onto + * GPIO 2C. + */ +static const struct via_port_cfg olpc_adap_configs[] = { + [VIA_PORT_26] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x26 }, + [VIA_PORT_31] = { VIA_PORT_I2C, VIA_MODE_I2C, VIASR, 0x31 }, + [VIA_PORT_25] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x25 }, + [VIA_PORT_2C] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x2c }, + [VIA_PORT_3D] = { VIA_PORT_GPIO, VIA_MODE_GPIO, VIASR, 0x3d }, + { 0, 0, 0, 0 } +}; + /* * We currently only support one viafb device (will there ever be * more than one?), so just declare it globally here. @@ -654,6 +668,9 @@ static int __devinit via_pci_probe(struct pci_dev *pdev, global_dev.pdev = pdev; global_dev.chip_type = ent->driver_data; global_dev.port_cfg = adap_configs; + if (machine_is_olpc()) + global_dev.port_cfg = olpc_adap_configs; + spin_lock_init(&global_dev.reg_lock); ret = via_pci_setup_mmio(&global_dev); if (ret) From c36940e678fc30779c99246c034deca1fed61ae4 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 2 Nov 2010 11:27:16 +0000 Subject: [PATCH 05/55] fbdev: sh_mobile_hdmi: add support for more precise HDMI clock configuration The HDMI clock has to be reconfigured for different video modes. However, the precision of the supplying SoC clock on SH-Mobile systems is often insufficient. This patch allows to additionally reconfigure the parent clock to achieve the optimal HDMI clock frequency, in case this is supported by the platform. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_hdmi.c | 112 ++++++++++++++++++++------------- include/video/sh_mobile_hdmi.h | 3 + 2 files changed, 71 insertions(+), 44 deletions(-) diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index d7df10315d8d..ef41c215abae 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -685,11 +685,21 @@ static void sh_hdmi_configure(struct sh_hdmi *hdmi) } static unsigned long sh_hdmi_rate_error(struct sh_hdmi *hdmi, - const struct fb_videomode *mode) + const struct fb_videomode *mode, + unsigned long *hdmi_rate, unsigned long *parent_rate) { - long target = PICOS2KHZ(mode->pixclock) * 1000, - rate = clk_round_rate(hdmi->hdmi_clk, target); - unsigned long rate_error = rate > 0 ? abs(rate - target) : ULONG_MAX; + unsigned long target = PICOS2KHZ(mode->pixclock) * 1000, rate_error; + struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; + + *hdmi_rate = clk_round_rate(hdmi->hdmi_clk, target); + if ((long)*hdmi_rate < 0) + *hdmi_rate = clk_get_rate(hdmi->hdmi_clk); + + rate_error = (long)*hdmi_rate > 0 ? abs(*hdmi_rate - target) : ULONG_MAX; + if (rate_error && pdata->clk_optimize_parent) + rate_error = pdata->clk_optimize_parent(target, hdmi_rate, parent_rate); + else if (clk_get_parent(hdmi->hdmi_clk)) + *parent_rate = clk_get_rate(clk_get_parent(hdmi->hdmi_clk)); dev_dbg(hdmi->dev, "%u-%u-%u-%u x %u-%u-%u-%u\n", mode->left_margin, mode->xres, @@ -697,14 +707,15 @@ static unsigned long sh_hdmi_rate_error(struct sh_hdmi *hdmi, mode->upper_margin, mode->yres, mode->lower_margin, mode->vsync_len); - dev_dbg(hdmi->dev, "\t@%lu(+/-%lu)Hz, e=%lu / 1000, r=%uHz\n", target, - rate_error, rate_error ? 10000 / (10 * target / rate_error) : 0, - mode->refresh); + dev_dbg(hdmi->dev, "\t@%lu(+/-%lu)Hz, e=%lu / 1000, r=%uHz, p=%luHz\n", target, + rate_error, rate_error ? 10000 / (10 * target / rate_error) : 0, + mode->refresh, *parent_rate); return rate_error; } -static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) +static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate, + unsigned long *parent_rate) { struct fb_var_screeninfo tmpvar; struct fb_var_screeninfo *var = &tmpvar; @@ -754,11 +765,14 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) for (i = 0, mode = hdmi->monspec.modedb; f_width && f_height && i < hdmi->monspec.modedb_len && !exact_match; i++, mode++) { - unsigned long rate_error = sh_hdmi_rate_error(hdmi, mode); + unsigned long rate_error; /* No interest in unmatching modes */ if (f_width != mode->xres || f_height != mode->yres) continue; + + rate_error = sh_hdmi_rate_error(hdmi, mode, hdmi_rate, parent_rate); + if (f_refresh == mode->refresh || (!f_refresh && !rate_error)) /* * Exact match if either the refresh rate matches or it @@ -802,7 +816,7 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) if (modelist) { found = &modelist->mode; - found_rate_error = sh_hdmi_rate_error(hdmi, found); + found_rate_error = sh_hdmi_rate_error(hdmi, found, hdmi_rate, parent_rate); } } @@ -810,10 +824,6 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) if (!found) return -ENXIO; - dev_info(hdmi->dev, "Using %s mode %ux%u@%uHz (%luHz), clock error %luHz\n", - modelist ? "default" : "EDID", found->xres, found->yres, - found->refresh, PICOS2KHZ(found->pixclock) * 1000, found_rate_error); - if ((found->xres == 720 && found->yres == 480) || (found->xres == 1280 && found->yres == 720) || (found->xres == 1920 && found->yres == 1080)) @@ -821,6 +831,11 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi) else hdmi->preprogrammed_mode = false; + dev_dbg(hdmi->dev, "Using %s %s mode %ux%u@%uHz (%luHz), clock error %luHz\n", + modelist ? "default" : "EDID", hdmi->preprogrammed_mode ? "VIC" : "external", + found->xres, found->yres, found->refresh, + PICOS2KHZ(found->pixclock) * 1000, found_rate_error); + fb_videomode_to_var(&hdmi->var, found); sh_hdmi_external_video_param(hdmi); @@ -972,39 +987,38 @@ static bool sh_hdmi_must_reconfigure(struct sh_hdmi *hdmi) /** * sh_hdmi_clk_configure() - set HDMI clock frequency and enable the clock - * @hdmi: driver context - * @pixclock: pixel clock period in picoseconds - * return: configured positive rate if successful - * 0 if couldn't set the rate, but managed to enable the clock - * negative error, if couldn't enable the clock + * @hdmi: driver context + * @hdmi_rate: HDMI clock frequency in Hz + * @parent_rate: if != 0 - set parent clock rate for optimal precision + * return: configured positive rate if successful + * 0 if couldn't set the rate, but managed to enable the + * clock, negative error, if couldn't enable the clock */ -static long sh_hdmi_clk_configure(struct sh_hdmi *hdmi, unsigned long pixclock) +static long sh_hdmi_clk_configure(struct sh_hdmi *hdmi, unsigned long hdmi_rate, + unsigned long parent_rate) { - long rate; + struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; int ret; - rate = PICOS2KHZ(pixclock) * 1000; - rate = clk_round_rate(hdmi->hdmi_clk, rate); - if (rate > 0) { - ret = clk_set_rate(hdmi->hdmi_clk, rate); + if (parent_rate && clk_get_parent(hdmi->hdmi_clk)) { + ret = clk_set_rate(clk_get_parent(hdmi->hdmi_clk), parent_rate); if (ret < 0) { - dev_warn(hdmi->dev, "Cannot set rate %ld: %d\n", rate, ret); - rate = 0; + dev_warn(hdmi->dev, "Cannot set parent rate %ld: %d\n", parent_rate, ret); + hdmi_rate = clk_round_rate(hdmi->hdmi_clk, hdmi_rate); } else { - dev_dbg(hdmi->dev, "HDMI set frequency %lu\n", rate); + dev_dbg(hdmi->dev, "HDMI set parent frequency %lu\n", parent_rate); } - } else { - rate = 0; - dev_warn(hdmi->dev, "Cannot get suitable rate: %ld\n", rate); } - ret = clk_enable(hdmi->hdmi_clk); + ret = clk_set_rate(hdmi->hdmi_clk, hdmi_rate); if (ret < 0) { - dev_err(hdmi->dev, "Cannot enable clock: %d\n", ret); - return ret; + dev_warn(hdmi->dev, "Cannot set rate %ld: %d\n", hdmi_rate, ret); + hdmi_rate = 0; + } else { + dev_dbg(hdmi->dev, "HDMI set frequency %lu\n", hdmi_rate); } - return rate; + return hdmi_rate; } /* Hotplug interrupt occurred, read EDID */ @@ -1024,16 +1038,17 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work) mutex_lock(&hdmi->mutex); if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { + unsigned long parent_rate = 0, hdmi_rate; + /* A device has been plugged in */ pm_runtime_get_sync(hdmi->dev); - ret = sh_hdmi_read_edid(hdmi); + ret = sh_hdmi_read_edid(hdmi, &hdmi_rate, &parent_rate); if (ret < 0) goto out; /* Reconfigure the clock */ - clk_disable(hdmi->hdmi_clk); - ret = sh_hdmi_clk_configure(hdmi, hdmi->var.pixclock); + ret = sh_hdmi_clk_configure(hdmi, hdmi_rate, parent_rate); if (ret < 0) goto out; @@ -1166,13 +1181,22 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) goto egetclk; } - /* Some arbitrary relaxed pixclock just to get things started */ - rate = sh_hdmi_clk_configure(hdmi, 37037); + /* An arbitrary relaxed pixclock just to get things started: from standard 480p */ + rate = clk_round_rate(hdmi->hdmi_clk, PICOS2KHZ(37037)); + if (rate > 0) + rate = sh_hdmi_clk_configure(hdmi, rate, 0); + if (rate < 0) { ret = rate; goto erate; } + ret = clk_enable(hdmi->hdmi_clk); + if (ret < 0) { + dev_err(hdmi->dev, "Cannot enable clock: %d\n", ret); + goto erate; + } + dev_dbg(&pdev->dev, "Enabled HDMI clock at %luHz\n", rate); if (!request_mem_region(res->start, resource_size(res), dev_name(&pdev->dev))) { @@ -1190,10 +1214,6 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) platform_set_drvdata(pdev, hdmi); - /* Product and revision IDs are 0 in sh-mobile version */ - dev_info(&pdev->dev, "Detected HDMI controller 0x%x:0x%x\n", - hdmi_read(hdmi, HDMI_PRODUCT_ID), hdmi_read(hdmi, HDMI_REVISION_ID)); - /* Set up LCDC callbacks */ board_cfg = &pdata->lcd_chan->board_cfg; board_cfg->owner = THIS_MODULE; @@ -1206,6 +1226,10 @@ static int __init sh_hdmi_probe(struct platform_device *pdev) pm_runtime_enable(&pdev->dev); pm_runtime_resume(&pdev->dev); + /* Product and revision IDs are 0 in sh-mobile version */ + dev_info(&pdev->dev, "Detected HDMI controller 0x%x:0x%x\n", + hdmi_read(hdmi, HDMI_PRODUCT_ID), hdmi_read(hdmi, HDMI_REVISION_ID)); + ret = request_irq(irq, sh_hdmi_hotplug, 0, dev_name(&pdev->dev), hdmi); if (ret < 0) { diff --git a/include/video/sh_mobile_hdmi.h b/include/video/sh_mobile_hdmi.h index 1e1aa54ab2e4..b56932927d0a 100644 --- a/include/video/sh_mobile_hdmi.h +++ b/include/video/sh_mobile_hdmi.h @@ -13,6 +13,7 @@ struct sh_mobile_lcdc_chan_cfg; struct device; +struct clk; /* * flags format @@ -33,6 +34,8 @@ struct sh_mobile_hdmi_info { struct sh_mobile_lcdc_chan_cfg *lcd_chan; struct device *lcd_dev; unsigned int flags; + long (*clk_optimize_parent)(unsigned long target, unsigned long *best_freq, + unsigned long *parent_freq); }; #endif From 8c183ecee23991dd6c0476a537c6bae8afd2567e Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 2 Nov 2010 11:27:20 +0000 Subject: [PATCH 06/55] fbdev: sh_mobile_hdmi: improve support for more video modes Configure pre-programmed VIC modes for VGA (640x480@60) and 720x576@50 modes, change PHY mode selection to be based on pixel clock frequency. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_hdmi.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index ef41c215abae..3adffe6acafc 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -466,7 +466,7 @@ static void sh_hdmi_audio_config(struct sh_hdmi *hdmi) */ static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) { - if (hdmi->var.yres > 480) { + if (hdmi->var.pixclock < 30000) { /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */ /* * [1:0] Speed_A @@ -570,8 +570,12 @@ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) */ if (hdmi->var.yres == 1080 && hdmi->var.xres == 1920) vic = 16; + else if (hdmi->var.yres == 576 && hdmi->var.xres == 720) + vic = 17; else if (hdmi->var.yres == 480 && hdmi->var.xres == 720) vic = 2; + else if (hdmi->var.yres == 480 && hdmi->var.xres == 640) + vic = 1; else vic = 4; hdmi_write(hdmi, vic, HDMI_CTRL_PKT_BUF_ACCESS_PB4); @@ -824,9 +828,11 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate, if (!found) return -ENXIO; - if ((found->xres == 720 && found->yres == 480) || - (found->xres == 1280 && found->yres == 720) || - (found->xres == 1920 && found->yres == 1080)) + if ((found->xres == 640 && found->yres == 480 && found->refresh == 60) || + (found->xres == 720 && found->yres == 480 && found->refresh == 60) || + (found->xres == 720 && found->yres == 576 && found->refresh == 50) || + (found->xres == 1280 && found->yres == 720 && found->refresh == 60) || + (found->xres == 1920 && found->yres == 1080 && found->refresh == 60)) hdmi->preprogrammed_mode = true; else hdmi->preprogrammed_mode = false; From d2ecbab5960d9814a269d36723647d6ef391ba8f Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 4 Nov 2010 11:06:06 +0000 Subject: [PATCH 07/55] fbdev: sh_mobile_lcdc: increase maximum framebuffer size to support 1080p LCDC hardware can support 1920x1080 formats, adjust the driver to cover them. Besides, instead of guessing some "reasonable" validity checks, only verify values in .fb_check_var(), that we are sure, we cannot support. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_lcdcfb.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 9b1364723c65..993be381c21c 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -54,8 +54,8 @@ static int lcdc_shared_regs[] = { }; #define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) -#define DEFAULT_XRES 1280 -#define DEFAULT_YRES 1024 +#define MAX_XRES 1920 +#define MAX_YRES 1080 static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { [LDDCKPAT1R] = 0x400, @@ -914,22 +914,12 @@ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *in { struct sh_mobile_lcdc_chan *ch = info->par; - if (var->xres < 160 || var->xres > 1920 || - var->yres < 120 || var->yres > 1080 || - var->left_margin < 32 || var->left_margin > 320 || - var->right_margin < 12 || var->right_margin > 240 || - var->upper_margin < 12 || var->upper_margin > 120 || - var->lower_margin < 1 || var->lower_margin > 64 || - var->hsync_len < 32 || var->hsync_len > 240 || - var->vsync_len < 2 || var->vsync_len > 64 || - var->pixclock < 6000 || var->pixclock > 40000 || + if (var->xres > MAX_XRES || var->yres > MAX_YRES || var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) { - dev_warn(info->dev, "Invalid info: %u %u %u %u %u %u %u %u %u!\n", - var->xres, var->yres, - var->left_margin, var->right_margin, - var->upper_margin, var->lower_margin, - var->hsync_len, var->vsync_len, - var->pixclock); + dev_warn(info->dev, "Invalid info: %u-%u-%u-%u x %u-%u-%u-%u @ %ukHz!\n", + var->left_margin, var->xres, var->right_margin, var->hsync_len, + var->upper_margin, var->yres, var->lower_margin, var->vsync_len, + PICOS2KHZ(var->pixclock)); return -EINVAL; } return 0; @@ -1226,7 +1216,7 @@ static int __devinit sh_mobile_lcdc_probe(struct platform_device *pdev) } if (!mode) - max_size = DEFAULT_XRES * DEFAULT_YRES; + max_size = MAX_XRES * MAX_YRES; else if (max_cfg) dev_dbg(&pdev->dev, "Found largest videomode %ux%u\n", max_cfg->xres, max_cfg->yres); From 0ea2af1c15b730dba9ce741420352d298bcd7862 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 4 Nov 2010 11:06:17 +0000 Subject: [PATCH 08/55] fbdev: sh_mobile_hdmi: add support for 1080p modes Add support for 3 more preprogrammed video modes: 1080p at 24, 50, and 60Hz. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_hdmi.c | 57 +++++++++++++++++++++------------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index 3adffe6acafc..8338bd4d277e 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -209,7 +209,8 @@ enum hotplug_state { struct sh_hdmi { void __iomem *base; enum hotplug_state hp_state; /* hot-plug status */ - bool preprogrammed_mode; /* use a pre-programmed VIC or the external mode */ + u8 preprogrammed_vic; /* use a pre-programmed VIC or + the external mode */ struct clk *hdmi_clk; struct device *dev; struct fb_info *info; @@ -342,7 +343,7 @@ static void sh_hdmi_external_video_param(struct sh_hdmi *hdmi) hdmi_write(hdmi, var->vsync_len, HDMI_EXTERNAL_V_DURATION); /* Set bit 0 of HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS here for external mode */ - if (!hdmi->preprogrammed_mode) + if (!hdmi->preprogrammed_vic) hdmi_write(hdmi, sync | 1 | (voffset << 4), HDMI_EXTERNAL_VIDEO_PARAM_SETTINGS); } @@ -466,7 +467,18 @@ static void sh_hdmi_audio_config(struct sh_hdmi *hdmi) */ static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) { - if (hdmi->var.pixclock < 30000) { + if (hdmi->var.pixclock < 10000) { + /* for 1080p8bit 148MHz */ + hdmi_write(hdmi, 0x1d, HDMI_SLIPHDMIT_PARAM_SETTINGS_1); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_2); + hdmi_write(hdmi, 0x00, HDMI_SLIPHDMIT_PARAM_SETTINGS_3); + hdmi_write(hdmi, 0x4c, HDMI_SLIPHDMIT_PARAM_SETTINGS_5); + hdmi_write(hdmi, 0x1e, HDMI_SLIPHDMIT_PARAM_SETTINGS_6); + hdmi_write(hdmi, 0x48, HDMI_SLIPHDMIT_PARAM_SETTINGS_7); + hdmi_write(hdmi, 0x0e, HDMI_SLIPHDMIT_PARAM_SETTINGS_8); + hdmi_write(hdmi, 0x25, HDMI_SLIPHDMIT_PARAM_SETTINGS_9); + hdmi_write(hdmi, 0x04, HDMI_SLIPHDMIT_PARAM_SETTINGS_10); + } else if (hdmi->var.pixclock < 30000) { /* 720p, 8bit, 74.25MHz. Might need to be adjusted for other formats */ /* * [1:0] Speed_A @@ -524,6 +536,7 @@ static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) */ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) { + struct fb_var_screeninfo *var = &hdmi->var; u8 vic; /* AVI InfoFrame */ @@ -565,17 +578,11 @@ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) hdmi_write(hdmi, 0x00, HDMI_CTRL_PKT_BUF_ACCESS_PB3); /* - * VIC = 1280 x 720p: ignored if external config is used - * Send 2 for 720 x 480p, 16 for 1080p, ignored in external mode + * VIC should be ignored if external config is used, so, we could just use 0, + * but play safe and use a valid value in any case just in case */ - if (hdmi->var.yres == 1080 && hdmi->var.xres == 1920) - vic = 16; - else if (hdmi->var.yres == 576 && hdmi->var.xres == 720) - vic = 17; - else if (hdmi->var.yres == 480 && hdmi->var.xres == 720) - vic = 2; - else if (hdmi->var.yres == 480 && hdmi->var.xres == 640) - vic = 1; + if (hdmi->preprogrammed_vic) + vic = hdmi->preprogrammed_vic; else vic = 4; hdmi_write(hdmi, vic, HDMI_CTRL_PKT_BUF_ACCESS_PB4); @@ -828,17 +835,25 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate, if (!found) return -ENXIO; - if ((found->xres == 640 && found->yres == 480 && found->refresh == 60) || - (found->xres == 720 && found->yres == 480 && found->refresh == 60) || - (found->xres == 720 && found->yres == 576 && found->refresh == 50) || - (found->xres == 1280 && found->yres == 720 && found->refresh == 60) || - (found->xres == 1920 && found->yres == 1080 && found->refresh == 60)) - hdmi->preprogrammed_mode = true; + if (found->xres == 640 && found->yres == 480 && found->refresh == 60) + hdmi->preprogrammed_vic = 1; + else if (found->xres == 720 && found->yres == 480 && found->refresh == 60) + hdmi->preprogrammed_vic = 2; + else if (found->xres == 720 && found->yres == 576 && found->refresh == 50) + hdmi->preprogrammed_vic = 17; + else if (found->xres == 1280 && found->yres == 720 && found->refresh == 60) + hdmi->preprogrammed_vic = 4; + else if (found->xres == 1920 && found->yres == 1080 && found->refresh == 24) + hdmi->preprogrammed_vic = 32; + else if (found->xres == 1920 && found->yres == 1080 && found->refresh == 50) + hdmi->preprogrammed_vic = 31; + else if (found->xres == 1920 && found->yres == 1080 && found->refresh == 60) + hdmi->preprogrammed_vic = 16; else - hdmi->preprogrammed_mode = false; + hdmi->preprogrammed_vic = 0; dev_dbg(hdmi->dev, "Using %s %s mode %ux%u@%uHz (%luHz), clock error %luHz\n", - modelist ? "default" : "EDID", hdmi->preprogrammed_mode ? "VIC" : "external", + modelist ? "default" : "EDID", hdmi->preprogrammed_vic ? "VIC" : "external", found->xres, found->yres, found->refresh, PICOS2KHZ(found->pixclock) * 1000, found_rate_error); From 830539d14379d0f5cb07832a3e4466418011f843 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Wed, 10 Nov 2010 17:51:44 +0900 Subject: [PATCH 09/55] fbdev: sh_mobile_lcdcfb: fix up compiler warnings. CC drivers/video/sh_mobile_lcdcfb.o drivers/video/sh_mobile_lcdcfb.c: In function 'sh_mobile_check_var': drivers/video/sh_mobile_lcdcfb.c:922: warning: format '%u' expects type 'unsigned int', but argument 11 has type 'long unsigned int' Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_lcdcfb.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index 993be381c21c..37419ba2c29a 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -916,7 +916,7 @@ static int sh_mobile_check_var(struct fb_var_screeninfo *var, struct fb_info *in if (var->xres > MAX_XRES || var->yres > MAX_YRES || var->xres * var->yres * (ch->cfg.bpp / 8) * 2 > info->fix.smem_len) { - dev_warn(info->dev, "Invalid info: %u-%u-%u-%u x %u-%u-%u-%u @ %ukHz!\n", + dev_warn(info->dev, "Invalid info: %u-%u-%u-%u x %u-%u-%u-%u @ %lukHz!\n", var->left_margin, var->xres, var->right_margin, var->hsync_len, var->upper_margin, var->yres, var->lower_margin, var->vsync_len, PICOS2KHZ(var->pixclock)); From c724d07a56e60e91b0aa75193f86fb000545ffe4 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Wed, 10 Nov 2010 17:54:20 +0900 Subject: [PATCH 10/55] fbdev: sh_mobile_hdmi: fix up compiler warnings. CC drivers/video/sh_mobile_hdmi.o drivers/video/sh_mobile_hdmi.c: In function 'sh_hdmi_avi_infoframe_setup': drivers/video/sh_mobile_hdmi.c:539: warning: unused variable 'var' drivers/video/sh_mobile_hdmi.c: In function 'sh_hdmi_clk_configure': drivers/video/sh_mobile_hdmi.c:1021: warning: unused variable 'pdata' Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_hdmi.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index 8338bd4d277e..3b4cf987fb43 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -536,7 +536,6 @@ static void sh_hdmi_phy_config(struct sh_hdmi *hdmi) */ static void sh_hdmi_avi_infoframe_setup(struct sh_hdmi *hdmi) { - struct fb_var_screeninfo *var = &hdmi->var; u8 vic; /* AVI InfoFrame */ @@ -1018,7 +1017,6 @@ static bool sh_hdmi_must_reconfigure(struct sh_hdmi *hdmi) static long sh_hdmi_clk_configure(struct sh_hdmi *hdmi, unsigned long hdmi_rate, unsigned long parent_rate) { - struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; int ret; if (parent_rate && clk_get_parent(hdmi->hdmi_clk)) { From 640dcfa0ff49ea1fcddad39e7c468683d9f00342 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Tue, 2 Nov 2010 11:27:34 +0000 Subject: [PATCH 11/55] ARM: mach-shmobile: optimize PLLC2 frequency for HDMI On ap4evb PLLC2 is only used as a parent of the HDMI clock, therefore it can be reconfigured freely to improve HDMI clock precision. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- arch/arm/mach-shmobile/board-ap4evb.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/arch/arm/mach-shmobile/board-ap4evb.c b/arch/arm/mach-shmobile/board-ap4evb.c index 32d9e2816e56..732baed08578 100644 --- a/arch/arm/mach-shmobile/board-ap4evb.c +++ b/arch/arm/mach-shmobile/board-ap4evb.c @@ -669,10 +669,15 @@ static struct platform_device lcdc1_device = { }, }; +static long ap4evb_clk_optimize(unsigned long target, unsigned long *best_freq, + unsigned long *parent_freq); + + static struct sh_mobile_hdmi_info hdmi_info = { .lcd_chan = &sh_mobile_lcdc1_info.ch[0], .lcd_dev = &lcdc1_device.dev, .flags = HDMI_SND_SRC_SPDIF, + .clk_optimize_parent = ap4evb_clk_optimize, }; static struct resource hdmi_resources[] = { @@ -699,6 +704,25 @@ static struct platform_device hdmi_device = { }, }; +static long ap4evb_clk_optimize(unsigned long target, unsigned long *best_freq, + unsigned long *parent_freq) +{ + struct clk *hdmi_ick = clk_get(&hdmi_device.dev, "ick"); + long error; + + if (IS_ERR(hdmi_ick)) { + int ret = PTR_ERR(hdmi_ick); + pr_err("Cannot get HDMI ICK: %d\n", ret); + return ret; + } + + error = clk_round_parent(hdmi_ick, target, best_freq, parent_freq, 1, 64); + + clk_put(hdmi_ick); + + return error; +} + static struct gpio_led ap4evb_leds[] = { { .name = "led4", From 9fbbdde93231ad7f35c217aa6bbbc7995133f483 Mon Sep 17 00:00:00 2001 From: Erik Gilling Date: Thu, 11 Nov 2010 15:44:43 +0100 Subject: [PATCH 12/55] video: add fb_edid_add_monspecs for parsing extended edid information Modern monitors/tvs have more extended EDID information blocks which can contain extra detailed modes. This adds a fb_edid_add_monspecs function which drivers can use to parse those additions blocks. Signed-off-by: Erik Gilling Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/fbmon.c | 57 +++++++++++++++++++++++++++++++++++++++++++ include/linux/fb.h | 2 ++ 2 files changed, 59 insertions(+) diff --git a/drivers/video/fbmon.c b/drivers/video/fbmon.c index 563a98b88e9b..a0b5a93b72d2 100644 --- a/drivers/video/fbmon.c +++ b/drivers/video/fbmon.c @@ -973,6 +973,63 @@ void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) DPRINTK("========================================\n"); } +void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ + unsigned char *block; + struct fb_videomode *mode, *m; + int num = 0, i, first = 1; + + if (edid == NULL) + return; + + if (!edid_checksum(edid)) + return; + + if (edid[0] != 0x2) + return; + + mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); + if (mode == NULL) + return; + + block = edid + edid[0x2]; + + DPRINTK(" Extended Detailed Timings\n"); + + for (i = 0; i < (128 - edid[0x2]) / DETAILED_TIMING_DESCRIPTION_SIZE; + i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { + if (!(block[0] == 0x00 && block[1] == 0x00)) { + get_detailed_timing(block, &mode[num]); + if (first) { + mode[num].flag |= FB_MODE_IS_FIRST; + first = 0; + } + num++; + } + } + + /* Yikes, EDID data is totally useless */ + if (!num) { + kfree(mode); + return; + } + + m = kzalloc((specs->modedb_len + num) * + sizeof(struct fb_videomode), GFP_KERNEL); + + if (!m) { + kfree(mode); + return; + } + + memmove(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode)); + memmove(m + specs->modedb_len, mode, num * sizeof(struct fb_videomode)); + kfree(mode); + kfree(specs->modedb); + specs->modedb = m; + specs->modedb_len = specs->modedb_len + num; +} + /* * VESA Generalized Timing Formula (GTF) */ diff --git a/include/linux/fb.h b/include/linux/fb.h index 7fca3dc4e475..6f0274d96f0c 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -1092,6 +1092,8 @@ extern int fb_parse_edid(unsigned char *edid, struct fb_var_screeninfo *var); extern const unsigned char *fb_firmware_edid(struct device *device); extern void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs); +extern void fb_edid_add_monspecs(unsigned char *edid, + struct fb_monspecs *specs); extern void fb_destroy_modedb(struct fb_videomode *modedb); extern int fb_find_mode_cvt(struct fb_videomode *mode, int margins, int rb); extern unsigned char *fb_ddc_read(struct i2c_adapter *adapter); From e4105119aca9b86b163fa07428df1f615034a03d Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 11 Nov 2010 15:44:52 +0100 Subject: [PATCH 13/55] fbdev: export fb_edid_add_monspecs() for modules, improve algorithm fb_edid_add_monspecs() should also be exported for use in modules, and it requires a dummy version for the case, when CONFIG_FB_MODE_HELPERS is not selected. This patch also improves the algorithm by removing a redundant memory allocation, adds function documentation, adds data verification and replaces memmove() with memcpy(). Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/fbmon.c | 58 ++++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/drivers/video/fbmon.c b/drivers/video/fbmon.c index a0b5a93b72d2..b25399abcf49 100644 --- a/drivers/video/fbmon.c +++ b/drivers/video/fbmon.c @@ -973,58 +973,56 @@ void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) DPRINTK("========================================\n"); } +/** + * fb_edid_add_monspecs() - add monitor video modes from E-EDID data + * @edid: 128 byte array with an E-EDID block + * @spacs: monitor specs to be extended + */ void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) { unsigned char *block; - struct fb_videomode *mode, *m; - int num = 0, i, first = 1; + struct fb_videomode *m; + int num = 0, i; + u8 edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE]; - if (edid == NULL) + if (!edid) return; if (!edid_checksum(edid)) return; - if (edid[0] != 0x2) + if (edid[0] != 0x2 || + edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE) return; - mode = kzalloc(50 * sizeof(struct fb_videomode), GFP_KERNEL); - if (mode == NULL) - return; - - block = edid + edid[0x2]; + block = edid + edid[2]; DPRINTK(" Extended Detailed Timings\n"); - for (i = 0; i < (128 - edid[0x2]) / DETAILED_TIMING_DESCRIPTION_SIZE; - i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) { - if (!(block[0] == 0x00 && block[1] == 0x00)) { - get_detailed_timing(block, &mode[num]); - if (first) { - mode[num].flag |= FB_MODE_IS_FIRST; - first = 0; - } - num++; - } - } + for (i = 0; i < (128 - edid[2]) / DETAILED_TIMING_DESCRIPTION_SIZE; + i++, block += DETAILED_TIMING_DESCRIPTION_SIZE) + if (PIXEL_CLOCK) + edt[num++] = block - edid; /* Yikes, EDID data is totally useless */ - if (!num) { - kfree(mode); + if (!num) return; - } m = kzalloc((specs->modedb_len + num) * sizeof(struct fb_videomode), GFP_KERNEL); - if (!m) { - kfree(mode); + if (!m) return; + + memcpy(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode)); + + for (i = specs->modedb_len; i < specs->modedb_len + num; i++) { + get_detailed_timing(edid + edt[i - specs->modedb_len], &m[i]); + if (i == specs->modedb_len) + m[i].flag |= FB_MODE_IS_FIRST; + pr_debug("Adding %ux%u@%u\n", m[i].xres, m[i].yres, m[i].refresh); } - memmove(m, specs->modedb, specs->modedb_len * sizeof(struct fb_videomode)); - memmove(m + specs->modedb_len, mode, num * sizeof(struct fb_videomode)); - kfree(mode); kfree(specs->modedb); specs->modedb = m; specs->modedb_len = specs->modedb_len + num; @@ -1346,6 +1344,9 @@ void fb_edid_to_monspecs(unsigned char *edid, struct fb_monspecs *specs) { specs = NULL; } +void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) +{ +} void fb_destroy_modedb(struct fb_videomode *modedb) { } @@ -1453,6 +1454,7 @@ EXPORT_SYMBOL(fb_firmware_edid); EXPORT_SYMBOL(fb_parse_edid); EXPORT_SYMBOL(fb_edid_to_monspecs); +EXPORT_SYMBOL(fb_edid_add_monspecs); EXPORT_SYMBOL(fb_get_mode); EXPORT_SYMBOL(fb_validate_mode); EXPORT_SYMBOL(fb_destroy_modedb); From 0ad83f6882c41df1a7fa387086029e162038c1f2 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 11 Nov 2010 15:45:04 +0100 Subject: [PATCH 14/55] fbdev: when parsing E-EDID blocks, also use SVD entries Add parsing of E-EDID SVD entries. In this first version only a few CEA/EIA-861E modes are implemented, more can be added as needed. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/fbmon.c | 37 ++++++++++++++++++++++++++++++++---- drivers/video/modedb.c | 43 ++++++++++++++++++++++++++++++++++++++++++ include/linux/fb.h | 1 + 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/drivers/video/fbmon.c b/drivers/video/fbmon.c index b25399abcf49..4f57485f8c54 100644 --- a/drivers/video/fbmon.c +++ b/drivers/video/fbmon.c @@ -983,7 +983,8 @@ void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) unsigned char *block; struct fb_videomode *m; int num = 0, i; - u8 edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE]; + u8 svd[64], edt[(128 - 4) / DETAILED_TIMING_DESCRIPTION_SIZE]; + u8 pos = 4, svd_n = 0; if (!edid) return; @@ -995,6 +996,21 @@ void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) edid[2] < 4 || edid[2] > 128 - DETAILED_TIMING_DESCRIPTION_SIZE) return; + DPRINTK(" Short Video Descriptors\n"); + + while (pos < edid[2]) { + u8 len = edid[pos] & 0x1f, type = (edid[pos] >> 5) & 7; + pr_debug("Data block %u of %u bytes\n", type, len); + if (type == 2) + for (i = pos; i < pos + len; i++) { + u8 idx = edid[pos + i] & 0x7f; + svd[svd_n++] = idx; + pr_debug("N%sative mode #%d\n", + edid[pos + i] & 0x80 ? "" : "on-n", idx); + } + pos += len + 1; + } + block = edid + edid[2]; DPRINTK(" Extended Detailed Timings\n"); @@ -1005,10 +1021,10 @@ void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) edt[num++] = block - edid; /* Yikes, EDID data is totally useless */ - if (!num) + if (!(num + svd_n)) return; - m = kzalloc((specs->modedb_len + num) * + m = kzalloc((specs->modedb_len + num + svd_n) * sizeof(struct fb_videomode), GFP_KERNEL); if (!m) @@ -1023,9 +1039,22 @@ void fb_edid_add_monspecs(unsigned char *edid, struct fb_monspecs *specs) pr_debug("Adding %ux%u@%u\n", m[i].xres, m[i].yres, m[i].refresh); } + for (i = specs->modedb_len + num; i < specs->modedb_len + num + svd_n; i++) { + int idx = svd[i - specs->modedb_len - num]; + if (!idx || idx > 63) { + pr_warning("Reserved SVD code %d\n", idx); + } else if (idx > ARRAY_SIZE(cea_modes) || !cea_modes[idx].xres) { + pr_warning("Unimplemented SVD code %d\n", idx); + } else { + memcpy(&m[i], cea_modes + idx, sizeof(m[i])); + pr_debug("Adding SVD #%d: %ux%u@%u\n", idx, + m[i].xres, m[i].yres, m[i].refresh); + } + } + kfree(specs->modedb); specs->modedb = m; - specs->modedb_len = specs->modedb_len + num; + specs->modedb_len = specs->modedb_len + num + svd_n; } /* diff --git a/drivers/video/modedb.c b/drivers/video/modedb.c index 0a4dbdc1693a..9a0ae6ca5427 100644 --- a/drivers/video/modedb.c +++ b/drivers/video/modedb.c @@ -278,6 +278,49 @@ static const struct fb_videomode modedb[] = { }; #ifdef CONFIG_FB_MODE_HELPERS +const struct fb_videomode cea_modes[64] = { + /* #1: 640x480p@59.94/60Hz */ + [1] = { + NULL, 60, 640, 480, 39722, 48, 16, 33, 10, 96, 2, 0, FB_VMODE_NONINTERLACED, 0, + }, + /* #3: 720x480p@59.94/60Hz */ + [3] = { + NULL, 60, 720, 480, 37037, 60, 16, 30, 9, 62, 6, 0, FB_VMODE_NONINTERLACED, 0, + }, + /* #5: 1920x1080i@59.94/60Hz */ + [5] = { + NULL, 60, 1920, 1080, 13763, 148, 88, 15, 2, 44, 5, 0, FB_VMODE_INTERLACED, 0, + }, + /* #7: 720(1440)x480iH@59.94/60Hz */ + [7] = { + NULL, 60, 1440, 480, 18554/*37108*/, 114, 38, 15, 4, 124, 3, 0, FB_VMODE_INTERLACED, 0, + }, + /* #9: 720(1440)x240pH@59.94/60Hz */ + [9] = { + NULL, 60, 1440, 240, 18554, 114, 38, 16, 4, 124, 3, 0, FB_VMODE_NONINTERLACED, 0, + }, + /* #18: 720x576pH@50Hz */ + [18] = { + NULL, 50, 720, 576, 37037, 68, 12, 39, 5, 64, 5, 0, FB_VMODE_NONINTERLACED, 0, + }, + /* #19: 1280x720p@50Hz */ + [19] = { + NULL, 50, 1280, 720, 13468, 220, 440, 20, 5, 40, 5, 0, FB_VMODE_NONINTERLACED, 0, + }, + /* #20: 1920x1080i@50Hz */ + [20] = { + NULL, 50, 1920, 1080, 13480, 148, 528, 15, 5, 528, 5, 0, FB_VMODE_INTERLACED, 0, + }, + /* #32: 1920x1080p@23.98/24Hz */ + [32] = { + NULL, 24, 1920, 1080, 13468, 148, 638, 36, 4, 44, 5, 0, FB_VMODE_NONINTERLACED, 0, + }, + /* #35: (2880)x480p4x@59.94/60Hz */ + [35] = { + NULL, 50, 2880, 480, 11100, 240, 64, 30, 9, 248, 6, 0, FB_VMODE_NONINTERLACED, 0, + }, +}; + const struct fb_videomode vesa_modes[] = { /* 0 640x350-85 VESA */ { NULL, 85, 640, 350, 31746, 96, 32, 60, 32, 64, 3, diff --git a/include/linux/fb.h b/include/linux/fb.h index 6f0274d96f0c..e154a79b8322 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -1151,6 +1151,7 @@ struct fb_videomode { extern const char *fb_mode_option; extern const struct fb_videomode vesa_modes[]; +extern const struct fb_videomode cea_modes[64]; struct fb_modelist { struct list_head list; From 4232f60791d12a5156c3e1c9b04ae08b873a2920 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 11 Nov 2010 14:45:09 +0000 Subject: [PATCH 15/55] fbdev: sh_mobile_hdmi: add support for E-EDID parsing Many HDMI clients implement enhanced EDID blocks, which often contain additional supported video modes. This patch implements parsing of such E-EDID blocks. Signed-off-by: Guennadi Liakhovetski Signed-off-by: Paul Mundt --- drivers/video/sh_mobile_hdmi.c | 60 +++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c index 3b4cf987fb43..76f9fac9020f 100644 --- a/drivers/video/sh_mobile_hdmi.c +++ b/drivers/video/sh_mobile_hdmi.c @@ -211,6 +211,9 @@ struct sh_hdmi { enum hotplug_state hp_state; /* hot-plug status */ u8 preprogrammed_vic; /* use a pre-programmed VIC or the external mode */ + u8 edid_block_addr; + u8 edid_segment_nr; + u8 edid_blocks; struct clk *hdmi_clk; struct device *dev; struct fb_info *info; @@ -756,7 +759,38 @@ static int sh_hdmi_read_edid(struct sh_hdmi *hdmi, unsigned long *hdmi_rate, printk(KERN_CONT "\n"); #endif - fb_edid_to_monspecs(edid, &hdmi->monspec); + if (!hdmi->edid_blocks) { + fb_edid_to_monspecs(edid, &hdmi->monspec); + hdmi->edid_blocks = edid[126] + 1; + + dev_dbg(hdmi->dev, "%d main modes, %d extension blocks\n", + hdmi->monspec.modedb_len, hdmi->edid_blocks - 1); + } else { + dev_dbg(hdmi->dev, "Extension %u detected, DTD start %u\n", + edid[0], edid[2]); + fb_edid_add_monspecs(edid, &hdmi->monspec); + } + + if (hdmi->edid_blocks > hdmi->edid_segment_nr * 2 + + (hdmi->edid_block_addr >> 7) + 1) { + /* More blocks to read */ + if (hdmi->edid_block_addr) { + hdmi->edid_block_addr = 0; + hdmi->edid_segment_nr++; + } else { + hdmi->edid_block_addr = 0x80; + } + /* Set EDID word address */ + hdmi_write(hdmi, hdmi->edid_block_addr, HDMI_EDID_WORD_ADDRESS); + /* Enable EDID interrupt */ + hdmi_write(hdmi, 0xC6, HDMI_INTERRUPT_MASK_1); + /* Set EDID segment pointer - starts reading EDID */ + hdmi_write(hdmi, hdmi->edid_segment_nr, HDMI_EDID_SEGMENT_POINTER); + return -EAGAIN; + } + + /* All E-EDID blocks ready */ + dev_dbg(hdmi->dev, "%d main and extended modes\n", hdmi->monspec.modedb_len); fb_get_options("sh_mobile_lcdc", &forced); if (forced && *forced) { @@ -903,32 +937,34 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id) /* Check, if hot plug & MSENS pin status are both high */ if ((msens & 0xC0) == 0xC0) { /* Display plug in */ + hdmi->edid_segment_nr = 0; + hdmi->edid_block_addr = 0; + hdmi->edid_blocks = 0; hdmi->hp_state = HDMI_HOTPLUG_CONNECTED; /* Set EDID word address */ hdmi_write(hdmi, 0x00, HDMI_EDID_WORD_ADDRESS); - /* Set EDID segment pointer */ - hdmi_write(hdmi, 0x00, HDMI_EDID_SEGMENT_POINTER); /* Enable EDID interrupt */ hdmi_write(hdmi, 0xC6, HDMI_INTERRUPT_MASK_1); + /* Set EDID segment pointer - starts reading EDID */ + hdmi_write(hdmi, 0x00, HDMI_EDID_SEGMENT_POINTER); } else if (!(status1 & 0x80)) { /* Display unplug, beware multiple interrupts */ - if (hdmi->hp_state != HDMI_HOTPLUG_DISCONNECTED) + if (hdmi->hp_state != HDMI_HOTPLUG_DISCONNECTED) { + hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED; schedule_delayed_work(&hdmi->edid_work, 0); - - hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED; + } /* display_off will switch back to mode_a */ } } else if (status1 & 2) { /* EDID error interrupt: retry */ /* Set EDID word address */ - hdmi_write(hdmi, 0x00, HDMI_EDID_WORD_ADDRESS); + hdmi_write(hdmi, hdmi->edid_block_addr, HDMI_EDID_WORD_ADDRESS); /* Set EDID segment pointer */ - hdmi_write(hdmi, 0x00, HDMI_EDID_SEGMENT_POINTER); + hdmi_write(hdmi, hdmi->edid_segment_nr, HDMI_EDID_SEGMENT_POINTER); } else if (status1 & 4) { /* Disable EDID interrupt */ hdmi_write(hdmi, 0xC0, HDMI_INTERRUPT_MASK_1); - hdmi->hp_state = HDMI_HOTPLUG_EDID_DONE; schedule_delayed_work(&hdmi->edid_work, msecs_to_jiffies(10)); } @@ -1056,7 +1092,7 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work) mutex_lock(&hdmi->mutex); - if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { + if (hdmi->hp_state == HDMI_HOTPLUG_CONNECTED) { unsigned long parent_rate = 0, hdmi_rate; /* A device has been plugged in */ @@ -1066,6 +1102,8 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work) if (ret < 0) goto out; + hdmi->hp_state = HDMI_HOTPLUG_EDID_DONE; + /* Reconfigure the clock */ ret = sh_hdmi_clk_configure(hdmi, hdmi_rate, parent_rate); if (ret < 0) @@ -1119,7 +1157,7 @@ static void sh_hdmi_edid_work_fn(struct work_struct *work) } out: - if (ret < 0) + if (ret < 0 && ret != -EAGAIN) hdmi->hp_state = HDMI_HOTPLUG_DISCONNECTED; mutex_unlock(&hdmi->mutex); From 96f8d864afd646e4a52ea55462b7d83e3b94fd5c Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Tue, 16 Nov 2010 14:00:24 +0900 Subject: [PATCH 16/55] fbdev: move udlfb out of staging. udlfb has undergone a fair bit of cleanup recently and is effectively at the point where it can be liberated from staging purgatory and promoted to a real driver. The outstanding cleanups are all minor, with some of them dependent on drivers/video headers, so these will be done incrementally from udlfb's new home. Requested-by: Bernie Thompson Signed-off-by: Paul Mundt --- .../staging/udlfb => Documentation/fb}/udlfb.txt | 0 drivers/staging/Kconfig | 2 -- drivers/staging/Makefile | 1 - drivers/staging/udlfb/Kconfig | 14 -------------- drivers/staging/udlfb/Makefile | 1 - drivers/video/Kconfig | 14 ++++++++++++++ drivers/video/Makefile | 1 + drivers/{staging/udlfb => video}/udlfb.c | 3 +-- {drivers/staging/udlfb => include/video}/udlfb.h | 0 9 files changed, 16 insertions(+), 20 deletions(-) rename {drivers/staging/udlfb => Documentation/fb}/udlfb.txt (100%) delete mode 100644 drivers/staging/udlfb/Kconfig delete mode 100644 drivers/staging/udlfb/Makefile rename drivers/{staging/udlfb => video}/udlfb.c (99%) rename {drivers/staging/udlfb => include/video}/udlfb.h (100%) diff --git a/drivers/staging/udlfb/udlfb.txt b/Documentation/fb/udlfb.txt similarity index 100% rename from drivers/staging/udlfb/udlfb.txt rename to Documentation/fb/udlfb.txt diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 5eafdf435550..df31a7228079 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -111,8 +111,6 @@ source "drivers/staging/vt6655/Kconfig" source "drivers/staging/vt6656/Kconfig" -source "drivers/staging/udlfb/Kconfig" - source "drivers/staging/hv/Kconfig" source "drivers/staging/vme/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index a97a955c094b..7a15c0c82b69 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -38,7 +38,6 @@ obj-$(CONFIG_USB_SERIAL_QUATECH_USB2) += quatech_usb2/ obj-$(CONFIG_OCTEON_ETHERNET) += octeon/ obj-$(CONFIG_VT6655) += vt6655/ obj-$(CONFIG_VT6656) += vt6656/ -obj-$(CONFIG_FB_UDL) += udlfb/ obj-$(CONFIG_HYPERV) += hv/ obj-$(CONFIG_VME_BUS) += vme/ obj-$(CONFIG_MRST_RAR_HANDLER) += memrar/ diff --git a/drivers/staging/udlfb/Kconfig b/drivers/staging/udlfb/Kconfig deleted file mode 100644 index 65bd5db4ca56..000000000000 --- a/drivers/staging/udlfb/Kconfig +++ /dev/null @@ -1,14 +0,0 @@ -config FB_UDL - tristate "Displaylink USB Framebuffer support" - depends on FB && USB - select FB_MODE_HELPERS - select FB_SYS_FILLRECT - select FB_SYS_COPYAREA - select FB_SYS_IMAGEBLIT - select FB_SYS_FOPS - select FB_DEFERRED_IO - ---help--- - This is a kernel framebuffer driver for DisplayLink USB devices. - Supports fbdev clients like xf86-video-fbdev, kdrive, fbi, and - mplayer -vo fbdev. Supports all USB 2.0 era DisplayLink devices. - To compile as a module, choose M here: the module name is udlfb. diff --git a/drivers/staging/udlfb/Makefile b/drivers/staging/udlfb/Makefile deleted file mode 100644 index 30d9e675b10f..000000000000 --- a/drivers/staging/udlfb/Makefile +++ /dev/null @@ -1 +0,0 @@ -obj-$(CONFIG_FB_UDL) += udlfb.o diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 27c1fb4b1e0d..37771d0916ef 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2034,6 +2034,20 @@ config FB_SM501 If unsure, say N. +config FB_UDL + tristate "Displaylink USB Framebuffer support" + depends on FB && USB + select FB_MODE_HELPERS + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO + ---help--- + This is a kernel framebuffer driver for DisplayLink USB devices. + Supports fbdev clients like xf86-video-fbdev, kdrive, fbi, and + mplayer -vo fbdev. Supports all USB 2.0 era DisplayLink devices. + To compile as a module, choose M here: the module name is udlfb. config FB_PNX4008_DUM tristate "Display Update Module support on Philips PNX4008 board" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 485e8ed1318c..03678e3021ab 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -122,6 +122,7 @@ 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 +obj-$(CONFIG_FB_UDL) += udlfb.o obj-$(CONFIG_FB_XILINX) += xilinxfb.o obj-$(CONFIG_SH_MIPI_DSI) += sh_mipi_dsi.o obj-$(CONFIG_FB_SH_MOBILE_HDMI) += sh_mobile_hdmi.o diff --git a/drivers/staging/udlfb/udlfb.c b/drivers/video/udlfb.c similarity index 99% rename from drivers/staging/udlfb/udlfb.c rename to drivers/video/udlfb.c index fed25105970a..0cca4873d490 100644 --- a/drivers/staging/udlfb/udlfb.c +++ b/drivers/video/udlfb.c @@ -26,8 +26,7 @@ #include #include #include - -#include "udlfb.h" +#include