From caf003fa1d4386755d34f12bb23f264da1496d3b Mon Sep 17 00:00:00 2001 From: Liviu Dudau Date: Wed, 18 Jan 2012 16:52:04 +0000 Subject: ARM HDLCD: Add support for ARM High Definition LCD. The ARM HDLCD device is now found in various new Versatile Express coretiles. Signed-off-by: Liviu Dudau --- drivers/video/Kconfig | 15 + drivers/video/Makefile | 1 + drivers/video/arm-hdlcd.c | 739 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/arm-hdlcd.h | 100 +++++++ 4 files changed, 855 insertions(+) create mode 100644 drivers/video/arm-hdlcd.c create mode 100644 include/linux/arm-hdlcd.h diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 2e937bdace6f..89f8c68c40c4 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -326,6 +326,21 @@ config FB_ARMCLCD here and read . The module will be called amba-clcd. +config FB_ARMHDLCD + tristate "ARM High Definition LCD support" + depends on FB && ARM + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This framebuffer device driver is for the ARM High Definition + Colour LCD controller. + + If you want to compile this as a module (=code which can be + inserted into and removed from the running kernel), say M + here and read . The module + will be called arm-hdlcd. + config FB_ACORN bool "Acorn VIDC support" depends on (FB = y) && ARM && ARCH_ACORN diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e8bae8dd4804..82f09484b0d8 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_FB_ATMEL) += atmel_lcdfb.o obj-$(CONFIG_FB_PVR2) += pvr2fb.o obj-$(CONFIG_FB_VOODOO1) += sstfb.o obj-$(CONFIG_FB_ARMCLCD) += amba-clcd.o +obj-$(CONFIG_FB_ARMHDLCD) += arm-hdlcd.o obj-$(CONFIG_FB_GOLDFISH) += goldfishfb.o obj-$(CONFIG_FB_68328) += 68328fb.o obj-$(CONFIG_FB_GBE) += gbefb.o diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c new file mode 100644 index 000000000000..3e103a5c9b57 --- /dev/null +++ b/drivers/video/arm-hdlcd.c @@ -0,0 +1,739 @@ +/* + * drivers/video/arm-hdlcd.c + * + * Copyright (C) 2011 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "edid.h" + +#ifdef CONFIG_SERIAL_AMBA_PCU_UART +/* set the DVI output mode using the firmware */ +int set_dvi_mode(u8 *msgbuf); +int get_edid(u8 *msgbuf); +#endif + +#define to_hdlcd_device(info) container_of(info, struct hdlcd_device, fb) + +static struct of_device_id hdlcd_of_matches[] = { + { .compatible = "arm,hdlcd" }, + {}, +}; + +/* Framebuffer size. */ +static unsigned long framebuffer_size; + +static char *fb_mode = "1680x1050-32@60\0\0\0\0\0"; + +static struct fb_var_screeninfo cached_var_screeninfo; + +static struct fb_videomode hdlcd_default_mode = { + .refresh = 60, + .xres = 1680, + .yres = 1050, + .pixclock = 8403, + .left_margin = 80, + .right_margin = 48, + .upper_margin = 21, + .lower_margin = 3, + .hsync_len = 32, + .vsync_len = 6, + .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, + .vmode = FB_VMODE_NONINTERLACED +}; + + +static inline void hdlcd_enable(struct hdlcd_device *hdlcd) +{ + dev_dbg(hdlcd->dev, "HDLCD: output enabled\n"); + writel(1, hdlcd->base + HDLCD_REG_COMMAND); +} + +static inline void hdlcd_disable(struct hdlcd_device *hdlcd) +{ + dev_dbg(hdlcd->dev, "HDLCD: output disabled\n"); + writel(0, hdlcd->base + HDLCD_REG_COMMAND); +} + +static int hdlcd_set_bitfields(struct hdlcd_device *hdlcd, + struct fb_var_screeninfo *var) +{ + int ret = 0; + + memset(&var->transp, 0, sizeof(var->transp)); + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->blue.offset = 0; + + switch (var->bits_per_pixel) { + case 8: + /* pseudocolor */ + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + case 16: + /* 565 format */ + var->red.length = 5; + var->green.length = 6; + var->blue.length = 5; + break; + case 32: + var->transp.length = 8; + case 24: + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; + default: + ret = -EINVAL; + break; + } + + if (!ret) { + var->green.offset = var->blue.length; + var->red.offset = var->green.offset + var->green.length; + if (var->bits_per_pixel == 32) + var->transp.offset = var->red.offset + var->red.length; + } + + return ret; +} + +static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + int bytes_per_pixel = var->bits_per_pixel / 8; + + var->yres_virtual = 2 * var->yres; + + if ((var->xres_virtual * bytes_per_pixel * var->yres_virtual) > hdlcd->fb.fix.smem_len) + return -ENOMEM; + + if (var->xres > HDLCD_MAX_XRES || var->yres > HDLCD_MAX_YRES) + return -EINVAL; + + /* make sure the bitfields are set appropriately */ + return hdlcd_set_bitfields(hdlcd, var); +} + +#ifdef CONFIG_SERIAL_AMBA_PCU_UART +static int hdlcd_set_output_mode(int xres, int yres) +{ + /* firmware uses some stupid protocol: 5 bytes (only byte 1 used) + to send 3 bits of information (value between 0 - 5) */ + u8 msgbuffer[5]; + + memset(msgbuffer, 0, sizeof(msgbuffer)); + /* default resolution: 640 x 480 */ + if (xres == 800 && yres <= 600) + msgbuffer[0] = 1; /* SVGA: 800 * 600 */ + else if (xres == 1024 && yres <= 768) + msgbuffer[0] = 2; /* XGA: 1024 * 768 */ + else if (xres == 1280 && yres <= 1024) + msgbuffer[0] = 3; /* SXGA: 1280 * 1024 */ + else if (xres == 1600 && yres <= 1200) + msgbuffer[0] = 4; /* UXGA: 1600 * 1200 */ + else if (xres == 1920 && yres <= 1200) + msgbuffer[0] = 5; /* WUXGA: 1920 * 1200 */ + + return set_dvi_mode(msgbuffer); +} +#else +inline int hdlcd_set_output_mode(int xres, int yres) +{ + return 0; +} +#endif + +#define WRITE_HDLCD_REG(reg, value) writel((value), hdlcd->base + (reg)) +#define READ_HDLCD_REG(reg) readl(hdlcd->base + (reg)) + +static int hdlcd_set_par(struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + int bytes_per_pixel = hdlcd->fb.var.bits_per_pixel / 8; + int polarities; + + if (!memcmp(&info->var, &cached_var_screeninfo, sizeof(struct fb_var_screeninfo))) + return 0; + + hdlcd->fb.fix.line_length = hdlcd->fb.var.xres * bytes_per_pixel; + + if (hdlcd->fb.var.bits_per_pixel >= 16) + hdlcd->fb.fix.visual = FB_VISUAL_TRUECOLOR; + else + hdlcd->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR; + + memcpy(&cached_var_screeninfo, &info->var, sizeof(struct fb_var_screeninfo)); + + polarities = HDLCD_POLARITY_DATAEN | +#ifndef CONFIG_ARCH_TUSCAN + HDLCD_POLARITY_PIXELCLK | +#endif + HDLCD_POLARITY_DATA; + polarities |= (hdlcd->fb.var.sync & FB_SYNC_HOR_HIGH_ACT) ? HDLCD_POLARITY_HSYNC : 0; + polarities |= (hdlcd->fb.var.sync & FB_SYNC_VERT_HIGH_ACT) ? HDLCD_POLARITY_VSYNC : 0; + + hdlcd_disable(hdlcd); + + WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_LENGTH, hdlcd->fb.var.xres * bytes_per_pixel); + WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_PITCH, hdlcd->fb.var.xres * bytes_per_pixel); + WRITE_HDLCD_REG(HDLCD_REG_FB_LINE_COUNT, hdlcd->fb.var.yres - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_SYNC, hdlcd->fb.var.vsync_len - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_BACK_PORCH, hdlcd->fb.var.upper_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_DATA, hdlcd->fb.var.yres - 1); + WRITE_HDLCD_REG(HDLCD_REG_V_FRONT_PORCH, hdlcd->fb.var.lower_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_SYNC, hdlcd->fb.var.hsync_len - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_BACK_PORCH, hdlcd->fb.var.left_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_DATA, hdlcd->fb.var.xres - 1); + WRITE_HDLCD_REG(HDLCD_REG_H_FRONT_PORCH, hdlcd->fb.var.right_margin - 1); + WRITE_HDLCD_REG(HDLCD_REG_POLARITIES, polarities); + WRITE_HDLCD_REG(HDLCD_REG_PIXEL_FORMAT, (bytes_per_pixel - 1) << 3); + WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, ((hdlcd->fb.var.red.length & 0xf) << 8) | hdlcd->fb.var.red.offset); + WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); + WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); + + hdlcd_set_output_mode(hdlcd->fb.var.xres, hdlcd->fb.var.yres); + + clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); + clk_enable(hdlcd->clk); + + hdlcd_enable(hdlcd); + + return 0; +} + +static int hdlcd_setcolreg(unsigned int regno, unsigned int red, unsigned int green, + unsigned int blue, unsigned int transp, struct fb_info *info) +{ + if (regno < 16) { + u32 *pal = info->pseudo_palette; + + pal[regno] = ((red >> 8) << info->var.red.offset) | + ((green >> 8) << info->var.green.offset) | + ((blue >> 8) << info->var.blue.offset); + } + + return 0; +} + +static irqreturn_t hdlcd_irq(int irq, void *data) +{ + struct hdlcd_device *hdlcd = data; + unsigned long irq_mask, irq_status; + + irq_mask = READ_HDLCD_REG(HDLCD_REG_INT_MASK); + irq_status = READ_HDLCD_REG(HDLCD_REG_INT_STATUS); + + /* acknowledge interrupt(s) */ + WRITE_HDLCD_REG(HDLCD_REG_INT_CLEAR, irq_status); + + if (irq_status & HDLCD_INTERRUPT_VSYNC) { + /* disable future VSYNC interrupts */ + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask & ~HDLCD_INTERRUPT_VSYNC); + + complete(&hdlcd->vsync_completion); + } + + return IRQ_HANDLED; +} + +static int hdlcd_wait_for_vsync(struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + unsigned long irq_mask; + int err; + + /* enable VSYNC interrupt */ + irq_mask = READ_HDLCD_REG(HDLCD_REG_INT_MASK); + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask | HDLCD_INTERRUPT_VSYNC); + + err = wait_for_completion_interruptible_timeout(&hdlcd->vsync_completion, + msecs_to_jiffies(100)); + + if (!err) + return -ETIMEDOUT; + + return 0; +} + +static int hdlcd_blank(int blank_mode, struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + + switch (blank_mode) { + case FB_BLANK_POWERDOWN: + clk_disable(hdlcd->clk); + case FB_BLANK_NORMAL: + hdlcd_disable(hdlcd); + break; + case FB_BLANK_UNBLANK: + clk_enable(hdlcd->clk); + hdlcd_enable(hdlcd); + break; + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + default: + return 1; + } + + return 0; +} + +static void hdlcd_mmap_open(struct vm_area_struct *vma) +{ +} + +static void hdlcd_mmap_close(struct vm_area_struct *vma) +{ +} + +static struct vm_operations_struct hdlcd_mmap_ops = { + .open = hdlcd_mmap_open, + .close = hdlcd_mmap_close, +}; + +static int hdlcd_mmap(struct fb_info *info, struct vm_area_struct *vma) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + unsigned long off; + unsigned long start; + unsigned long len = hdlcd->fb.fix.smem_len; + + if (vma->vm_end - vma->vm_start == 0) + return 0; + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + + off = vma->vm_pgoff << PAGE_SHIFT; + if ((off >= len) || (vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + + start = hdlcd->fb.fix.smem_start; + off += start; + + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &hdlcd_mmap_ops; + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static int hdlcd_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) +{ + struct hdlcd_device *hdlcd = to_hdlcd_device(info); + + hdlcd->fb.var.yoffset = var->yoffset; + WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start + + (var->yoffset * hdlcd->fb.fix.line_length)); + + hdlcd_wait_for_vsync(info); + + return 0; +} + +static int hdlcd_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +{ + int err; + + switch (cmd) { + case FBIO_WAITFORVSYNC: + err = hdlcd_wait_for_vsync(info); + break; + default: + err = -ENOIOCTLCMD; + break; + } + + return err; +} + +static struct fb_ops hdlcd_ops = { + .owner = THIS_MODULE, + .fb_check_var = hdlcd_check_var, + .fb_set_par = hdlcd_set_par, + .fb_setcolreg = hdlcd_setcolreg, + .fb_blank = hdlcd_blank, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_mmap = hdlcd_mmap, + .fb_pan_display = hdlcd_pan_display, + .fb_ioctl = hdlcd_ioctl, + .fb_compat_ioctl = hdlcd_ioctl +}; + +static int hdlcd_setup(struct hdlcd_device *hdlcd) +{ + u32 version; + int err = -EFAULT; + + hdlcd->clk = clk_get(hdlcd->dev, NULL); + if (IS_ERR(hdlcd->clk)) { + dev_err(hdlcd->dev, "HDLCD: unable to find clock data\n"); + return PTR_ERR(hdlcd->clk); + } + + hdlcd->base = ioremap_nocache(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len); + if (!hdlcd->base) { + dev_err(hdlcd->dev, "HDLCD: unable to map registers\n"); + goto remap_err; + } + + hdlcd->fb.pseudo_palette = kmalloc(sizeof(u32) * 16, GFP_KERNEL); + if (!hdlcd->fb.pseudo_palette) { + dev_err(hdlcd->dev, "HDLCD: unable to allocate pseudo_palette memory\n"); + err = -ENOMEM; + goto kmalloc_err; + } + + version = readl(hdlcd->base + HDLCD_REG_VERSION); + if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) { + dev_err(hdlcd->dev, "HDLCD: unknown product id: 0x%x\n", version); + err = -EINVAL; + goto kmalloc_err; + } + dev_info(hdlcd->dev, "HDLCD: found ARM HDLCD version r%dp%d\n", + (version & HDLCD_VERSION_MAJOR_MASK) >> 8, + version & HDLCD_VERSION_MINOR_MASK); + + strcpy(hdlcd->fb.fix.id, "hdlcd"); + hdlcd->fb.fbops = &hdlcd_ops; + hdlcd->fb.flags = FBINFO_FLAG_DEFAULT/* | FBINFO_VIRTFB*/; + + hdlcd->fb.fix.type = FB_TYPE_PACKED_PIXELS; + hdlcd->fb.fix.type_aux = 0; + hdlcd->fb.fix.xpanstep = 0; + hdlcd->fb.fix.ypanstep = 1; + hdlcd->fb.fix.ywrapstep = 0; + hdlcd->fb.fix.accel = FB_ACCEL_NONE; + + hdlcd->fb.var.nonstd = 0; + hdlcd->fb.var.activate = FB_ACTIVATE_NOW; + hdlcd->fb.var.height = -1; + hdlcd->fb.var.width = -1; + hdlcd->fb.var.accel_flags = 0; + + init_completion(&hdlcd->vsync_completion); + + if (hdlcd->edid) { + /* build modedb from EDID */ + fb_edid_to_monspecs(hdlcd->edid, &hdlcd->fb.monspecs); + fb_videomode_to_modelist(hdlcd->fb.monspecs.modedb, + hdlcd->fb.monspecs.modedb_len, + &hdlcd->fb.modelist); + fb_find_mode(&hdlcd->fb.var, &hdlcd->fb, fb_mode, + hdlcd->fb.monspecs.modedb, + hdlcd->fb.monspecs.modedb_len, + &hdlcd_default_mode, 32); + } else { + hdlcd->fb.monspecs.hfmin = 0; + hdlcd->fb.monspecs.hfmax = 100000; + hdlcd->fb.monspecs.vfmin = 0; + hdlcd->fb.monspecs.vfmax = 400; + hdlcd->fb.monspecs.dclkmin = 1000000; + hdlcd->fb.monspecs.dclkmax = 100000000; + fb_find_mode(&hdlcd->fb.var, &hdlcd->fb, fb_mode, NULL, 0, &hdlcd_default_mode, 32); + } + + dev_info(hdlcd->dev, "using %dx%d-%d@%d mode\n", hdlcd->fb.var.xres, + hdlcd->fb.var.yres, hdlcd->fb.var.bits_per_pixel, + hdlcd->fb.mode ? hdlcd->fb.mode->refresh : 60); + hdlcd->fb.var.xres_virtual = hdlcd->fb.var.xres; + hdlcd->fb.var.yres_virtual = hdlcd->fb.var.yres * 2; + + /* initialise and set the palette */ + if (fb_alloc_cmap(&hdlcd->fb.cmap, NR_PALETTE, 0)) { + dev_err(hdlcd->dev, "failed to allocate cmap memory\n"); + err = -ENOMEM; + goto setup_err; + } + fb_set_cmap(&hdlcd->fb.cmap, &hdlcd->fb); + + /* Allow max number of outstanding requests with the largest beat burst */ + WRITE_HDLCD_REG(HDLCD_REG_BUS_OPTIONS, HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); + /* Set the framebuffer base to start of allocated memory */ + WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start); + /* Ensure interrupts are disabled */ + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0); + + if (!register_framebuffer(&hdlcd->fb)) { + fb_set_var(&hdlcd->fb, &hdlcd->fb.var); + clk_enable(hdlcd->clk); + return 0; + } + + dev_err(hdlcd->dev, "HDLCD: cannot register framebuffer\n"); + + fb_dealloc_cmap(&hdlcd->fb.cmap); +setup_err: + iounmap(hdlcd->base); +kmalloc_err: + kfree(hdlcd->fb.pseudo_palette); +remap_err: + clk_put(hdlcd->clk); + return err; +} + +static inline unsigned char atohex(u8 data) +{ + if (!isxdigit(data)) + return 0; + /* truncate the upper nibble and add 9 to non-digit values */ + return (data > 0x39) ? ((data & 0xf) + 9) : (data & 0xf); +} + +/* EDID data is passed from devicetree in a literal string that can contain spaces and + the hexadecimal dump of the data */ +static int parse_edid_data(struct hdlcd_device *hdlcd, const u8 *edid_data, int data_len) +{ + int i, j; + + if (!edid_data) + return -EINVAL; + + hdlcd->edid = kzalloc(EDID_LENGTH, GFP_KERNEL); + if (!hdlcd->edid) + return -ENOMEM; + + for (i = 0, j = 0; i < data_len; i++) { + if (isspace(edid_data[i])) + continue; + hdlcd->edid[j++] = atohex(edid_data[i]); + if (j >= EDID_LENGTH) + break; + } + + if (j < EDID_LENGTH) { + kfree(hdlcd->edid); + hdlcd->edid = NULL; + return -EINVAL; + } + + return 0; +} + +static int hdlcd_probe(struct platform_device *pdev) +{ + int err = 0, i; + struct hdlcd_device *hdlcd; + struct resource *mem; +#ifdef CONFIG_OF + struct device_node *of_node; +#endif + + memset(&cached_var_screeninfo, 0, sizeof(struct fb_var_screeninfo)); + + dev_dbg(&pdev->dev, "HDLCD: probing\n"); + + hdlcd = kzalloc(sizeof(*hdlcd), GFP_KERNEL); + if (!hdlcd) + return -ENOMEM; + +#ifdef CONFIG_OF + of_node = pdev->dev.of_node; + if (of_node) { + int len; + const u8 *edid; + const void *prop = of_get_property(of_node, "mode", &len); + if (prop) + strncpy(fb_mode, prop, len); + prop = of_get_property(of_node, "framebuffer", &len); + if (prop) { + hdlcd->fb.fix.smem_start = of_read_ulong((const __be32 *) prop, 1); + framebuffer_size = of_read_ulong((const __be32 *)prop, 1); + if (framebuffer_size > HDLCD_MAX_FRAMEBUFFER_SIZE) + framebuffer_size = HDLCD_MAX_FRAMEBUFFER_SIZE; + dev_dbg(&pdev->dev, "HDLCD: phys_addr = 0x%lx, size = 0x%lx\n", + hdlcd->fb.fix.smem_start, framebuffer_size); + } + edid = of_get_property(of_node, "edid", &len); + if (edid) { + err = parse_edid_data(hdlcd, edid, len); +#ifdef CONFIG_SERIAL_AMBA_PCU_UART + } else { + /* ask the firmware to fetch the EDID */ + dev_dbg(&pdev->dev, "HDLCD: Requesting EDID data\n"); + hdlcd->edid = kzalloc(EDID_LENGTH, GFP_KERNEL); + if (!hdlcd->edid) + return -ENOMEM; + err = get_edid(hdlcd->edid); +#endif /* CONFIG_SERIAL_AMBA_PCU_UART */ + } + if (err) + dev_info(&pdev->dev, "HDLCD: Failed to parse EDID data\n"); + } +#endif /* CONFIG_OF */ + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) { + dev_err(&pdev->dev, "HDLCD: cannot get platform resources\n"); + err = -EINVAL; + goto resource_err; + } + + i = platform_get_irq(pdev, 0); + if (i < 0) { + dev_err(&pdev->dev, "HDLCD: no irq defined for vsync\n"); + err = -ENOENT; + goto resource_err; + } else { + err = request_irq(i, hdlcd_irq, 0, dev_name(&pdev->dev), hdlcd); + if (err) { + dev_err(&pdev->dev, "HDLCD: unable to request irq\n"); + goto resource_err; + } + hdlcd->irq = i; + } + + if (!request_mem_region(mem->start, resource_size(mem), dev_name(&pdev->dev))) { + err = -ENXIO; + goto request_err; + } + + if (!hdlcd->fb.fix.smem_start) { + dev_err(&pdev->dev, "platform did not allocate frame buffer memory\n"); + err = -ENOMEM; + goto memalloc_err; + } + hdlcd->fb.screen_base = ioremap_wc(hdlcd->fb.fix.smem_start, framebuffer_size); + if (!hdlcd->fb.screen_base) { + dev_err(&pdev->dev, "unable to ioremap framebuffer\n"); + err = -ENOMEM; + goto probe_err; + } + + hdlcd->fb.screen_size = framebuffer_size; + hdlcd->fb.fix.smem_len = framebuffer_size; + hdlcd->fb.fix.mmio_start = mem->start; + hdlcd->fb.fix.mmio_len = resource_size(mem); + + /* Clear the framebuffer */ + memset(hdlcd->fb.screen_base, 0, framebuffer_size); + + hdlcd->dev = &pdev->dev; + + dev_dbg(&pdev->dev, "HDLCD: framebuffer virt base %p, phys base 0x%lX\n", + hdlcd->fb.screen_base, (unsigned long)hdlcd->fb.fix.smem_start); + + err = hdlcd_setup(hdlcd); + + if (err) + goto probe_err; + + platform_set_drvdata(pdev, hdlcd); + return 0; + +probe_err: + iounmap(hdlcd->fb.screen_base); + memblock_free(hdlcd->fb.fix.smem_start, hdlcd->fb.fix.smem_start); + +memalloc_err: + release_mem_region(mem->start, resource_size(mem)); + +request_err: + free_irq(hdlcd->irq, hdlcd); + +resource_err: + kfree(hdlcd); + + return err; +} + +static int hdlcd_remove(struct platform_device *pdev) +{ + struct hdlcd_device *hdlcd = platform_get_drvdata(pdev); + + clk_disable(hdlcd->clk); + clk_put(hdlcd->clk); + + /* unmap memory */ + iounmap(hdlcd->fb.screen_base); + iounmap(hdlcd->base); + + /* deallocate fb memory */ + fb_dealloc_cmap(&hdlcd->fb.cmap); + kfree(hdlcd->fb.pseudo_palette); + memblock_free(hdlcd->fb.fix.smem_start, hdlcd->fb.fix.smem_start); + release_mem_region(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len); + + free_irq(hdlcd->irq, NULL); + kfree(hdlcd); + + return 0; +} + +#ifdef CONFIG_PM +static int hdlcd_suspend(struct platform_device *pdev, pm_message_t state) +{ + /* not implemented yet */ + return 0; +} + +static int hdlcd_resume(struct platform_device *pdev) +{ + /* not implemented yet */ + return 0; +} +#else +#define hdlcd_suspend NULL +#define hdlcd_resume NULL +#endif + +static struct platform_driver hdlcd_driver = { + .probe = hdlcd_probe, + .remove = hdlcd_remove, + .suspend = hdlcd_suspend, + .resume = hdlcd_resume, + .driver = { + .name = "hdlcd", + .owner = THIS_MODULE, + .of_match_table = hdlcd_of_matches, + }, +}; + +static int __init hdlcd_init(void) +{ + return platform_driver_register(&hdlcd_driver); +} + +void __exit hdlcd_exit(void) +{ + platform_driver_unregister(&hdlcd_driver); +} + +module_init(hdlcd_init); +module_exit(hdlcd_exit); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("ARM HDLCD core driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h new file mode 100644 index 000000000000..085fbec1fec7 --- /dev/null +++ b/include/linux/arm-hdlcd.h @@ -0,0 +1,100 @@ +/* + * include/linux/arm-hdlcd.h + * + * Copyright (C) 2011 ARM Limited + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive + * for more details. + * + * ARM HDLCD Controller register definition + */ + +#include +#include + +/* register offsets */ +#define HDLCD_REG_VERSION 0x0000 /* ro */ +#define HDLCD_REG_INT_RAWSTAT 0x0010 /* rw */ +#define HDLCD_REG_INT_CLEAR 0x0014 /* wo */ +#define HDLCD_REG_INT_MASK 0x0018 /* rw */ +#define HDLCD_REG_INT_STATUS 0x001c /* ro */ +#define HDLCD_REG_USER_OUT 0x0020 /* rw */ +#define HDLCD_REG_FB_BASE 0x0100 /* rw */ +#define HDLCD_REG_FB_LINE_LENGTH 0x0104 /* rw */ +#define HDLCD_REG_FB_LINE_COUNT 0x0108 /* rw */ +#define HDLCD_REG_FB_LINE_PITCH 0x010c /* rw */ +#define HDLCD_REG_BUS_OPTIONS 0x0110 /* rw */ +#define HDLCD_REG_V_SYNC 0x0200 /* rw */ +#define HDLCD_REG_V_BACK_PORCH 0x0204 /* rw */ +#define HDLCD_REG_V_DATA 0x0208 /* rw */ +#define HDLCD_REG_V_FRONT_PORCH 0x020c /* rw */ +#define HDLCD_REG_H_SYNC 0x0210 /* rw */ +#define HDLCD_REG_H_BACK_PORCH 0x0214 /* rw */ +#define HDLCD_REG_H_DATA 0x0218 /* rw */ +#define HDLCD_REG_H_FRONT_PORCH 0x021c /* rw */ +#define HDLCD_REG_POLARITIES 0x0220 /* rw */ +#define HDLCD_REG_COMMAND 0x0230 /* rw */ +#define HDLCD_REG_PIXEL_FORMAT 0x0240 /* rw */ +#define HDLCD_REG_BLUE_SELECT 0x0244 /* rw */ +#define HDLCD_REG_GREEN_SELECT 0x0248 /* rw */ +#define HDLCD_REG_RED_SELECT 0x024c /* rw */ + +/* version */ +#define HDLCD_PRODUCT_ID 0x1CDC0000 +#define HDLCD_PRODUCT_MASK 0xFFFF0000 +#define HDLCD_VERSION_MAJOR_MASK 0x0000FF00 +#define HDLCD_VERSION_MINOR_MASK 0x000000FF + +/* interrupts */ +#define HDLCD_INTERRUPT_DMA_END (1 << 0) +#define HDLCD_INTERRUPT_BUS_ERROR (1 << 1) +#define HDLCD_INTERRUPT_VSYNC (1 << 2) +#define HDLCD_INTERRUPT_UNDERRUN (1 << 3) + +/* polarity */ +#define HDLCD_POLARITY_VSYNC (1 << 0) +#define HDLCD_POLARITY_HSYNC (1 << 1) +#define HDLCD_POLARITY_DATAEN (1 << 2) +#define HDLCD_POLARITY_DATA (1 << 3) +#define HDLCD_POLARITY_PIXELCLK (1 << 4) + +/* commands */ +#define HDLCD_COMMAND_DISABLE (0 << 0) +#define HDLCD_COMMAND_ENABLE (1 << 0) + +/* pixel format */ +#define HDLCD_PIXEL_FMT_LITTLE_ENDIAN (0 << 31) +#define HDLCD_PIXEL_FMT_BIG_ENDIAN (1 << 31) +#define HDLCD_BYTES_PER_PIXEL_MASK (3 << 3) + +/* bus options */ +#define HDLCD_BUS_BURST_MASK 0x01f +#define HDLCD_BUS_MAX_OUTSTAND 0xf00 +#define HDLCD_BUS_BURST_NONE (0 << 0) +#define HDLCD_BUS_BURST_1 (1 << 0) +#define HDLCD_BUS_BURST_2 (1 << 1) +#define HDLCD_BUS_BURST_4 (1 << 2) +#define HDLCD_BUS_BURST_8 (1 << 3) +#define HDLCD_BUS_BURST_16 (1 << 4) + +/* Max resolution supported is 4096x4096, 8 bit per color component, + 8 bit alpha, but we are going to choose the usual hardware default + (2048x2048, 32 bpp) and enable double buffering */ +#define HDLCD_MAX_XRES 2048 +#define HDLCD_MAX_YRES 2048 +#define HDLCD_MAX_FRAMEBUFFER_SIZE (HDLCD_MAX_XRES * HDLCD_MAX_YRES << 2) + +#define HDLCD_MEM_BASE (CONFIG_PAGE_OFFSET - 0x1000000) + +#define NR_PALETTE 256 + +struct hdlcd_device { + struct fb_info fb; + struct device *dev; + struct clk *clk; + void __iomem *base; + int irq; + struct completion vsync_completion; + unsigned char *edid; +}; -- cgit v1.2.3 From 7c7d3b31d5e22bed91202c022932e4bde0cf1822 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 13 Jun 2012 09:45:43 +0100 Subject: ARM HDLCD: Add missing clk_{un}prepare calls Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 3e103a5c9b57..221004379c75 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -219,6 +219,7 @@ static int hdlcd_set_par(struct fb_info *info) hdlcd_set_output_mode(hdlcd->fb.var.xres, hdlcd->fb.var.yres); + clk_prepare(hdlcd->clk); clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); clk_enable(hdlcd->clk); @@ -674,6 +675,7 @@ static int hdlcd_remove(struct platform_device *pdev) struct hdlcd_device *hdlcd = platform_get_drvdata(pdev); clk_disable(hdlcd->clk); + clk_unprepare(hdlcd->clk); clk_put(hdlcd->clk); /* unmap memory */ -- cgit v1.2.3 From 51f1911828c30b0ce27825452cef4db5a00edb07 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 13 Jul 2012 13:32:48 +0100 Subject: ARM HDLCD: Fixup driver to resemble ARM's latest codebase. Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 221004379c75..948d41c14130 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -29,10 +29,12 @@ #include "edid.h" -#ifdef CONFIG_SERIAL_AMBA_PCU_UART /* set the DVI output mode using the firmware */ -int set_dvi_mode(u8 *msgbuf); +extern int set_dvi_mode(int mode); + +#ifdef CONFIG_SERIAL_AMBA_PCU_UART int get_edid(u8 *msgbuf); +#else #endif #define to_hdlcd_device(info) container_of(info, struct hdlcd_device, fb) @@ -140,34 +142,29 @@ static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return hdlcd_set_bitfields(hdlcd, var); } -#ifdef CONFIG_SERIAL_AMBA_PCU_UART static int hdlcd_set_output_mode(int xres, int yres) { - /* firmware uses some stupid protocol: 5 bytes (only byte 1 used) + /* some firmware uses some stupid protocol: 5 bytes (only byte 1 used) to send 3 bits of information (value between 0 - 5) */ u8 msgbuffer[5]; memset(msgbuffer, 0, sizeof(msgbuffer)); - /* default resolution: 640 x 480 */ - if (xres == 800 && yres <= 600) + + if (xres < 800 && yres < 600) + msgbuffer[0] = 0; /* default resolution: 640 x 480 */ + else if (xres < 1024 && yres < 768) msgbuffer[0] = 1; /* SVGA: 800 * 600 */ - else if (xres == 1024 && yres <= 768) + else if (xres < 1280 && yres < 1024) msgbuffer[0] = 2; /* XGA: 1024 * 768 */ - else if (xres == 1280 && yres <= 1024) + else if (xres < 1600 && yres < 1200) msgbuffer[0] = 3; /* SXGA: 1280 * 1024 */ - else if (xres == 1600 && yres <= 1200) + else if (xres < 1920 && yres <= 1200) msgbuffer[0] = 4; /* UXGA: 1600 * 1200 */ - else if (xres == 1920 && yres <= 1200) + else msgbuffer[0] = 5; /* WUXGA: 1920 * 1200 */ - return set_dvi_mode(msgbuffer); -} -#else -inline int hdlcd_set_output_mode(int xres, int yres) -{ - return 0; + return set_dvi_mode(msgbuffer[0]); } -#endif #define WRITE_HDLCD_REG(reg, value) writel((value), hdlcd->base + (reg)) #define READ_HDLCD_REG(reg) readl(hdlcd->base + (reg)) @@ -565,13 +562,16 @@ static int hdlcd_probe(struct platform_device *pdev) if (of_node) { int len; const u8 *edid; - const void *prop = of_get_property(of_node, "mode", &len); + const __be32 *prop = of_get_property(of_node, "mode", &len); if (prop) - strncpy(fb_mode, prop, len); + strncpy(fb_mode, (char *)prop, len); prop = of_get_property(of_node, "framebuffer", &len); if (prop) { - hdlcd->fb.fix.smem_start = of_read_ulong((const __be32 *) prop, 1); - framebuffer_size = of_read_ulong((const __be32 *)prop, 1); + hdlcd->fb.fix.smem_start = of_read_ulong(prop, + of_n_addr_cells(of_node)); + prop += of_n_addr_cells(of_node); + framebuffer_size = of_read_ulong(prop, + of_n_size_cells(of_node)); if (framebuffer_size > HDLCD_MAX_FRAMEBUFFER_SIZE) framebuffer_size = HDLCD_MAX_FRAMEBUFFER_SIZE; dev_dbg(&pdev->dev, "HDLCD: phys_addr = 0x%lx, size = 0x%lx\n", -- cgit v1.2.3 From f03c663576a50642f1e9f142627a86de61a6574e Mon Sep 17 00:00:00 2001 From: Dietmar Eggemann Date: Thu, 21 Jun 2012 17:16:33 +0100 Subject: ARM HDLCD: Get it working under Android. Add a shortcut when set_par is called to only change the yoffset. Android is doing that instead of calling pan_display to flip the framebuffer. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 948d41c14130..526d771db991 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -166,6 +166,11 @@ static int hdlcd_set_output_mode(int xres, int yres) return set_dvi_mode(msgbuffer[0]); } + +/* prototype */ +static int hdlcd_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info); + #define WRITE_HDLCD_REG(reg, value) writel((value), hdlcd->base + (reg)) #define READ_HDLCD_REG(reg) readl(hdlcd->base + (reg)) @@ -174,9 +179,22 @@ static int hdlcd_set_par(struct fb_info *info) struct hdlcd_device *hdlcd = to_hdlcd_device(info); int bytes_per_pixel = hdlcd->fb.var.bits_per_pixel / 8; int polarities; - - if (!memcmp(&info->var, &cached_var_screeninfo, sizeof(struct fb_var_screeninfo))) + int old_yoffset; + + /* check for shortcuts */ + old_yoffset = cached_var_screeninfo.yoffset; + cached_var_screeninfo.yoffset = info->var.yoffset; + if (!memcmp(&info->var, &cached_var_screeninfo, + sizeof(struct fb_var_screeninfo))) { + if(old_yoffset != info->var.yoffset) { + /* we only changed yoffset */ + hdlcd_pan_display(&info->var, info); + memcpy(&cached_var_screeninfo, &info->var, + sizeof(struct fb_var_screeninfo)); + } + /* or no change */ return 0; + } hdlcd->fb.fix.line_length = hdlcd->fb.var.xres * bytes_per_pixel; -- cgit v1.2.3 From 585617333b9f883182dd285b50710deab04d4e00 Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Fri, 29 Jun 2012 13:10:35 +0100 Subject: ARM HDLCD: Change default byte ordering to match Android assumption Change color byte location in 32-bit word from argb to abgr to match the assumption made in Android when 32-bit color displays are used. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 526d771db991..9b5b21ab136f 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -116,10 +116,25 @@ static int hdlcd_set_bitfields(struct hdlcd_device *hdlcd, } if (!ret) { - var->green.offset = var->blue.length; - var->red.offset = var->green.offset + var->green.length; - if (var->bits_per_pixel == 32) - var->transp.offset = var->red.offset + var->red.length; + if(var->bits_per_pixel != 32) + { + var->green.offset = var->blue.length; + var->red.offset = var->green.offset + var->green.length; + } + else + { + /* Previously, the byte ordering for 32-bit color was + * (msb)(lsb) + * but this does not match what android expects and + * the colors are odd. Instead, use + * + * Since we tell fb what we are doing, console + * , X and directfb access should work fine. + */ + var->green.offset = var->red.length; + var->blue.offset = var->green.offset + var->green.length; + var->transp.offset = var->blue.offset + var->blue.length; + } } return ret; -- cgit v1.2.3 From 7195cb14440127f4bcda912dd51cfc2760f9f25e Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Fri, 29 Jun 2012 16:07:08 +0100 Subject: ARM HDLCD: Add useful functions to HDLCD driver for system integration During TC2 integration a bad config option resulted in HDLCD memory reads not being serviced often enough. This lead to unsightly screen blanking. These options allow the developer to count the number of underruns and to control the color used by HDLCD when an underrun prevents accessing pixel data. The combination of these two options allow easy diagnosis of HDLCD underrun conditions. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 90 +++++++++++++++++++++++++++++++++++++++++++++-- include/linux/arm-hdlcd.h | 14 ++++++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 9b5b21ab136f..8d5409db6b17 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -26,6 +26,10 @@ #include #include #include +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS +#include +#include +#endif #include "edid.h" @@ -47,6 +51,63 @@ static struct of_device_id hdlcd_of_matches[] = { /* Framebuffer size. */ static unsigned long framebuffer_size; +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS +static unsigned long buffer_underrun_events; +static DEFINE_SPINLOCK(hdlcd_underrun_lock); + +static void hdlcd_underrun_set(unsigned long val) +{ + spin_lock(&hdlcd_underrun_lock); + buffer_underrun_events = val; + spin_unlock(&hdlcd_underrun_lock); +} + +static unsigned long hdlcd_underrun_get(void) +{ + unsigned long val; + spin_lock(&hdlcd_underrun_lock); + val = buffer_underrun_events; + spin_unlock(&hdlcd_underrun_lock); + return val; +} + +#ifdef CONFIG_PROC_FS +static int hdlcd_underrun_show(struct seq_file *m, void *v) +{ + unsigned char underrun_string[32]; + snprintf(underrun_string, 32, "%lu\n", hdlcd_underrun_get()); + seq_puts(m, underrun_string); + return 0; +} + +static int proc_hdlcd_underrun_open(struct inode *inode, struct file *file) +{ + return single_open(file, hdlcd_underrun_show, NULL); +} + +static const struct file_operations proc_hdlcd_underrun_operations = { + .open = proc_hdlcd_underrun_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int hdlcd_underrun_init(void) +{ + hdlcd_underrun_set(0); + proc_create("hdlcd_underrun", 0, NULL, &proc_hdlcd_underrun_operations); + return 0; +} +static void hdlcd_underrun_close(void) +{ + remove_proc_entry("hdlcd_underrun", NULL); +} +#else +static int hdlcd_underrun_init(void) { return 0; } +static void hdlcd_underrun_close(void) { } +#endif +#endif + static char *fb_mode = "1680x1050-32@60\0\0\0\0\0"; static struct fb_var_screeninfo cached_var_screeninfo; @@ -66,7 +127,6 @@ static struct fb_videomode hdlcd_default_mode = { .vmode = FB_VMODE_NONINTERLACED }; - static inline void hdlcd_enable(struct hdlcd_device *hdlcd) { dev_dbg(hdlcd->dev, "HDLCD: output enabled\n"); @@ -243,7 +303,12 @@ static int hdlcd_set_par(struct fb_info *info) WRITE_HDLCD_REG(HDLCD_REG_H_FRONT_PORCH, hdlcd->fb.var.right_margin - 1); WRITE_HDLCD_REG(HDLCD_REG_POLARITIES, polarities); WRITE_HDLCD_REG(HDLCD_REG_PIXEL_FORMAT, (bytes_per_pixel - 1) << 3); +#ifdef HDLCD_RED_DEFAULT_COLOUR + WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, (0x00ff0000 | (hdlcd->fb.var.red.length & 0xf) << 8) \ + | hdlcd->fb.var.red.offset); +#else WRITE_HDLCD_REG(HDLCD_REG_RED_SELECT, ((hdlcd->fb.var.red.length & 0xf) << 8) | hdlcd->fb.var.red.offset); +#endif WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); @@ -282,7 +347,12 @@ static irqreturn_t hdlcd_irq(int irq, void *data) /* acknowledge interrupt(s) */ WRITE_HDLCD_REG(HDLCD_REG_INT_CLEAR, irq_status); - +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + if (irq_status & HDLCD_INTERRUPT_UNDERRUN) { + /* increment the count */ + hdlcd_underrun_set(hdlcd_underrun_get() + 1); + } +#endif if (irq_status & HDLCD_INTERRUPT_VSYNC) { /* disable future VSYNC interrupts */ WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, irq_mask & ~HDLCD_INTERRUPT_VSYNC); @@ -514,9 +584,13 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) WRITE_HDLCD_REG(HDLCD_REG_BUS_OPTIONS, HDLCD_BUS_MAX_OUTSTAND | HDLCD_BUS_BURST_16); /* Set the framebuffer base to start of allocated memory */ WRITE_HDLCD_REG(HDLCD_REG_FB_BASE, hdlcd->fb.fix.smem_start); +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + /* turn on underrun interrupt for counting */ + WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, HDLCD_INTERRUPT_UNDERRUN); +#else /* Ensure interrupts are disabled */ WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0); - +#endif if (!register_framebuffer(&hdlcd->fb)) { fb_set_var(&hdlcd->fb, &hdlcd->fb.var); clk_enable(hdlcd->clk); @@ -758,11 +832,21 @@ static struct platform_driver hdlcd_driver = { static int __init hdlcd_init(void) { +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + int err = platform_driver_register(&hdlcd_driver); + if (!err) + hdlcd_underrun_init(); + return err; +#else return platform_driver_register(&hdlcd_driver); +#endif } void __exit hdlcd_exit(void) { +#ifdef HDLCD_COUNT_BUFFERUNDERRUNS + hdlcd_underrun_close(); +#endif platform_driver_unregister(&hdlcd_driver); } diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h index 085fbec1fec7..f3f4887ac4fa 100644 --- a/include/linux/arm-hdlcd.h +++ b/include/linux/arm-hdlcd.h @@ -89,6 +89,20 @@ #define NR_PALETTE 256 +/* OEMs using HDLCD may wish to enable these settings if + * display disruption is apparent and you suspect HDLCD + * access to RAM may be starved. + */ +/* Turn HDLCD default color red instead of black so + * that it's easy to see pixel clock data underruns + * (compared to other visual disruption) + */ +//#define HDLCD_RED_DEFAULT_COLOUR +/* Add a counter in the IRQ handler to count buffer underruns + * and /proc/hdlcd_underrun to read the counter + */ +//#define HDLCD_COUNT_BUFFERUNDERRUNS + struct hdlcd_device { struct fb_info fb; struct device *dev; -- cgit v1.2.3 From 5705f5f9c6d47d59af73145b5febd1b1d5a56775 Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Fri, 29 Jun 2012 16:07:46 +0100 Subject: ARM HDLCD: Review comments from Liviu - extraneous memcpy Signed-off-by: Chris Redpath Reviewed-By: Liviu Dudau --- drivers/video/arm-hdlcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 8d5409db6b17..f16bb882e0c5 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -262,10 +262,10 @@ static int hdlcd_set_par(struct fb_info *info) if (!memcmp(&info->var, &cached_var_screeninfo, sizeof(struct fb_var_screeninfo))) { if(old_yoffset != info->var.yoffset) { - /* we only changed yoffset */ + /* we only changed yoffset, and we already + * already recorded it a couple lines up + */ hdlcd_pan_display(&info->var, info); - memcpy(&cached_var_screeninfo, &info->var, - sizeof(struct fb_var_screeninfo)); } /* or no change */ return 0; -- cgit v1.2.3 From 9f14266f6f6108f0ff67be65da8866f3b757beec Mon Sep 17 00:00:00 2001 From: Chris Redpath Date: Thu, 12 Jul 2012 12:33:13 +0100 Subject: ARM HDLCD: Add developer option to remove double-height framebuffers This option can be used with Android to push the graphics subsystem into a different composition strategy which is more effective when used on hardware where the framebuffer memory is not cacheable. Signed-off-by: Chris Redpath --- drivers/video/arm-hdlcd.c | 8 ++++++++ include/linux/arm-hdlcd.h | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index f16bb882e0c5..9600c70209b2 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -205,7 +205,11 @@ static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) struct hdlcd_device *hdlcd = to_hdlcd_device(info); int bytes_per_pixel = var->bits_per_pixel / 8; +#ifdef HDLCD_NO_VIRTUAL_SCREEN + var->yres_virtual = var->yres; +#else var->yres_virtual = 2 * var->yres; +#endif if ((var->xres_virtual * bytes_per_pixel * var->yres_virtual) > hdlcd->fb.fix.smem_len) return -ENOMEM; @@ -570,7 +574,11 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) hdlcd->fb.var.yres, hdlcd->fb.var.bits_per_pixel, hdlcd->fb.mode ? hdlcd->fb.mode->refresh : 60); hdlcd->fb.var.xres_virtual = hdlcd->fb.var.xres; +#ifdef HDLCD_NO_VIRTUAL_SCREEN + hdlcd->fb.var.yres_virtual = hdlcd->fb.var.yres; +#else hdlcd->fb.var.yres_virtual = hdlcd->fb.var.yres * 2; +#endif /* initialise and set the palette */ if (fb_alloc_cmap(&hdlcd->fb.cmap, NR_PALETTE, 0)) { diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h index f3f4887ac4fa..124995aa34f5 100644 --- a/include/linux/arm-hdlcd.h +++ b/include/linux/arm-hdlcd.h @@ -102,6 +102,10 @@ * and /proc/hdlcd_underrun to read the counter */ //#define HDLCD_COUNT_BUFFERUNDERRUNS +/* Restrict height to 1x screen size + * + */ +//#define HDLCD_NO_VIRTUAL_SCREEN struct hdlcd_device { struct fb_info fb; -- cgit v1.2.3 From e361c22545d9879a7f20f45aaea8b96366092cc4 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 13 Jul 2012 14:25:50 +0100 Subject: ARM HDLCD: Enable HDLCD_NO_VIRTUAL_SCREEN on Android This is a hack which prevents annoying screen flicker in the Android UI. Signed-off-by: Jon Medhurst --- include/linux/arm-hdlcd.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/linux/arm-hdlcd.h b/include/linux/arm-hdlcd.h index 124995aa34f5..939f3a81d56b 100644 --- a/include/linux/arm-hdlcd.h +++ b/include/linux/arm-hdlcd.h @@ -107,6 +107,10 @@ */ //#define HDLCD_NO_VIRTUAL_SCREEN +#ifdef CONFIG_ANDROID +#define HDLCD_NO_VIRTUAL_SCREEN +#endif + struct hdlcd_device { struct fb_info fb; struct device *dev; -- cgit v1.2.3 From ac1b17efc7bae092fd46aeb144ddbf28add50877 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Thu, 4 Oct 2012 12:38:06 +0100 Subject: ARM HDLCD: Changes to get HDLCD working with 've-updates' Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index 9600c70209b2..bd5fe68684a3 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -33,9 +33,6 @@ #include "edid.h" -/* set the DVI output mode using the firmware */ -extern int set_dvi_mode(int mode); - #ifdef CONFIG_SERIAL_AMBA_PCU_UART int get_edid(u8 *msgbuf); #else @@ -221,31 +218,6 @@ static int hdlcd_check_var(struct fb_var_screeninfo *var, struct fb_info *info) return hdlcd_set_bitfields(hdlcd, var); } -static int hdlcd_set_output_mode(int xres, int yres) -{ - /* some firmware uses some stupid protocol: 5 bytes (only byte 1 used) - to send 3 bits of information (value between 0 - 5) */ - u8 msgbuffer[5]; - - memset(msgbuffer, 0, sizeof(msgbuffer)); - - if (xres < 800 && yres < 600) - msgbuffer[0] = 0; /* default resolution: 640 x 480 */ - else if (xres < 1024 && yres < 768) - msgbuffer[0] = 1; /* SVGA: 800 * 600 */ - else if (xres < 1280 && yres < 1024) - msgbuffer[0] = 2; /* XGA: 1024 * 768 */ - else if (xres < 1600 && yres < 1200) - msgbuffer[0] = 3; /* SXGA: 1280 * 1024 */ - else if (xres < 1920 && yres <= 1200) - msgbuffer[0] = 4; /* UXGA: 1600 * 1200 */ - else - msgbuffer[0] = 5; /* WUXGA: 1920 * 1200 */ - - return set_dvi_mode(msgbuffer[0]); -} - - /* prototype */ static int hdlcd_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); @@ -316,8 +288,6 @@ static int hdlcd_set_par(struct fb_info *info) WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); - hdlcd_set_output_mode(hdlcd->fb.var.xres, hdlcd->fb.var.yres); - clk_prepare(hdlcd->clk); clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); clk_enable(hdlcd->clk); @@ -502,6 +472,8 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) u32 version; int err = -EFAULT; + hdlcd->fb.device = hdlcd->dev; + hdlcd->clk = clk_get(hdlcd->dev, NULL); if (IS_ERR(hdlcd->clk)) { dev_err(hdlcd->dev, "HDLCD: unable to find clock data\n"); -- cgit v1.2.3 From e9a113ad8f8d113afd80028561f07426b229a31f Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 5 Mar 2013 09:24:57 +0800 Subject: ARM HDLCD: Fix compilation on Linux 3.9 Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index bd5fe68684a3..f9c4e7490c05 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include -- cgit v1.2.3 From 6b614fae0ee3b62aea5345b80ec8ada919973a90 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Wed, 13 Jun 2012 10:01:28 +0100 Subject: ARM: vexpress: Add support for HDLCD This is a temporary solution to get everything running. Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts | 4 ++++ arch/arm/boot/dts/vexpress-v2p-ca5s.dts | 4 ++++ arch/arm/mach-vexpress/v2m.c | 28 ++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts index 9420053acc14..cc6a8c0cfe33 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15-tc1.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xbf000000 0x01000000; + / { model = "V2P-CA15"; arm,hbi = <0x237>; @@ -57,6 +59,8 @@ interrupts = <0 85 4>; clocks = <&oscclk5>; clock-names = "pxlclk"; + mode = "1024x768-16@60"; + framebuffer = <0 0xff000000 0 0x01000000>; }; memory-controller@2b0a0000 { diff --git a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts index c544a5504591..cf633ed6a1b4 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca5s.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca5s.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xbf000000 0x01000000; + / { model = "V2P-CA5s"; arm,hbi = <0x225>; @@ -59,6 +61,8 @@ interrupts = <0 85 4>; clocks = <&oscclk3>; clock-names = "pxlclk"; + mode = "640x480-16@60"; + framebuffer = <0xbf000000 0x01000000>; }; memory-controller@2a150000 { diff --git a/arch/arm/mach-vexpress/v2m.c b/arch/arm/mach-vexpress/v2m.c index 8802030df98d..c227959e5e4d 100644 --- a/arch/arm/mach-vexpress/v2m.c +++ b/arch/arm/mach-vexpress/v2m.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -373,6 +374,31 @@ MACHINE_START(VEXPRESS, "ARM-Versatile Express") .init_machine = v2m_init, MACHINE_END +static void __init v2m_dt_hdlcd_init(void) +{ + struct device_node *node; + int len, na, ns; + const __be32 *prop; + phys_addr_t fb_base, fb_size; + + node = of_find_compatible_node(NULL, NULL, "arm,hdlcd"); + if (!node) + return; + + na = of_n_addr_cells(node); + ns = of_n_size_cells(node); + + prop = of_get_property(node, "framebuffer", &len); + if (WARN_ON(!prop || len < (na + ns) * sizeof(*prop))) + return; + + fb_base = of_read_number(prop, na); + fb_size = of_read_number(prop + na, ns); + + if (WARN_ON(memblock_remove(fb_base, fb_size))) + return; +}; + static struct map_desc v2m_rs1_io_desc __initdata = { .virtual = V2M_PERIPH, .pfn = __phys_to_pfn(0x1c000000), @@ -423,6 +449,8 @@ void __init v2m_dt_init_early(void) pr_warning("vexpress: DT HBI (%x) is not matching " "hardware (%x)!\n", dt_hbi, hbi); } + + v2m_dt_hdlcd_init(); } static void __init v2m_dt_timer_init(void) -- cgit v1.2.3 From 0e2f1857008ef538fe170a914427aad552f61ada Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Tue, 26 Mar 2013 13:06:11 +0000 Subject: ARM: vexpress: Update TC2 device tree for HDLCD hack Signed-off-by: Jon Medhurst --- arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts index d2803be4e1a8..6dfa9fa29a08 100644 --- a/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts +++ b/arch/arm/boot/dts/vexpress-v2p-ca15_a7.dts @@ -9,6 +9,8 @@ /dts-v1/; +/memreserve/ 0xff000000 0x01000000; + / { model = "V2P-CA15_CA7"; arm,hbi = <0x249>; @@ -81,6 +83,8 @@ compatible = "arm,hdlcd"; reg = <0 0x2b000000 0 0x1000>; interrupts = <0 85 4>; + mode = "1024x768-16@60"; + framebuffer = <0 0xff000000 0 0x01000000>; clocks = <&oscclk5>; clock-names = "pxlclk"; }; -- cgit v1.2.3 From 83bce1182d983be01f3247f974654cdac5690436 Mon Sep 17 00:00:00 2001 From: Jon Medhurst Date: Fri, 14 Jun 2013 18:30:08 +0100 Subject: ARM HDLCD: Fix clock initialisation sequence This reworks HDLCD initialisation to mirror how CLCD does this, in particular to prepare the clock immediately after it has been got which ensures that we don't try and enable clocks before they were prepared, e.g. in the former clk_enable after register_framebuffer(). The reason this issue wasn't noticed before is that we have been setting CONFIG_FRAMEBUFFER_CONSOLE and this caused register_framebuffer() to trigger the creation of a console which calls hdlcd_set_par(), which in turn was preparing and enabling the clock. Signed-off-by: Jon Medhurst --- drivers/video/arm-hdlcd.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/drivers/video/arm-hdlcd.c b/drivers/video/arm-hdlcd.c index f9c4e7490c05..cfd631e3dc52 100644 --- a/drivers/video/arm-hdlcd.c +++ b/drivers/video/arm-hdlcd.c @@ -289,7 +289,6 @@ static int hdlcd_set_par(struct fb_info *info) WRITE_HDLCD_REG(HDLCD_REG_GREEN_SELECT, ((hdlcd->fb.var.green.length & 0xf) << 8) | hdlcd->fb.var.green.offset); WRITE_HDLCD_REG(HDLCD_REG_BLUE_SELECT, ((hdlcd->fb.var.blue.length & 0xf) << 8) | hdlcd->fb.var.blue.offset); - clk_prepare(hdlcd->clk); clk_set_rate(hdlcd->clk, (1000000000 / hdlcd->fb.var.pixclock) * 1000); clk_enable(hdlcd->clk); @@ -481,6 +480,10 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) return PTR_ERR(hdlcd->clk); } + err = clk_prepare(hdlcd->clk); + if (err) + goto clk_prepare_err; + hdlcd->base = ioremap_nocache(hdlcd->fb.fix.mmio_start, hdlcd->fb.fix.mmio_len); if (!hdlcd->base) { dev_err(hdlcd->dev, "HDLCD: unable to map registers\n"); @@ -572,9 +575,9 @@ static int hdlcd_setup(struct hdlcd_device *hdlcd) /* Ensure interrupts are disabled */ WRITE_HDLCD_REG(HDLCD_REG_INT_MASK, 0); #endif + fb_set_var(&hdlcd->fb, &hdlcd->fb.var); + if (!register_framebuffer(&hdlcd->fb)) { - fb_set_var(&hdlcd->fb, &hdlcd->fb.var); - clk_enable(hdlcd->clk); return 0; } @@ -586,6 +589,8 @@ setup_err: kmalloc_err: kfree(hdlcd->fb.pseudo_palette); remap_err: + clk_unprepare(hdlcd->clk); +clk_prepare_err: clk_put(hdlcd->clk); return err; } -- cgit v1.2.3