diff options
author | Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com> | 2013-01-31 12:54:56 +0100 |
---|---|---|
committer | Sascha Hauer <s.hauer@pengutronix.de> | 2013-01-31 19:05:50 +0100 |
commit | f15fb7b428129205aaaf2b23235753c0b3773aa3 (patch) | |
tree | aad71a7db7ca9919895a0a19c0ffb5e894f5ce48 /drivers/video/atmel_hlcdfb.c | |
parent | 0f9770a6fe6f4a258dd9c25ca4efd6713e9c5a66 (diff) | |
download | barebox-f15fb7b428129205aaaf2b23235753c0b3773aa3.tar.gz barebox-f15fb7b428129205aaaf2b23235753c0b3773aa3.tar.xz |
video: add Atmel HLCD support
Signed-off-by: Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
Diffstat (limited to 'drivers/video/atmel_hlcdfb.c')
-rw-r--r-- | drivers/video/atmel_hlcdfb.c | 297 |
1 files changed, 297 insertions, 0 deletions
diff --git a/drivers/video/atmel_hlcdfb.c b/drivers/video/atmel_hlcdfb.c new file mode 100644 index 0000000000..78a737d0b9 --- /dev/null +++ b/drivers/video/atmel_hlcdfb.c @@ -0,0 +1,297 @@ +/* + * Driver for AT91/AT32 LCD Controller + * + * Copyright (C) 2007 Atmel Corporation + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <common.h> +#include <io.h> +#include <init.h> +#include <linux/clk.h> +#include <mach/hardware.h> +#include <mach/atmel_hlcdc.h> +#include <mach/io.h> +#include <mach/cpu.h> +#include <errno.h> +#include <asm/mmu.h> + +#include "atmel_lcdfb.h" + +#define ATMEL_LCDC_CVAL_DEFAULT 0xc8 + +static void atmel_hlcdfb_stop(struct atmel_lcdfb_info *sinfo, u32 flags) +{ + /* Disable DISP signal */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_DISPDIS); + while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_DISPSTS)) + mdelay(1); + /* Disable synchronization */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_SYNCDIS); + while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_LCDSTS)) + mdelay(1); + /* Disable pixel clock */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_CLKDIS); + while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_CLKSTS)) + mdelay(1); + /* Disable PWM */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDDIS, LCDC_LCDDIS_PWMDIS); + while ((lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_PWMSTS)) + mdelay(1); + + if (!(flags & ATMEL_LCDC_STOP_NOWAIT)) + /* Wait for the end of DMA transfer */ + while (!(lcdc_readl(sinfo, ATMEL_LCDC_BASEISR) & LCDC_BASEISR_DMA)) + mdelay(10); + /*FIXME: OVL DMA? */ +} + +static void atmel_hlcdfb_start(struct atmel_lcdfb_info *sinfo) +{ + lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, LCDC_LCDEN_CLKEN); + while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_CLKSTS)) + mdelay(1); + lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, LCDC_LCDEN_SYNCEN); + while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_LCDSTS)) + mdelay(1); + lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, LCDC_LCDEN_DISPEN); + while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_DISPSTS)) + mdelay(1); + lcdc_writel(sinfo, ATMEL_LCDC_LCDEN, LCDC_LCDEN_PWMEN); + while (!(lcdc_readl(sinfo, ATMEL_LCDC_LCDSR) & LCDC_LCDSR_PWMSTS)) + mdelay(1); +} + +struct atmel_hlcd_dma_desc { + u32 address; + u32 control; + u32 next; +}; + +static void atmel_hlcdfb_update_dma(struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->priv; + unsigned long dma_addr; + struct atmel_hlcd_dma_desc *desc; + + dma_addr = (u32)info->screen_base; + dma_addr &= ~3UL; + + /* Setup the DMA descriptor, this descriptor will loop to itself */ + desc = sinfo->dma_desc; + + desc->address = dma_addr; + /* Disable DMA transfer interrupt & descriptor loaded interrupt. */ + desc->control = LCDC_BASECTRL_ADDIEN | LCDC_BASECTRL_DSCRIEN + | LCDC_BASECTRL_DMAIEN | LCDC_BASECTRL_DFETCH; + desc->next = (u32)desc; + + lcdc_writel(sinfo, ATMEL_LCDC_BASEADDR, desc->address); + lcdc_writel(sinfo, ATMEL_LCDC_BASECTRL, desc->control); + lcdc_writel(sinfo, ATMEL_LCDC_BASENEXT, desc->next); + lcdc_writel(sinfo, ATMEL_LCDC_BASECHER, LCDC_BASECHER_CHEN | LCDC_BASECHER_UPDATEEN); +} + +static void atmel_hlcdfb_limit_screeninfo(struct fb_videomode *mode) +{ + u32 hbpw, hfpw; + + if (cpu_is_at91sam9x5()) { + hbpw = LCDC_LCDCFG3_HBPW; + hfpw = LCDC_LCDCFG3_HFPW; + } else { + hbpw = LCDC2_LCDCFG3_HBPW; + hfpw = LCDC2_LCDCFG3_HFPW; + } + + /* Saturate vertical and horizontal timings at maximum values */ + mode->vsync_len = min_t(u32, mode->vsync_len, + (LCDC_LCDCFG1_VSPW >> LCDC_LCDCFG1_VSPW_OFFSET) + 1); + mode->upper_margin = min_t(u32, mode->upper_margin, + (LCDC_LCDCFG2_VFPW >> LCDC_LCDCFG2_VFPW_OFFSET) + 1); + mode->lower_margin = min_t(u32, mode->lower_margin, + LCDC_LCDCFG2_VBPW >> LCDC_LCDCFG2_VBPW_OFFSET); + mode->right_margin = min_t(u32, mode->right_margin, + (hbpw >> LCDC_LCDCFG3_HBPW_OFFSET) + 1); + mode->hsync_len = min_t(u32, mode->hsync_len, + (LCDC_LCDCFG1_HSPW >> LCDC_LCDCFG1_HSPW_OFFSET) + 1); + mode->left_margin = min_t(u32, mode->left_margin, + (hfpw >> LCDC_LCDCFG3_HFPW_OFFSET) + 1); +} + +static u32 atmel_hlcdfb_get_rgbmode(struct fb_info *info) +{ + u32 value = 0; + + switch (info->bits_per_pixel) { + case 1: + value = LCDC_BASECFG1_CLUTMODE_1BPP | LCDC_BASECFG1_CLUTEN; + break; + case 2: + value = LCDC_BASECFG1_CLUTMODE_2BPP | LCDC_BASECFG1_CLUTEN; + break; + case 4: + value = LCDC_BASECFG1_CLUTMODE_4BPP | LCDC_BASECFG1_CLUTEN; + break; + case 8: + value = LCDC_BASECFG1_CLUTMODE_8BPP | LCDC_BASECFG1_CLUTEN; + break; + case 12: + value = LCDC_BASECFG1_RGBMODE_12BPP_RGB_444; + break; + case 16: + if (info->transp.offset) + value = LCDC_BASECFG1_RGBMODE_16BPP_ARGB_4444; + else + value = LCDC_BASECFG1_RGBMODE_16BPP_RGB_565; + break; + case 18: + value = LCDC_BASECFG1_RGBMODE_18BPP_RGB_666_PACKED; + break; + case 24: + value = LCDC_BASECFG1_RGBMODE_24BPP_RGB_888_PACKED; + break; + case 32: + value = LCDC_BASECFG1_RGBMODE_32BPP_ARGB_8888; + break; + default: + dev_err(&info->dev, "Cannot set video mode for %dbpp\n", + info->bits_per_pixel); + break; + } + + return value; +} + +static void atmel_hlcdfb_setup_core_base(struct fb_info *info) +{ + struct atmel_lcdfb_info *sinfo = info->priv; + struct atmel_lcdfb_platform_data *pdata = sinfo->pdata; + struct fb_videomode *mode = info->mode; + unsigned long value; + unsigned long clk_value_khz; + + dev_dbg(&info->dev, "%s:\n", __func__); + /* Set pixel clock */ + clk_value_khz = clk_get_rate(sinfo->lcdc_clk) / 1000; + + value = DIV_ROUND_CLOSEST(clk_value_khz, PICOS2KHZ(mode->pixclock)); + + if (value < 1) { + dev_notice(&info->dev, "using system clock as pixel clock\n"); + value = LCDC_LCDCFG0_CLKPWMSEL; + } else { + mode->pixclock = KHZ2PICOS(clk_value_khz / value); + dev_dbg(&info->dev, " updated pixclk: %lu KHz\n", + PICOS2KHZ(mode->pixclock)); + value = value - 2; + dev_dbg(&info->dev, " * programming CLKDIV = 0x%08lx\n", + value); + value = (value << LCDC_LCDCFG0_CLKDIV_OFFSET); + } + value |= LCDC_LCDCFG0_CGDISBASE; + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG0, value); + + /* Initialize control register 5 */ + /* In 9x5, the default_lcdcon2 will use for LCDCFG5 */ + value = pdata->default_lcdcon2; + value |= (sinfo->guard_time << LCDC_LCDCFG5_GUARDTIME_OFFSET) + | LCDC_LCDCFG5_DISPDLY + | LCDC_LCDCFG5_VSPDLYS; + + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT)) + value |= LCDC_LCDCFG5_HSPOL; + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT)) + value |= LCDC_LCDCFG5_VSPOL; + + dev_dbg(&info->dev, " * LCDC_LCDCFG5 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG5, value); + + /* Vertical & Horizontal Timing */ + value = (mode->vsync_len - 1) << LCDC_LCDCFG1_VSPW_OFFSET; + value |= (mode->hsync_len - 1) << LCDC_LCDCFG1_HSPW_OFFSET; + dev_dbg(&info->dev, " * LCDC_LCDCFG1 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG1, value); + + value = (mode->upper_margin) << LCDC_LCDCFG2_VBPW_OFFSET; + value |= (mode->lower_margin - 1) << LCDC_LCDCFG2_VFPW_OFFSET; + dev_dbg(&info->dev, " * LCDC_LCDCFG2 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG2, value); + + value = (mode->left_margin - 1) << LCDC_LCDCFG3_HBPW_OFFSET; + value |= (mode->right_margin - 1) << LCDC_LCDCFG3_HFPW_OFFSET; + dev_dbg(&info->dev, " * LCDC_LCDCFG3 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG3, value); + + /* Display size */ + value = (mode->yres - 1) << LCDC_LCDCFG4_RPF_OFFSET; + value |= (mode->xres - 1) << LCDC_LCDCFG4_PPL_OFFSET; + dev_dbg(&info->dev, " * LCDC_LCDCFG4 = %08lx\n", value); + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG4, value); + + lcdc_writel(sinfo, ATMEL_LCDC_BASECFG0, LCDC_BASECFG0_BLEN_AHB_INCR16 | LCDC_BASECFG0_DLBO); + lcdc_writel(sinfo, ATMEL_LCDC_BASECFG1, atmel_hlcdfb_get_rgbmode(info)); + lcdc_writel(sinfo, ATMEL_LCDC_BASECFG2, 0); + lcdc_writel(sinfo, ATMEL_LCDC_BASECFG3, 0); /* Default color */ + lcdc_writel(sinfo, ATMEL_LCDC_BASECFG4, LCDC_BASECFG4_DMA); + + /* Disable all interrupts */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDIDR, ~0UL); + lcdc_writel(sinfo, ATMEL_LCDC_BASEIDR, ~0UL); + /* Enable BASE LAYER overflow interrupts, if want to enable DMA interrupt, also need set it at LCDC_BASECTRL reg */ + lcdc_writel(sinfo, ATMEL_LCDC_BASEIER, LCDC_BASEIER_OVR); + /* FIXME: Let video-driver register a callback */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDIER, LCDC_LCDIER_FIFOERRIE | + LCDC_LCDIER_BASEIE | LCDC_LCDIER_HEOIE); +} + + +static void atmel_hlcdfb_init_contrast(struct atmel_lcdfb_info *sinfo) +{ + /* have some default contrast/backlight settings */ + lcdc_writel(sinfo, ATMEL_LCDC_LCDCFG6, LCDC_LCDCFG6_PWMPOL | + (ATMEL_LCDC_CVAL_DEFAULT << LCDC_LCDCFG6_PWMCVAL_OFFSET)); +} + +struct atmel_lcdfb_devdata atmel_hlcdfb_data = { + .start = atmel_hlcdfb_start, + .stop = atmel_hlcdfb_stop, + .update_dma = atmel_hlcdfb_update_dma, + .setup_core = atmel_hlcdfb_setup_core_base, + .init_contrast = atmel_hlcdfb_init_contrast, + .limit_screeninfo = atmel_hlcdfb_limit_screeninfo, + .dma_desc_size = sizeof(struct atmel_hlcd_dma_desc), +}; + +static int atmel_hlcdc_probe(struct device_d *dev) +{ + return atmel_lcdc_register(dev, &atmel_hlcdfb_data); +} + +static struct driver_d atmel_hlcdc_driver = { + .name = "atmel_hlcdfb", + .probe = atmel_hlcdc_probe, +}; + +static int atmel_hlcdc_init(void) +{ + return platform_driver_register(&atmel_hlcdc_driver); +} +device_initcall(atmel_hlcdc_init); |