blob: ee2f0268bd8755bd892f9edf39f7ac330ad4ede0 [file] [log] [blame]
/*
* Copyright (C) 2010 Juergen Beisert, Pengutronix <jbe@pengutronix.de>
*
* This is based on code from:
* Author: Vitaly Wool <vital@embeddedalley.com>
*
* Copyright 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
* Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved.
*
* 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.
*/
#include <common.h>
#include <init.h>
#include <driver.h>
#include <malloc.h>
#include <errno.h>
#include <xfuncs.h>
#include <asm/io.h>
#include <mach/imx-regs.h>
#include <mach/clock.h>
#include <mach/fb.h>
#define HW_LCDIF_CTRL 0x00
# define CTRL_SFTRST (1 << 31)
# define CTRL_CLKGATE (1 << 30)
# define CTRL_BYPASS_COUNT (1 << 19)
# define CTRL_VSYNC_MODE (1 << 18)
# define CTRL_DOTCLK_MODE (1 << 17)
# define CTRL_DATA_SELECT (1 << 16)
# define SET_BUS_WIDTH(x) (((x) & 0x3) << 10)
# define SET_WORD_LENGTH(x) (((x) & 0x3) << 8)
# define GET_WORD_LENGTH(x) (((x) >> 8) & 0x3)
# define CTRL_MASTER (1 << 5)
# define CTRL_DF16 (1 << 3)
# define CTRL_DF18 (1 << 2)
# define CTRL_DF24 (1 << 1)
# define CTRL_RUN (1 << 0)
#define HW_LCDIF_CTRL1 0x10
# define CTRL1_FIFO_CLEAR (1 << 21)
# define SET_BYTE_PACKAGING(x) (((x) & 0xf) << 16)
# define GET_BYTE_PACKAGING(x) (((x) >> 16) & 0xf)
# define CTRL1_RESET (1 << 0)
#ifdef CONFIG_ARCH_IMX28
# define HW_LCDIF_CTRL2 0x20
# define HW_LCDIF_TRANSFER_COUNT 0x30
#endif
#ifdef CONFIG_ARCH_IMX23
# define HW_LCDIF_TRANSFER_COUNT 0x20
#endif
# define SET_VCOUNT(x) (((x) & 0xffff) << 16)
# define SET_HCOUNT(x) ((x) & 0xffff)
#ifdef CONFIG_ARCH_IMX28
# define HW_LCDIF_CUR_BUF 0x40
# define HW_LCDIF_NEXT_BUF 0x50
#endif
#ifdef CONFIG_ARCH_IMX23
# define HW_LCDIF_CUR_BUF 0x30
# define HW_LCDIF_NEXT_BUF 0x40
#endif
#define HW_LCDIF_TIMING 0x60
# define SET_CMD_HOLD(x) (((x) & 0xff) << 24)
# define SET_CMD_SETUP(x) (((x) & 0xff) << 16)
# define SET_DATA_HOLD(x) (((x) & 0xff) << 8)
# define SET_DATA_SETUP(x) ((x) & 0xff)
#define HW_LCDIF_VDCTRL0 0x70
# define VDCTRL0_ENABLE_PRESENT (1 << 28)
# define VDCTRL0_VSYNC_POL (1 << 27) /* 0 = low active, 1 = high active */
# define VDCTRL0_HSYNC_POL (1 << 26) /* 0 = low active, 1 = high active */
# define VDCTRL0_DOTCLK_POL (1 << 25) /* 0 = output@falling, capturing@rising edge */
# define VDCTRL0_ENABLE_POL (1 << 24) /* 0 = low active, 1 = high active */
# define VDCTRL0_VSYNC_PERIOD_UNIT (1 << 21)
# define VDCTRL0_VSYNC_PULSE_WIDTH_UNIT (1 << 20)
# define VDCTRL0_HALF_LINE (1 << 19)
# define VDCTRL0_HALF_LINE_MODE (1 << 18)
# define SET_VSYNC_PULSE_WIDTH(x) ((x) & 0x3ffff)
#define HW_LCDIF_VDCTRL1 0x80
#define HW_LCDIF_VDCTRL2 0x90
#ifdef CONFIG_ARCH_IMX28
# define SET_HSYNC_PULSE_WIDTH(x) (((x) & 0x3fff) << 18)
#endif
#ifdef CONFIG_ARCH_IMX23
# define SET_HSYNC_PULSE_WIDTH(x) (((x) & 0xff) << 24)
#endif
# define SET_HSYNC_PERIOD(x) ((x) & 0x3ffff)
#define HW_LCDIF_VDCTRL3 0xa0
# define VDCTRL3_MUX_SYNC_SIGNALS (1 << 29)
# define VDCTRL3_VSYNC_ONLY (1 << 28)
# define SET_HOR_WAIT_CNT(x) (((x) & 0xfff) << 16)
# define SET_VERT_WAIT_CNT(x) ((x) & 0xffff)
#define HW_LCDIF_VDCTRL4 0xb0
#ifdef CONFIG_ARCH_IMX28
# define SET_DOTCLK_DLY(x) (((x) & 0x7) << 29)
#endif
# define VDCTRL4_SYNC_SIGNALS_ON (1 << 18)
# define SET_DOTCLK_H_VALID_DATA_CNT(x) ((x) & 0x3ffff)
#define HW_LCDIF_DVICTRL0 0xc0
#define HW_LCDIF_DVICTRL1 0xd0
#define HW_LCDIF_DVICTRL2 0xe0
#define HW_LCDIF_DVICTRL3 0xf0
#define HW_LCDIF_DVICTRL4 0x100
#ifdef CONFIG_ARCH_IMX28
# define HW_LCDIF_DATA 0x180
#endif
#ifdef CONFIG_ARCH_IMX23
# define HW_LCDIF_DATA 0x1b0
#endif
#ifdef CONFIG_ARCH_IMX28
# define HW_LCDIF_DEBUG0 0x1d0
#endif
#ifdef CONFIG_ARCH_IMX23
# define HW_LCDIF_DEBUG0 0x1f0
#endif
# define DEBUG_HSYNC (1 < 26)
# define DEBUG_VSYNC (1 < 25)
#define RED 0
#define GREEN 1
#define BLUE 2
#define TRANSP 3
struct imxfb_info {
void __iomem *base;
unsigned memory_size;
struct fb_info info;
struct device_d *hw_dev;
struct imx_fb_platformdata *pdata;
};
/* the RGB565 true colour mode */
static const struct fb_bitfield def_rgb565[] = {
[RED] = {
.offset = 11,
.length = 5,
},
[GREEN] = {
.offset = 5,
.length = 6,
},
[BLUE] = {
.offset = 0,
.length = 5,
},
[TRANSP] = { /* no support for transparency */
.length = 0,
}
};
/* the RGB666 true colour mode */
static const struct fb_bitfield def_rgb666[] = {
[RED] = {
.offset = 16,
.length = 6,
},
[GREEN] = {
.offset = 8,
.length = 6,
},
[BLUE] = {
.offset = 0,
.length = 6,
},
[TRANSP] = { /* no support for transparency */
.length = 0,
}
};
/* the RGB888 true colour mode */
static const struct fb_bitfield def_rgb888[] = {
[RED] = {
.offset = 16,
.length = 8,
},
[GREEN] = {
.offset = 8,
.length = 8,
},
[BLUE] = {
.offset = 0,
.length = 8,
},
[TRANSP] = { /* no support for transparency */
.length = 0,
}
};
static inline unsigned calc_line_length(unsigned ppl, unsigned bpp)
{
if (bpp == 24)
bpp = 32;
return (ppl * bpp) >> 3;
}
static void stmfb_enable_controller(struct fb_info *fb_info)
{
struct imxfb_info *fbi = fb_info->priv;
uint32_t reg, last_reg;
unsigned loop, edges;
/*
* Sometimes some data is still present in the FIFO. This leads into
* a correct but shifted picture. Clearing the FIFO helps
*/
writel(CTRL1_FIFO_CLEAR, fbi->base + HW_LCDIF_CTRL1 + BIT_SET);
/* if it was disabled, re-enable the mode again */
reg = readl(fbi->base + HW_LCDIF_CTRL);
reg |= CTRL_DOTCLK_MODE;
writel(reg, fbi->base + HW_LCDIF_CTRL);
/* enable the SYNC signals first, then the DMA engine */
reg = readl(fbi->base + HW_LCDIF_VDCTRL4);
reg |= VDCTRL4_SYNC_SIGNALS_ON;
writel(reg, fbi->base + HW_LCDIF_VDCTRL4);
/*
* Give the attached LC display or monitor a chance to sync into
* our signals.
* Wait for at least 2 VSYNCs = four VSYNC edges
*/
edges = 4;
while (edges != 0) {
loop = 800;
last_reg = readl(fbi->base + HW_LCDIF_DEBUG0) & DEBUG_VSYNC;
do {
reg = readl(fbi->base + HW_LCDIF_DEBUG0) & DEBUG_VSYNC;
if (reg != last_reg)
break;
last_reg = reg;
loop--;
} while (loop != 0);
edges--;
}
/* stop FIFO reset */
writel(CTRL1_FIFO_CLEAR, fbi->base + HW_LCDIF_CTRL1 + BIT_CLR);
/* enable LCD using LCD_RESET signal*/
if (fbi->pdata->flags & USE_LCD_RESET)
writel(CTRL1_RESET, fbi->base + HW_LCDIF_CTRL1 + BIT_SET);
/* start the engine right now */
writel(CTRL_RUN, fbi->base + HW_LCDIF_CTRL + BIT_SET);
if (fbi->pdata->enable)
fbi->pdata->enable(1);
}
static void stmfb_disable_controller(struct fb_info *fb_info)
{
struct imxfb_info *fbi = fb_info->priv;
unsigned loop;
uint32_t reg;
/* disable LCD using LCD_RESET signal*/
if (fbi->pdata->flags & USE_LCD_RESET)
writel(CTRL1_RESET, fbi->base + HW_LCDIF_CTRL1 + BIT_CLR);
if (fbi->pdata->enable)
fbi->pdata->enable(0);
/*
* Even if we disable the controller here, it will still continue
* until its FIFOs are running out of data
*/
reg = readl(fbi->base + HW_LCDIF_CTRL);
reg &= ~CTRL_DOTCLK_MODE;
writel(reg, fbi->base + HW_LCDIF_CTRL);
loop = 1000;
while (loop) {
reg = readl(fbi->base + HW_LCDIF_CTRL);
if (!(reg & CTRL_RUN))
break;
loop--;
}
reg = readl(fbi->base + HW_LCDIF_VDCTRL4);
reg &= ~VDCTRL4_SYNC_SIGNALS_ON;
writel(reg, fbi->base + HW_LCDIF_VDCTRL4);
}
static int stmfb_activate_var(struct fb_info *fb_info)
{
struct imxfb_info *fbi = fb_info->priv;
struct imx_fb_platformdata *pdata = fbi->pdata;
struct fb_videomode *mode = fb_info->mode;
uint32_t reg;
unsigned size;
/*
* we need at least this amount of memory for the framebuffer
*/
size = calc_line_length(mode->xres, fb_info->bits_per_pixel) *
mode->yres;
if (pdata->fixed_screen) {
if (pdata->fixed_screen_size < size)
return -ENOMEM;
fb_info->screen_base = pdata->fixed_screen;
fbi->memory_size = pdata->fixed_screen_size;
} else {
fb_info->screen_base = xrealloc(fb_info->screen_base, size);
fbi->memory_size = size;
}
/** @todo ensure HCLK is active at this point of time! */
size = imx_set_lcdifclk(PICOS2KHZ(mode->pixclock) * 1000);
if (size == 0) {
dev_dbg(fbi->hw_dev, "Unable to set a valid pixel clock\n");
return -EINVAL;
}
/*
* bring the controller out of reset and
* configure it into DOTCLOCK mode
*/
reg = CTRL_BYPASS_COUNT | /* always in DOTCLOCK mode */
CTRL_DOTCLK_MODE;
writel(reg, fbi->base + HW_LCDIF_CTRL);
/* master mode only */
reg |= CTRL_MASTER;
/*
* Configure videomode and interface mode
*/
reg |= SET_BUS_WIDTH(pdata->ld_intf_width);
switch (fb_info->bits_per_pixel) {
case 8:
reg |= SET_WORD_LENGTH(1);
/** @todo refer manual page 2046 for 8 bpp modes */
dev_dbg(fbi->hw_dev, "8 bpp mode not supported yet\n");
break;
case 16:
pr_debug("Setting up an RGB565 mode\n");
reg |= SET_WORD_LENGTH(0);
reg &= ~CTRL_DF16; /* we assume RGB565 */
writel(SET_BYTE_PACKAGING(0xf), fbi->base + HW_LCDIF_CTRL1);
fb_info->red = def_rgb565[RED];
fb_info->green = def_rgb565[GREEN];
fb_info->blue = def_rgb565[BLUE];
fb_info->transp = def_rgb565[TRANSP];
break;
case 24:
case 32:
pr_debug("Setting up an RGB888/666 mode\n");
reg |= SET_WORD_LENGTH(3);
switch (pdata->ld_intf_width) {
case STMLCDIF_8BIT:
dev_dbg(fbi->hw_dev,
"Unsupported LCD bus width mapping\n");
break;
case STMLCDIF_16BIT:
case STMLCDIF_18BIT:
/* 24 bit to 18 bit mapping
* which means: ignore the upper 2 bits in
* each colour component
*/
reg |= CTRL_DF24;
fb_info->red = def_rgb666[RED];
fb_info->green = def_rgb666[GREEN];
fb_info->blue = def_rgb666[BLUE];
fb_info->transp = def_rgb666[TRANSP];
break;
case STMLCDIF_24BIT:
/* real 24 bit */
fb_info->red = def_rgb888[RED];
fb_info->green = def_rgb888[GREEN];
fb_info->blue = def_rgb888[BLUE];
fb_info->transp = def_rgb888[TRANSP];
break;
}
/* do not use packed pixels = one pixel per word instead */
writel(SET_BYTE_PACKAGING(0x7), fbi->base + HW_LCDIF_CTRL1);
break;
default:
dev_dbg(fbi->hw_dev, "Unhandled colour depth of %u\n",
fb_info->bits_per_pixel);
return -EINVAL;
}
writel(reg, fbi->base + HW_LCDIF_CTRL);
pr_debug("Setting up CTRL to %08X\n", reg);
writel(SET_VCOUNT(mode->yres) |
SET_HCOUNT(mode->xres), fbi->base + HW_LCDIF_TRANSFER_COUNT);
reg = VDCTRL0_ENABLE_PRESENT | /* always in DOTCLOCK mode */
VDCTRL0_VSYNC_PERIOD_UNIT |
VDCTRL0_VSYNC_PULSE_WIDTH_UNIT;
if (mode->sync & FB_SYNC_HOR_HIGH_ACT)
reg |= VDCTRL0_HSYNC_POL;
if (mode->sync & FB_SYNC_VERT_HIGH_ACT)
reg |= VDCTRL0_VSYNC_POL;
if (mode->sync & FB_SYNC_DE_HIGH_ACT)
reg |= VDCTRL0_ENABLE_POL;
if (mode->sync & FB_SYNC_CLK_INVERT)
reg |= VDCTRL0_DOTCLK_POL;
reg |= SET_VSYNC_PULSE_WIDTH(mode->vsync_len);
writel(reg, fbi->base + HW_LCDIF_VDCTRL0);
pr_debug("Setting up VDCTRL0 to %08X\n", reg);
/* frame length in lines */
writel(mode->upper_margin + mode->vsync_len + mode->lower_margin +
mode->yres,
fbi->base + HW_LCDIF_VDCTRL1);
/* line length in units of clocks or pixels */
writel(SET_HSYNC_PULSE_WIDTH(mode->hsync_len) |
SET_HSYNC_PERIOD(mode->left_margin + mode->hsync_len +
mode->right_margin + mode->xres),
fbi->base + HW_LCDIF_VDCTRL2);
writel(SET_HOR_WAIT_CNT(mode->left_margin + mode->hsync_len) |
SET_VERT_WAIT_CNT(mode->upper_margin + mode->vsync_len),
fbi->base + HW_LCDIF_VDCTRL3);
writel(
#ifdef CONFIG_ARCH_IMX28
SET_DOTCLK_DLY(pdata->dotclk_delay) |
#endif
SET_DOTCLK_H_VALID_DATA_CNT(mode->xres),
fbi->base + HW_LCDIF_VDCTRL4);
writel((uint32_t)fb_info->screen_base, fbi->base + HW_LCDIF_CUR_BUF);
writel((uint32_t)fb_info->screen_base, fbi->base + HW_LCDIF_NEXT_BUF);
return 0;
}
static void stmfb_info(struct device_d *hw_dev)
{
struct imx_fb_platformdata *pdata = hw_dev->platform_data;
unsigned u;
printf(" Supported video modes:\n");
for (u = 0; u < pdata->mode_cnt; u++)
printf(" - '%s': %u x %u\n", pdata->mode_list[u].name,
pdata->mode_list[u].xres, pdata->mode_list[u].yres);
}
/*
* There is only one video hardware instance available.
* It makes no sense to dynamically allocate this data
*/
static struct fb_ops imxfb_ops = {
.fb_activate_var = stmfb_activate_var,
.fb_enable = stmfb_enable_controller,
.fb_disable = stmfb_disable_controller,
};
static struct imxfb_info fbi = {
.info = {
.fbops = &imxfb_ops,
},
};
static int stmfb_probe(struct device_d *hw_dev)
{
struct imx_fb_platformdata *pdata = hw_dev->platform_data;
int ret;
/* just init */
fbi.info.priv = &fbi;
/* add runtime hardware info */
fbi.hw_dev = hw_dev;
fbi.base = (void *)hw_dev->map_base;
fbi.pdata = pdata;
/* add runtime video info */
fbi.info.mode_list = pdata->mode_list;
fbi.info.num_modes = pdata->mode_cnt;
fbi.info.mode = &fbi.info.mode_list[0];
fbi.info.xres = fbi.info.mode->xres;
fbi.info.yres = fbi.info.mode->yres;
if (pdata->bits_per_pixel)
fbi.info.bits_per_pixel = pdata->bits_per_pixel;
else
fbi.info.bits_per_pixel = 16;
ret = register_framebuffer(&fbi.info);
if (ret != 0) {
dev_err(hw_dev, "Failed to register framebuffer\n");
return -EINVAL;
}
return 0;
}
static struct driver_d stmfb_driver = {
.name = "stmfb",
.probe = stmfb_probe,
.info = stmfb_info,
};
static int stmfb_init(void)
{
return register_driver(&stmfb_driver);
}
device_initcall(stmfb_init);
/**
* @file
* @brief LCDIF driver for i.MX23 and i.MX28
*
* The LCDIF support four modes of operation
* - MPU interface (to drive smart displays) -> not supported yet
* - VSYNC interface (like MPU interface plus Vsync) -> not supported yet
* - Dotclock interface (to drive LC displays with RGB data and sync signals)
* - DVI (to drive ITU-R BT656) -> not supported yet
*
* This driver depends on a correct setup of the pins used for this purpose
* (platform specific).
*
* For the developer: Don't forget to set the data bus width to the display
* in the imx_fb_platformdata structure. You will else end up with ugly colours.
* If you fight against jitter you can vary the clock delay. This is a feature
* of the i.MX28 and you can vary it between 2 ns ... 8 ns in 2 ns steps. Give
* the required value in the imx_fb_platformdata structure.
*/