| /* |
| * 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. |
| */ |