diff options
Diffstat (limited to 'drivers/video')
-rw-r--r-- | drivers/video/Kconfig | 33 | ||||
-rw-r--r-- | drivers/video/fb_defio.c | 27 | ||||
-rw-r--r-- | drivers/video/sh7760fb.c | 86 | ||||
-rw-r--r-- | drivers/video/sh_mobile_lcdcfb.c | 198 |
4 files changed, 232 insertions, 112 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 3f3ce13fef4..d0c821992a9 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1889,10 +1889,11 @@ config FB_W100 config FB_SH_MOBILE_LCDC tristate "SuperH Mobile LCDC framebuffer support" depends on FB && SUPERH - select FB_CFB_FILLRECT - select FB_CFB_COPYAREA - select FB_CFB_IMAGEBLIT - default m + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + select FB_DEFERRED_IO ---help--- Frame buffer driver for the on-chip SH-Mobile LCD controller. @@ -2021,17 +2022,19 @@ config FB_COBALT depends on FB && MIPS_COBALT config FB_SH7760 - bool "SH7760/SH7763 LCDC support" - depends on FB && (CPU_SUBTYPE_SH7760 || CPU_SUBTYPE_SH7763) - select FB_CFB_FILLRECT - select FB_CFB_COPYAREA - select FB_CFB_IMAGEBLIT - help - Support for the SH7760/SH7763 integrated (D)STN/TFT LCD Controller. - Supports display resolutions up to 1024x1024 pixel, grayscale and - color operation, with depths ranging from 1 bpp to 8 bpp monochrome - and 8, 15 or 16 bpp color; 90 degrees clockwise display rotation for - panels <= 320 pixel horizontal resolution. + bool "SH7760/SH7763/SH7720/SH7721 LCDC support" + depends on FB && (CPU_SUBTYPE_SH7760 || CPU_SUBTYPE_SH7763 \ + || CPU_SUBTYPE_SH7720 || CPU_SUBTYPE_SH7721) + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + ---help--- + Support for the SH7760/SH7763/SH7720/SH7721 integrated + (D)STN/TFT LCD Controller. + Supports display resolutions up to 1024x1024 pixel, grayscale and + color operation, with depths ranging from 1 bpp to 8 bpp monochrome + and 8, 15 or 16 bpp color; 90 degrees clockwise display rotation for + panels <= 320 pixel horizontal resolution. config FB_VIRTUAL tristate "Virtual Frame Buffer support (ONLY FOR TESTING!)" diff --git a/drivers/video/fb_defio.c b/drivers/video/fb_defio.c index 4835bdc4e9f..082026546ae 100644 --- a/drivers/video/fb_defio.c +++ b/drivers/video/fb_defio.c @@ -24,6 +24,19 @@ #include <linux/rmap.h> #include <linux/pagemap.h> +struct page *fb_deferred_io_page(struct fb_info *info, unsigned long offs) +{ + void *screen_base = (void __force *) info->screen_base; + struct page *page; + + if (is_vmalloc_addr(screen_base + offs)) + page = vmalloc_to_page(screen_base + offs); + else + page = pfn_to_page((info->fix.smem_start + offs) >> PAGE_SHIFT); + + return page; +} + /* this is to find and return the vmalloc-ed fb pages */ static int fb_deferred_io_fault(struct vm_area_struct *vma, struct vm_fault *vmf) @@ -31,14 +44,12 @@ static int fb_deferred_io_fault(struct vm_area_struct *vma, unsigned long offset; struct page *page; struct fb_info *info = vma->vm_private_data; - /* info->screen_base is virtual memory */ - void *screen_base = (void __force *) info->screen_base; offset = vmf->pgoff << PAGE_SHIFT; if (offset >= info->fix.smem_len) return VM_FAULT_SIGBUS; - page = vmalloc_to_page(screen_base + offset); + page = fb_deferred_io_page(info, offset); if (!page) return VM_FAULT_SIGBUS; @@ -60,6 +71,10 @@ int fb_deferred_io_fsync(struct file *file, struct dentry *dentry, int datasync) { struct fb_info *info = file->private_data; + /* Skip if deferred io is complied-in but disabled on this fbdev */ + if (!info->fbdefio) + return 0; + /* Kill off the delayed work */ cancel_rearming_delayed_work(&info->deferred_work); @@ -184,7 +199,6 @@ EXPORT_SYMBOL_GPL(fb_deferred_io_open); void fb_deferred_io_cleanup(struct fb_info *info) { - void *screen_base = (void __force *) info->screen_base; struct fb_deferred_io *fbdefio = info->fbdefio; struct page *page; int i; @@ -195,9 +209,12 @@ void fb_deferred_io_cleanup(struct fb_info *info) /* clear out the mapping that we setup */ for (i = 0 ; i < info->fix.smem_len; i += PAGE_SIZE) { - page = vmalloc_to_page(screen_base + i); + page = fb_deferred_io_page(info, i); page->mapping = NULL; } + + info->fbops->fb_mmap = NULL; + mutex_destroy(&fbdefio->lock); } EXPORT_SYMBOL_GPL(fb_deferred_io_cleanup); diff --git a/drivers/video/sh7760fb.c b/drivers/video/sh7760fb.c index 8d0212da451..653bdfee305 100644 --- a/drivers/video/sh7760fb.c +++ b/drivers/video/sh7760fb.c @@ -13,6 +13,8 @@ * * Thanks to Siegfried Schaefer <s.schaefer at schaefer-edv.de> * for his original source and testing! + * + * sh7760_setcolreg get from drivers/video/sh_mobile_lcdcfb.c */ #include <linux/completion.h> @@ -53,29 +55,6 @@ static irqreturn_t sh7760fb_irq(int irq, void *data) return IRQ_HANDLED; } -static void sh7760fb_wait_vsync(struct fb_info *info) -{ - struct sh7760fb_par *par = info->par; - - if (par->pd->novsync) - return; - - iowrite16(ioread16(par->base + LDINTR) & ~VINT_CHECK, - par->base + LDINTR); - - if (par->irq < 0) { - /* poll for vert. retrace: status bit is sticky */ - while (!(ioread16(par->base + LDINTR) & VINT_CHECK)) - cpu_relax(); - } else { - /* a "wait_for_irq_event(par->irq)" would be extremely nice */ - init_completion(&par->vsync); - enable_irq(par->irq); - wait_for_completion(&par->vsync); - disable_irq_nosync(par->irq); - } -} - /* wait_for_lps - wait until power supply has reached a certain state. */ static int wait_for_lps(struct sh7760fb_par *par, int val) { @@ -117,55 +96,28 @@ static int sh7760fb_blank(int blank, struct fb_info *info) return wait_for_lps(par, lps); } -/* set color registers */ -static int sh7760fb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +static int sh7760_setcolreg (u_int regno, + u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) { - struct sh7760fb_par *par = info->par; - u32 s = cmap->start; - u32 l = cmap->len; - u16 *r = cmap->red; - u16 *g = cmap->green; - u16 *b = cmap->blue; - u32 col, tmo; - int ret; + u32 *palette = info->pseudo_palette; - ret = 0; + if (regno >= 16) + return -EINVAL; - sh7760fb_wait_vsync(info); + /* only FB_VISUAL_TRUECOLOR supported */ - /* request palette access */ - iowrite16(LDPALCR_PALEN, par->base + LDPALCR); + red >>= 16 - info->var.red.length; + green >>= 16 - info->var.green.length; + blue >>= 16 - info->var.blue.length; + transp >>= 16 - info->var.transp.length; - /* poll for access grant */ - tmo = 100; - while (!(ioread16(par->base + LDPALCR) & LDPALCR_PALS) && (--tmo)) - cpu_relax(); + palette[regno] = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); - if (!tmo) { - ret = 1; - dev_dbg(info->dev, "no palette access!\n"); - goto out; - } - - while (l && (s < 256)) { - col = ((*r) & 0xff) << 16; - col |= ((*g) & 0xff) << 8; - col |= ((*b) & 0xff); - col &= SH7760FB_PALETTE_MASK; - iowrite32(col, par->base + LDPR(s)); - - if (s < 16) - ((u32 *) (info->pseudo_palette))[s] = s; - - s++; - l--; - r++; - g++; - b++; - } -out: - iowrite16(0, par->base + LDPALCR); - return ret; + return 0; } static void encode_fix(struct fb_fix_screeninfo *fix, struct fb_info *info, @@ -406,7 +358,7 @@ static struct fb_ops sh7760fb_ops = { .owner = THIS_MODULE, .fb_blank = sh7760fb_blank, .fb_check_var = sh7760fb_check_var, - .fb_setcmap = sh7760fb_setcmap, + .fb_setcolreg = sh7760_setcolreg, .fb_set_par = sh7760fb_set_par, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c index efff672fd7b..0e2b8fd24df 100644 --- a/drivers/video/sh_mobile_lcdcfb.c +++ b/drivers/video/sh_mobile_lcdcfb.c @@ -16,7 +16,9 @@ #include <linux/clk.h> #include <linux/platform_device.h> #include <linux/dma-mapping.h> +#include <linux/interrupt.h> #include <video/sh_mobile_lcdc.h> +#include <asm/atomic.h> #define PALETTE_NR 16 @@ -30,11 +32,15 @@ struct sh_mobile_lcdc_chan { u32 pseudo_palette[PALETTE_NR]; struct fb_info info; dma_addr_t dma_handle; + struct fb_deferred_io defio; }; struct sh_mobile_lcdc_priv { void __iomem *base; + int irq; #ifdef CONFIG_HAVE_CLK + atomic_t clk_usecnt; + struct clk *dot_clk; struct clk *clk; #endif unsigned long lddckr; @@ -56,7 +62,7 @@ struct sh_mobile_lcdc_priv { /* per-channel registers */ enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R, - LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR }; + LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR }; static unsigned long lcdc_offs_mainlcd[] = { [LDDCKPAT1R] = 0x400, @@ -66,6 +72,7 @@ static unsigned long lcdc_offs_mainlcd[] = { [LDMT3R] = 0x420, [LDDFR] = 0x424, [LDSM1R] = 0x428, + [LDSM2R] = 0x42c, [LDSA1R] = 0x430, [LDMLSR] = 0x438, [LDHCNR] = 0x448, @@ -83,6 +90,7 @@ static unsigned long lcdc_offs_sublcd[] = { [LDMT3R] = 0x608, [LDDFR] = 0x60c, [LDSM1R] = 0x610, + [LDSM2R] = 0x614, [LDSA1R] = 0x618, [LDMLSR] = 0x620, [LDHCNR] = 0x624, @@ -96,6 +104,8 @@ static unsigned long lcdc_offs_sublcd[] = { #define LCDC_RESET 0x00000100 #define DISPLAY_BEU 0x00000008 #define LCDC_ENABLE 0x00000001 +#define LDINTR_FE 0x00000400 +#define LDINTR_FS 0x00000004 static void lcdc_write_chan(struct sh_mobile_lcdc_chan *chan, int reg_nr, unsigned long data) @@ -170,6 +180,65 @@ struct sh_mobile_lcdc_sys_bus_ops sh_mobile_lcdc_sys_bus_ops = { lcdc_sys_read_data, }; +#ifdef CONFIG_HAVE_CLK +static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) +{ + if (atomic_inc_and_test(&priv->clk_usecnt)) { + clk_enable(priv->clk); + if (priv->dot_clk) + clk_enable(priv->dot_clk); + } +} + +static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) +{ + if (atomic_sub_return(1, &priv->clk_usecnt) == -1) { + if (priv->dot_clk) + clk_disable(priv->dot_clk); + clk_disable(priv->clk); + } +} +#else +static void sh_mobile_lcdc_clk_on(struct sh_mobile_lcdc_priv *priv) {} +static void sh_mobile_lcdc_clk_off(struct sh_mobile_lcdc_priv *priv) {} +#endif + +static void sh_mobile_lcdc_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + struct sh_mobile_lcdc_chan *ch = info->par; + + /* enable clocks before accessing hardware */ + sh_mobile_lcdc_clk_on(ch->lcdc); + + /* trigger panel update */ + lcdc_write_chan(ch, LDSM2R, 1); +} + +static void sh_mobile_lcdc_deferred_io_touch(struct fb_info *info) +{ + struct fb_deferred_io *fbdefio = info->fbdefio; + + if (fbdefio) + schedule_delayed_work(&info->deferred_work, fbdefio->delay); +} + +static irqreturn_t sh_mobile_lcdc_irq(int irq, void *data) +{ + struct sh_mobile_lcdc_priv *priv = data; + unsigned long tmp; + + /* acknowledge interrupt */ + tmp = lcdc_read(priv, _LDINTR); + tmp &= 0xffffff00; /* mask in high 24 bits */ + tmp |= 0x000000ff ^ LDINTR_FS; /* status in low 8 */ + lcdc_write(priv, _LDINTR, tmp); + + /* disable clocks */ + sh_mobile_lcdc_clk_off(priv); + return IRQ_HANDLED; +} + static void sh_mobile_lcdc_start_stop(struct sh_mobile_lcdc_priv *priv, int start) { @@ -207,6 +276,11 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) int k, m; int ret = 0; + /* enable clocks before accessing the hardware */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) + if (priv->ch[k].enabled) + sh_mobile_lcdc_clk_on(priv); + /* reset */ lcdc_write(priv, _LDCNT2R, lcdc_read(priv, _LDCNT2R) | LCDC_RESET); lcdc_wait_bit(priv, _LDCNT2R, LCDC_RESET, 0); @@ -249,7 +323,7 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) lcdc_write(priv, _LDDCKSTPR, 0); lcdc_wait_bit(priv, _LDDCKSTPR, ~0, 0); - /* interrupts are disabled */ + /* interrupts are disabled to begin with */ lcdc_write(priv, _LDINTR, 0); for (k = 0; k < ARRAY_SIZE(priv->ch); k++) { @@ -310,9 +384,6 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) return ret; } - /* --- display_lcdc_data() --- */ - lcdc_write(priv, _LDINTR, 0x00000f00); - /* word and long word swap */ lcdc_write(priv, _LDDDSR, lcdc_read(priv, _LDDDSR) | 6); @@ -334,8 +405,24 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv) /* set line size */ lcdc_write_chan(ch, LDMLSR, ch->info.fix.line_length); - /* continuous read mode */ - lcdc_write_chan(ch, LDSM1R, 0); + /* setup deferred io if SYS bus */ + tmp = ch->cfg.sys_bus_cfg.deferred_io_msec; + if (ch->ldmt1r_value & (1 << 12) && tmp) { + ch->defio.deferred_io = sh_mobile_lcdc_deferred_io; + ch->defio.delay = msecs_to_jiffies(tmp); + ch->info.fbdefio = &ch->defio; + fb_deferred_io_init(&ch->info); + + /* one-shot mode */ + lcdc_write_chan(ch, LDSM1R, 1); + + /* enable "Frame End Interrupt Enable" bit */ + lcdc_write(priv, _LDINTR, LDINTR_FE); + + } else { + /* continuous read mode */ + lcdc_write_chan(ch, LDSM1R, 0); + } } /* display output */ @@ -359,6 +446,7 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) { struct sh_mobile_lcdc_chan *ch; struct sh_mobile_lcdc_board_cfg *board_cfg; + unsigned long tmp; int k; /* tell the board code to disable the panel */ @@ -367,10 +455,22 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv) board_cfg = &ch->cfg.board_cfg; if (board_cfg->display_off) board_cfg->display_off(board_cfg->board_data); + + /* cleanup deferred io if SYS bus */ + tmp = ch->cfg.sys_bus_cfg.deferred_io_msec; + if (ch->ldmt1r_value & (1 << 12) && tmp) { + fb_deferred_io_cleanup(&ch->info); + ch->info.fbdefio = NULL; + } } /* stop the lcdc */ sh_mobile_lcdc_start_stop(priv, 0); + + /* stop clocks */ + for (k = 0; k < ARRAY_SIZE(priv->ch); k++) + if (priv->ch[k].enabled) + sh_mobile_lcdc_clk_off(priv); } static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) @@ -413,9 +513,13 @@ static int sh_mobile_lcdc_check_interface(struct sh_mobile_lcdc_chan *ch) return -EINVAL; } -static int sh_mobile_lcdc_setup_clocks(struct device *dev, int clock_source, +static int sh_mobile_lcdc_setup_clocks(struct platform_device *pdev, + int clock_source, struct sh_mobile_lcdc_priv *priv) { +#ifdef CONFIG_HAVE_CLK + char clk_name[8]; +#endif char *str; int icksel; @@ -430,14 +534,21 @@ static int sh_mobile_lcdc_setup_clocks(struct device *dev, int clock_source, priv->lddckr = icksel << 16; #ifdef CONFIG_HAVE_CLK + atomic_set(&priv->clk_usecnt, -1); + snprintf(clk_name, sizeof(clk_name), "lcdc%d", pdev->id); + priv->clk = clk_get(&pdev->dev, clk_name); + if (IS_ERR(priv->clk)) { + dev_err(&pdev->dev, "cannot get clock \"%s\"\n", clk_name); + return PTR_ERR(priv->clk); + } + if (str) { - priv->clk = clk_get(dev, str); - if (IS_ERR(priv->clk)) { - dev_err(dev, "cannot get clock %s\n", str); - return PTR_ERR(priv->clk); + priv->dot_clk = clk_get(&pdev->dev, str); + if (IS_ERR(priv->dot_clk)) { + dev_err(&pdev->dev, "cannot get dot clock %s\n", str); + clk_put(priv->clk); + return PTR_ERR(priv->dot_clk); } - - clk_enable(priv->clk); } #endif @@ -475,11 +586,34 @@ static struct fb_fix_screeninfo sh_mobile_lcdc_fix = { .accel = FB_ACCEL_NONE, }; +static void sh_mobile_lcdc_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + sys_fillrect(info, rect); + sh_mobile_lcdc_deferred_io_touch(info); +} + +static void sh_mobile_lcdc_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + sys_copyarea(info, area); + sh_mobile_lcdc_deferred_io_touch(info); +} + +static void sh_mobile_lcdc_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + sys_imageblit(info, image); + sh_mobile_lcdc_deferred_io_touch(info); +} + static struct fb_ops sh_mobile_lcdc_ops = { .fb_setcolreg = sh_mobile_lcdc_setcolreg, - .fb_fillrect = cfb_fillrect, - .fb_copyarea = cfb_copyarea, - .fb_imageblit = cfb_imageblit, + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_fillrect = sh_mobile_lcdc_fillrect, + .fb_copyarea = sh_mobile_lcdc_copyarea, + .fb_imageblit = sh_mobile_lcdc_imageblit, }; static int sh_mobile_lcdc_set_bpp(struct fb_var_screeninfo *var, int bpp) @@ -540,8 +674,9 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (res == NULL) { - dev_err(&pdev->dev, "cannot find IO resource\n"); + i = platform_get_irq(pdev, 0); + if (!res || i < 0) { + dev_err(&pdev->dev, "cannot get platform resources\n"); error = -ENOENT; goto err0; } @@ -553,6 +688,14 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) goto err0; } + error = request_irq(i, sh_mobile_lcdc_irq, IRQF_DISABLED, + pdev->dev.bus_id, priv); + if (error) { + dev_err(&pdev->dev, "unable to request irq\n"); + goto err1; + } + + priv->irq = i; platform_set_drvdata(pdev, priv); pdata = pdev->dev.platform_data; @@ -587,8 +730,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) goto err1; } - error = sh_mobile_lcdc_setup_clocks(&pdev->dev, - pdata->clock_source, priv); + error = sh_mobile_lcdc_setup_clocks(pdev, pdata->clock_source, priv); if (error) { dev_err(&pdev->dev, "unable to setup clocks\n"); goto err1; @@ -637,6 +779,7 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) info->fix.smem_start = priv->ch[i].dma_handle; info->screen_base = buf; info->device = &pdev->dev; + info->par = &priv->ch[i]; } if (error) @@ -664,6 +807,10 @@ static int __init sh_mobile_lcdc_probe(struct platform_device *pdev) (int) priv->ch[i].cfg.lcd_cfg.xres, (int) priv->ch[i].cfg.lcd_cfg.yres, priv->ch[i].cfg.bpp); + + /* deferred io mode: disable clock to save power */ + if (info->fbdefio) + sh_mobile_lcdc_clk_off(priv); } return 0; @@ -697,15 +844,16 @@ static int sh_mobile_lcdc_remove(struct platform_device *pdev) } #ifdef CONFIG_HAVE_CLK - if (priv->clk) { - clk_disable(priv->clk); - clk_put(priv->clk); - } + if (priv->dot_clk) + clk_put(priv->dot_clk); + clk_put(priv->clk); #endif if (priv->base) iounmap(priv->base); + if (priv->irq) + free_irq(priv->irq, priv); kfree(priv); return 0; } |