linux/drivers/video/omap/lcdc.c

857 lines
19 KiB
C
Raw Normal View History

/*
* OMAP1 internal LCD controller
*
* Copyright (C) 2004 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
*
* 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 <linux/module.h>
#include <linux/device.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <linux/err.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/clk.h>
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h percpu.h is included by sched.h and module.h and thus ends up being included when building most .c files. percpu.h includes slab.h which in turn includes gfp.h making everything defined by the two files universally available and complicating inclusion dependencies. percpu.h -> slab.h dependency is about to be removed. Prepare for this change by updating users of gfp and slab facilities include those headers directly instead of assuming availability. As this conversion needs to touch large number of source files, the following script is used as the basis of conversion. http://userweb.kernel.org/~tj/misc/slabh-sweep.py The script does the followings. * Scan files for gfp and slab usages and update includes such that only the necessary includes are there. ie. if only gfp is used, gfp.h, if slab is used, slab.h. * When the script inserts a new include, it looks at the include blocks and try to put the new include such that its order conforms to its surrounding. It's put in the include block which contains core kernel includes, in the same order that the rest are ordered - alphabetical, Christmas tree, rev-Xmas-tree or at the end if there doesn't seem to be any matching order. * If the script can't find a place to put a new include (mostly because the file doesn't have fitting include block), it prints out an error message indicating which .h file needs to be added to the file. The conversion was done in the following steps. 1. The initial automatic conversion of all .c files updated slightly over 4000 files, deleting around 700 includes and adding ~480 gfp.h and ~3000 slab.h inclusions. The script emitted errors for ~400 files. 2. Each error was manually checked. Some didn't need the inclusion, some needed manual addition while adding it to implementation .h or embedding .c file was more appropriate for others. This step added inclusions to around 150 files. 3. The script was run again and the output was compared to the edits from #2 to make sure no file was left behind. 4. Several build tests were done and a couple of problems were fixed. e.g. lib/decompress_*.c used malloc/free() wrappers around slab APIs requiring slab.h to be added manually. 5. The script was run on all .h files but without automatically editing them as sprinkling gfp.h and slab.h inclusions around .h files could easily lead to inclusion dependency hell. Most gfp.h inclusion directives were ignored as stuff from gfp.h was usually wildly available and often used in preprocessor macros. Each slab.h inclusion directive was examined and added manually as necessary. 6. percpu.h was updated not to include slab.h. 7. Build test were done on the following configurations and failures were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my distributed build env didn't work with gcov compiles) and a few more options had to be turned off depending on archs to make things build (like ipr on powerpc/64 which failed due to missing writeq). * x86 and x86_64 UP and SMP allmodconfig and a custom test config. * powerpc and powerpc64 SMP allmodconfig * sparc and sparc64 SMP allmodconfig * ia64 SMP allmodconfig * s390 SMP allmodconfig * alpha SMP allmodconfig * um on x86_64 SMP allmodconfig 8. percpu.h modifications were reverted so that it could be applied as a separate patch and serve as bisection point. Given the fact that I had only a couple of failures from tests on step 6, I'm fairly confident about the coverage of this conversion patch. If there is a breakage, it's likely to be something in one of the arch headers which should be easily discoverable easily on most builds of the specific arch. Signed-off-by: Tejun Heo <tj@kernel.org> Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org> Cc: Ingo Molnar <mingo@redhat.com> Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 08:04:11 +00:00
#include <linux/gfp.h>
#include <mach/lcdc.h>
ARM: OMAP: Move plat-omap/dma-omap.h to include/linux/omap-dma.h Based on earlier discussions[1] we attempted to find a suitable location for the omap DMA header in commit 2b6c4e73 (ARM: OMAP: DMA: Move plat/dma.h to plat-omap/dma-omap.h) until the conversion to dmaengine is complete. Unfortunately that was before I was able to try to test compile of the ARM multiplatform builds for omap2+, and the end result was not very good. So I'm creating yet another all over the place patch to cut the last dependency for building omap2+ for ARM multiplatform. After this, we have finally removed the driver dependencies to the arch/arm code, except for few drivers that are being worked on. The other option was to make the <plat-omap/dma-omap.h> path to work, but we'd have to add some new header directory to for multiplatform builds. Or we would have to manually include arch/arm/plat-omap/include again from arch/arm/Makefile for omap2+. Neither of these alternatives sound appealing as they will likely lead addition of various other headers exposed to the drivers, which we want to avoid for the multiplatform kernels. Since we already have a minimal include/linux/omap-dma.h, let's just use that instead and add a note to it to not use the custom omap DMA functions any longer where possible. Note that converting omap DMA to dmaengine depends on dmaengine supporting automatically incrementing the FIFO address at the device end, and converting all the remaining legacy drivers. So it's going to be few more merge windows. [1] https://patchwork.kernel.org/patch/1519591/# cc: Russell King <linux@arm.linux.org.uk> cc: Kevin Hilman <khilman@ti.com> cc: "Benoît Cousson" <b-cousson@ti.com> cc: Herbert Xu <herbert@gondor.apana.org.au> cc: "David S. Miller" <davem@davemloft.net> cc: Vinod Koul <vinod.koul@intel.com> cc: Dan Williams <djbw@fb.com> cc: Mauro Carvalho Chehab <mchehab@infradead.org> cc: Laurent Pinchart <laurent.pinchart@ideasonboard.com> cc: Guennadi Liakhovetski <g.liakhovetski@gmx.de> cc: David Woodhouse <dwmw2@infradead.org> cc: Kyungmin Park <kyungmin.park@samsung.com> cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org> cc: Tomi Valkeinen <tomi.valkeinen@ti.com> cc: Florian Tobias Schandinat <FlorianSchandinat@gmx.de> cc: Hans Verkuil <hans.verkuil@cisco.com> cc: Vaibhav Hiremath <hvaibhav@ti.com> cc: Lokesh Vutla <lokeshvutla@ti.com> cc: Rusty Russell <rusty@rustcorp.com.au> cc: Artem Bityutskiy <artem.bityutskiy@linux.intel.com> cc: Afzal Mohammed <afzal@ti.com> cc: linux-crypto@vger.kernel.org cc: linux-media@vger.kernel.org cc: linux-mtd@lists.infradead.org cc: linux-usb@vger.kernel.org cc: linux-fbdev@vger.kernel.org Acked-by: Felipe Balbi <balbi@ti.com> Signed-off-by: Tony Lindgren <tony@atomide.com>
2012-11-30 16:41:50 +00:00
#include <linux/omap-dma.h>
#include <asm/mach-types.h>
#include "omapfb.h"
#include "lcdc.h"
#define MODULE_NAME "lcdc"
#define MAX_PALETTE_SIZE PAGE_SIZE
enum lcdc_load_mode {
OMAP_LCDC_LOAD_PALETTE,
OMAP_LCDC_LOAD_FRAME,
OMAP_LCDC_LOAD_PALETTE_AND_FRAME
};
static struct omap_lcd_controller {
enum omapfb_update_mode update_mode;
int ext_mode;
unsigned long frame_offset;
int screen_width;
int xres;
int yres;
enum omapfb_color_format color_mode;
int bpp;
void *palette_virt;
dma_addr_t palette_phys;
int palette_code;
int palette_size;
unsigned int irq_mask;
struct completion last_frame_complete;
struct completion palette_load_complete;
struct clk *lcd_ck;
struct omapfb_device *fbdev;
void (*dma_callback)(void *data);
void *dma_callback_data;
int fbmem_allocated;
dma_addr_t vram_phys;
void *vram_virt;
unsigned long vram_size;
} lcdc;
static void inline enable_irqs(int mask)
{
lcdc.irq_mask |= mask;
}
static void inline disable_irqs(int mask)
{
lcdc.irq_mask &= ~mask;
}
static void set_load_mode(enum lcdc_load_mode mode)
{
u32 l;
l = omap_readl(OMAP_LCDC_CONTROL);
l &= ~(3 << 20);
switch (mode) {
case OMAP_LCDC_LOAD_PALETTE:
l |= 1 << 20;
break;
case OMAP_LCDC_LOAD_FRAME:
l |= 2 << 20;
break;
case OMAP_LCDC_LOAD_PALETTE_AND_FRAME:
break;
default:
BUG();
}
omap_writel(l, OMAP_LCDC_CONTROL);
}
static void enable_controller(void)
{
u32 l;
l = omap_readl(OMAP_LCDC_CONTROL);
l |= OMAP_LCDC_CTRL_LCD_EN;
l &= ~OMAP_LCDC_IRQ_MASK;
l |= lcdc.irq_mask | OMAP_LCDC_IRQ_DONE; /* enabled IRQs */
omap_writel(l, OMAP_LCDC_CONTROL);
}
static void disable_controller_async(void)
{
u32 l;
u32 mask;
l = omap_readl(OMAP_LCDC_CONTROL);
mask = OMAP_LCDC_CTRL_LCD_EN | OMAP_LCDC_IRQ_MASK;
/*
* Preserve the DONE mask, since we still want to get the
* final DONE irq. It will be disabled in the IRQ handler.
*/
mask &= ~OMAP_LCDC_IRQ_DONE;
l &= ~mask;
omap_writel(l, OMAP_LCDC_CONTROL);
}
static void disable_controller(void)
{
init_completion(&lcdc.last_frame_complete);
disable_controller_async();
if (!wait_for_completion_timeout(&lcdc.last_frame_complete,
msecs_to_jiffies(500)))
dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n");
}
static void reset_controller(u32 status)
{
static unsigned long reset_count;
static unsigned long last_jiffies;
disable_controller_async();
reset_count++;
if (reset_count == 1 || time_after(jiffies, last_jiffies + HZ)) {
dev_err(lcdc.fbdev->dev,
"resetting (status %#010x,reset count %lu)\n",
status, reset_count);
last_jiffies = jiffies;
}
if (reset_count < 100) {
enable_controller();
} else {
reset_count = 0;
dev_err(lcdc.fbdev->dev,
"too many reset attempts, giving up.\n");
}
}
/*
* Configure the LCD DMA according to the current mode specified by parameters
* in lcdc.fbdev and fbdev->var.
*/
static void setup_lcd_dma(void)
{
static const int dma_elem_type[] = {
0,
OMAP_DMA_DATA_TYPE_S8,
OMAP_DMA_DATA_TYPE_S16,
0,
OMAP_DMA_DATA_TYPE_S32,
};
struct omapfb_plane_struct *plane = lcdc.fbdev->fb_info[0]->par;
struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var;
unsigned long src;
int esize, xelem, yelem;
src = lcdc.vram_phys + lcdc.frame_offset;
switch (var->rotate) {
case 0:
if (plane->info.mirror || (src & 3) ||
lcdc.color_mode == OMAPFB_COLOR_YUV420 ||
(lcdc.xres & 1))
esize = 2;
else
esize = 4;
xelem = lcdc.xres * lcdc.bpp / 8 / esize;
yelem = lcdc.yres;
break;
case 90:
case 180:
case 270:
if (cpu_is_omap15xx()) {
BUG();
}
esize = 2;
xelem = lcdc.yres * lcdc.bpp / 16;
yelem = lcdc.xres;
break;
default:
BUG();
return;
}
#ifdef VERBOSE
dev_dbg(lcdc.fbdev->dev,
"setup_dma: src %#010lx esize %d xelem %d yelem %d\n",
src, esize, xelem, yelem);
#endif
omap_set_lcd_dma_b1(src, xelem, yelem, dma_elem_type[esize]);
if (!cpu_is_omap15xx()) {
int bpp = lcdc.bpp;
/*
* YUV support is only for external mode when we have the
* YUV window embedded in a 16bpp frame buffer.
*/
if (lcdc.color_mode == OMAPFB_COLOR_YUV420)
bpp = 16;
/* Set virtual xres elem size */
omap_set_lcd_dma_b1_vxres(
lcdc.screen_width * bpp / 8 / esize);
/* Setup transformations */
omap_set_lcd_dma_b1_rotation(var->rotate);
omap_set_lcd_dma_b1_mirror(plane->info.mirror);
}
omap_setup_lcd_dma();
}
static irqreturn_t lcdc_irq_handler(int irq, void *dev_id)
{
u32 status;
status = omap_readl(OMAP_LCDC_STATUS);
if (status & (OMAP_LCDC_STAT_FUF | OMAP_LCDC_STAT_SYNC_LOST))
reset_controller(status);
else {
if (status & OMAP_LCDC_STAT_DONE) {
u32 l;
/*
* Disable IRQ_DONE. The status bit will be cleared
* only when the controller is reenabled and we don't
* want to get more interrupts.
*/
l = omap_readl(OMAP_LCDC_CONTROL);
l &= ~OMAP_LCDC_IRQ_DONE;
omap_writel(l, OMAP_LCDC_CONTROL);
complete(&lcdc.last_frame_complete);
}
if (status & OMAP_LCDC_STAT_LOADED_PALETTE) {
disable_controller_async();
complete(&lcdc.palette_load_complete);
}
}
/*
* Clear these interrupt status bits.
* Sync_lost, FUF bits were cleared by disabling the LCD controller
* LOADED_PALETTE can be cleared this way only in palette only
* load mode. In other load modes it's cleared by disabling the
* controller.
*/
status &= ~(OMAP_LCDC_STAT_VSYNC |
OMAP_LCDC_STAT_LOADED_PALETTE |
OMAP_LCDC_STAT_ABC |
OMAP_LCDC_STAT_LINE_INT);
omap_writel(status, OMAP_LCDC_STATUS);
return IRQ_HANDLED;
}
/*
* Change to a new video mode. We defer this to a later time to avoid any
* flicker and not to mess up the current LCD DMA context. For this we disable
* the LCD controller, which will generate a DONE irq after the last frame has
* been transferred. Then it'll be safe to reconfigure both the LCD controller
* as well as the LCD DMA.
*/
static int omap_lcdc_setup_plane(int plane, int channel_out,
unsigned long offset, int screen_width,
int pos_x, int pos_y, int width, int height,
int color_mode)
{
struct fb_var_screeninfo *var = &lcdc.fbdev->fb_info[0]->var;
struct lcd_panel *panel = lcdc.fbdev->panel;
int rot_x, rot_y;
if (var->rotate == 0) {
rot_x = panel->x_res;
rot_y = panel->y_res;
} else {
rot_x = panel->y_res;
rot_y = panel->x_res;
}
if (plane != 0 || channel_out != 0 || pos_x != 0 || pos_y != 0 ||
width > rot_x || height > rot_y) {
#ifdef VERBOSE
dev_dbg(lcdc.fbdev->dev,
"invalid plane params plane %d pos_x %d pos_y %d "
"w %d h %d\n", plane, pos_x, pos_y, width, height);
#endif
return -EINVAL;
}
lcdc.frame_offset = offset;
lcdc.xres = width;
lcdc.yres = height;
lcdc.screen_width = screen_width;
lcdc.color_mode = color_mode;
switch (color_mode) {
case OMAPFB_COLOR_CLUT_8BPP:
lcdc.bpp = 8;
lcdc.palette_code = 0x3000;
lcdc.palette_size = 512;
break;
case OMAPFB_COLOR_RGB565:
lcdc.bpp = 16;
lcdc.palette_code = 0x4000;
lcdc.palette_size = 32;
break;
case OMAPFB_COLOR_RGB444:
lcdc.bpp = 16;
lcdc.palette_code = 0x4000;
lcdc.palette_size = 32;
break;
case OMAPFB_COLOR_YUV420:
if (lcdc.ext_mode) {
lcdc.bpp = 12;
break;
}
/* fallthrough */
case OMAPFB_COLOR_YUV422:
if (lcdc.ext_mode) {
lcdc.bpp = 16;
break;
}
/* fallthrough */
default:
/* FIXME: other BPPs.
* bpp1: code 0, size 256
* bpp2: code 0x1000 size 256
* bpp4: code 0x2000 size 256
* bpp12: code 0x4000 size 32
*/
dev_dbg(lcdc.fbdev->dev, "invalid color mode %d\n", color_mode);
BUG();
return -1;
}
if (lcdc.ext_mode) {
setup_lcd_dma();
return 0;
}
if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) {
disable_controller();
omap_stop_lcd_dma();
setup_lcd_dma();
enable_controller();
}
return 0;
}
static int omap_lcdc_enable_plane(int plane, int enable)
{
dev_dbg(lcdc.fbdev->dev,
"plane %d enable %d update_mode %d ext_mode %d\n",
plane, enable, lcdc.update_mode, lcdc.ext_mode);
if (plane != OMAPFB_PLANE_GFX)
return -EINVAL;
return 0;
}
/*
* Configure the LCD DMA for a palette load operation and do the palette
* downloading synchronously. We don't use the frame+palette load mode of
* the controller, since the palette can always be downloaded separately.
*/
static void load_palette(void)
{
u16 *palette;
palette = (u16 *)lcdc.palette_virt;
*(u16 *)palette &= 0x0fff;
*(u16 *)palette |= lcdc.palette_code;
omap_set_lcd_dma_b1(lcdc.palette_phys,
lcdc.palette_size / 4 + 1, 1, OMAP_DMA_DATA_TYPE_S32);
omap_set_lcd_dma_single_transfer(1);
omap_setup_lcd_dma();
init_completion(&lcdc.palette_load_complete);
enable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE);
set_load_mode(OMAP_LCDC_LOAD_PALETTE);
enable_controller();
if (!wait_for_completion_timeout(&lcdc.palette_load_complete,
msecs_to_jiffies(500)))
dev_err(lcdc.fbdev->dev, "timeout waiting for FRAME DONE\n");
/* The controller gets disabled in the irq handler */
disable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE);
omap_stop_lcd_dma();
omap_set_lcd_dma_single_transfer(lcdc.ext_mode);
}
/* Used only in internal controller mode */
static int omap_lcdc_setcolreg(u_int regno, u16 red, u16 green, u16 blue,
u16 transp, int update_hw_pal)
{
u16 *palette;
if (lcdc.color_mode != OMAPFB_COLOR_CLUT_8BPP || regno > 255)
return -EINVAL;
palette = (u16 *)lcdc.palette_virt;
palette[regno] &= ~0x0fff;
palette[regno] |= ((red >> 12) << 8) | ((green >> 12) << 4 ) |
(blue >> 12);
if (update_hw_pal) {
disable_controller();
omap_stop_lcd_dma();
load_palette();
setup_lcd_dma();
set_load_mode(OMAP_LCDC_LOAD_FRAME);
enable_controller();
}
return 0;
}
static void calc_ck_div(int is_tft, int pck, int *pck_div)
{
unsigned long lck;
pck = max(1, pck);
lck = clk_get_rate(lcdc.lcd_ck);
*pck_div = (lck + pck - 1) / pck;
if (is_tft)
*pck_div = max(2, *pck_div);
else
*pck_div = max(3, *pck_div);
if (*pck_div > 255) {
/* FIXME: try to adjust logic clock divider as well */
*pck_div = 255;
dev_warn(lcdc.fbdev->dev, "pixclock %d kHz too low.\n",
pck / 1000);
}
}
static void inline setup_regs(void)
{
u32 l;
struct lcd_panel *panel = lcdc.fbdev->panel;
int is_tft = panel->config & OMAP_LCDC_PANEL_TFT;
unsigned long lck;
int pcd;
l = omap_readl(OMAP_LCDC_CONTROL);
l &= ~OMAP_LCDC_CTRL_LCD_TFT;
l |= is_tft ? OMAP_LCDC_CTRL_LCD_TFT : 0;
#ifdef CONFIG_MACH_OMAP_PALMTE
/* FIXME:if (machine_is_omap_palmte()) { */
/* PalmTE uses alternate TFT setting in 8BPP mode */
l |= (is_tft && panel->bpp == 8) ? 0x810000 : 0;
/* } */
#endif
omap_writel(l, OMAP_LCDC_CONTROL);
l = omap_readl(OMAP_LCDC_TIMING2);
l &= ~(((1 << 6) - 1) << 20);
l |= (panel->config & OMAP_LCDC_SIGNAL_MASK) << 20;
omap_writel(l, OMAP_LCDC_TIMING2);
l = panel->x_res - 1;
l |= (panel->hsw - 1) << 10;
l |= (panel->hfp - 1) << 16;
l |= (panel->hbp - 1) << 24;
omap_writel(l, OMAP_LCDC_TIMING0);
l = panel->y_res - 1;
l |= (panel->vsw - 1) << 10;
l |= panel->vfp << 16;
l |= panel->vbp << 24;
omap_writel(l, OMAP_LCDC_TIMING1);
l = omap_readl(OMAP_LCDC_TIMING2);
l &= ~0xff;
lck = clk_get_rate(lcdc.lcd_ck);
if (!panel->pcd)
calc_ck_div(is_tft, panel->pixel_clock * 1000, &pcd);
else {
dev_warn(lcdc.fbdev->dev,
"Pixel clock divider value is obsolete.\n"
"Try to set pixel_clock to %lu and pcd to 0 "
"in drivers/video/omap/lcd_%s.c and submit a patch.\n",
lck / panel->pcd / 1000, panel->name);
pcd = panel->pcd;
}
l |= pcd & 0xff;
l |= panel->acb << 8;
omap_writel(l, OMAP_LCDC_TIMING2);
/* update panel info with the exact clock */
panel->pixel_clock = lck / pcd / 1000;
}
/*
* Configure the LCD controller, download the color palette and start a looped
* DMA transfer of the frame image data. Called only in internal
* controller mode.
*/
static int omap_lcdc_set_update_mode(enum omapfb_update_mode mode)
{
int r = 0;
if (mode != lcdc.update_mode) {
switch (mode) {
case OMAPFB_AUTO_UPDATE:
setup_regs();
load_palette();
/* Setup and start LCD DMA */
setup_lcd_dma();
set_load_mode(OMAP_LCDC_LOAD_FRAME);
enable_irqs(OMAP_LCDC_IRQ_DONE);
/* This will start the actual DMA transfer */
enable_controller();
lcdc.update_mode = mode;
break;
case OMAPFB_UPDATE_DISABLED:
disable_controller();
omap_stop_lcd_dma();
lcdc.update_mode = mode;
break;
default:
r = -EINVAL;
}
}
return r;
}
static enum omapfb_update_mode omap_lcdc_get_update_mode(void)
{
return lcdc.update_mode;
}
/* PM code called only in internal controller mode */
static void omap_lcdc_suspend(void)
{
OMAPFB: LCDC: change update_mode to DISABLED when going suspend I was observing the following error messages on my OMAP1 based Amstrad Delta board when first changing from text to graphics mode or vice versa after the LCD display had been blanked: omapfb omapfb: timeout waiting for FRAME DONE with a followup error message while unblanking it back: omapfb omapfb: resetting (status 0xffffffb2,reset count 1) As a visible result, image pixels happened to be shifted by a few bits, giving wrong colors. Examining the code, I found that this problem occures when an OMAP1 internal LCD controller is disabled from omap_lcdc_suspend() and then a subsequent omap_lcdc_setup_plane() calls disable_controller() again. This potentially error provoking behaviour is triggered by the lcdc.update_mode flag being kept at OMAP_AUTO_UPDATE, regardless of the controller and panel being suspended. This patch tries to correct the problem by replacing both omap_lcdc_suspend() and omap_lcdc_resume() function bodies with single calls to omap_lcdc_set_update_mode() with a respective OMAP_UPDATE_DISABLE or OMAP_AUTO_UPDATE argument. As a result, exactly the same lower level operations are performed, with addition of changing the lcdc.update_mode flag to a value better suited for the controller state. This prevents any further calls to disable_controller() from omap_lcdc_setup_plane() while the display is suspended. Created against linux-2.6.34-rc7. Tested on Amstrad Delta. Signed-off-by: Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@nokia.com>
2010-05-17 01:20:13 +00:00
omap_lcdc_set_update_mode(OMAPFB_UPDATE_DISABLED);
}
static void omap_lcdc_resume(void)
{
OMAPFB: LCDC: change update_mode to DISABLED when going suspend I was observing the following error messages on my OMAP1 based Amstrad Delta board when first changing from text to graphics mode or vice versa after the LCD display had been blanked: omapfb omapfb: timeout waiting for FRAME DONE with a followup error message while unblanking it back: omapfb omapfb: resetting (status 0xffffffb2,reset count 1) As a visible result, image pixels happened to be shifted by a few bits, giving wrong colors. Examining the code, I found that this problem occures when an OMAP1 internal LCD controller is disabled from omap_lcdc_suspend() and then a subsequent omap_lcdc_setup_plane() calls disable_controller() again. This potentially error provoking behaviour is triggered by the lcdc.update_mode flag being kept at OMAP_AUTO_UPDATE, regardless of the controller and panel being suspended. This patch tries to correct the problem by replacing both omap_lcdc_suspend() and omap_lcdc_resume() function bodies with single calls to omap_lcdc_set_update_mode() with a respective OMAP_UPDATE_DISABLE or OMAP_AUTO_UPDATE argument. As a result, exactly the same lower level operations are performed, with addition of changing the lcdc.update_mode flag to a value better suited for the controller state. This prevents any further calls to disable_controller() from omap_lcdc_setup_plane() while the display is suspended. Created against linux-2.6.34-rc7. Tested on Amstrad Delta. Signed-off-by: Janusz Krzysztofik <jkrzyszt@tis.icnet.pl> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@nokia.com>
2010-05-17 01:20:13 +00:00
omap_lcdc_set_update_mode(OMAPFB_AUTO_UPDATE);
}
static void omap_lcdc_get_caps(int plane, struct omapfb_caps *caps)
{
return;
}
int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data)
{
BUG_ON(callback == NULL);
if (lcdc.dma_callback)
return -EBUSY;
else {
lcdc.dma_callback = callback;
lcdc.dma_callback_data = data;
}
return 0;
}
EXPORT_SYMBOL(omap_lcdc_set_dma_callback);
void omap_lcdc_free_dma_callback(void)
{
lcdc.dma_callback = NULL;
}
EXPORT_SYMBOL(omap_lcdc_free_dma_callback);
static void lcdc_dma_handler(u16 status, void *data)
{
if (lcdc.dma_callback)
lcdc.dma_callback(lcdc.dma_callback_data);
}
static int mmap_kern(void)
{
struct vm_struct *kvma;
struct vm_area_struct vma;
pgprot_t pgprot;
unsigned long vaddr;
kvma = get_vm_area(lcdc.vram_size, VM_IOREMAP);
if (kvma == NULL) {
dev_err(lcdc.fbdev->dev, "can't get kernel vm area\n");
return -ENOMEM;
}
vma.vm_mm = &init_mm;
vaddr = (unsigned long)kvma->addr;
vma.vm_start = vaddr;
vma.vm_end = vaddr + lcdc.vram_size;
pgprot = pgprot_writecombine(pgprot_kernel);
if (io_remap_pfn_range(&vma, vaddr,
lcdc.vram_phys >> PAGE_SHIFT,
lcdc.vram_size, pgprot) < 0) {
dev_err(lcdc.fbdev->dev, "kernel mmap for FB memory failed\n");
return -EAGAIN;
}
lcdc.vram_virt = (void *)vaddr;
return 0;
}
static void unmap_kern(void)
{
vunmap(lcdc.vram_virt);
}
static int alloc_palette_ram(void)
{
lcdc.palette_virt = dma_alloc_writecombine(lcdc.fbdev->dev,
MAX_PALETTE_SIZE, &lcdc.palette_phys, GFP_KERNEL);
if (lcdc.palette_virt == NULL) {
dev_err(lcdc.fbdev->dev, "failed to alloc palette memory\n");
return -ENOMEM;
}
memset(lcdc.palette_virt, 0, MAX_PALETTE_SIZE);
return 0;
}
static void free_palette_ram(void)
{
dma_free_writecombine(lcdc.fbdev->dev, MAX_PALETTE_SIZE,
lcdc.palette_virt, lcdc.palette_phys);
}
static int alloc_fbmem(struct omapfb_mem_region *region)
{
int bpp;
int frame_size;
struct lcd_panel *panel = lcdc.fbdev->panel;
bpp = panel->bpp;
if (bpp == 12)
bpp = 16;
frame_size = PAGE_ALIGN(panel->x_res * bpp / 8 * panel->y_res);
if (region->size > frame_size)
frame_size = region->size;
lcdc.vram_size = frame_size;
lcdc.vram_virt = dma_alloc_writecombine(lcdc.fbdev->dev,
lcdc.vram_size, &lcdc.vram_phys, GFP_KERNEL);
if (lcdc.vram_virt == NULL) {
dev_err(lcdc.fbdev->dev, "unable to allocate FB DMA memory\n");
return -ENOMEM;
}
region->size = frame_size;
region->paddr = lcdc.vram_phys;
region->vaddr = lcdc.vram_virt;
region->alloc = 1;
memset(lcdc.vram_virt, 0, lcdc.vram_size);
return 0;
}
static void free_fbmem(void)
{
dma_free_writecombine(lcdc.fbdev->dev, lcdc.vram_size,
lcdc.vram_virt, lcdc.vram_phys);
}
static int setup_fbmem(struct omapfb_mem_desc *req_md)
{
int r;
if (!req_md->region_cnt) {
dev_err(lcdc.fbdev->dev, "no memory regions defined\n");
return -EINVAL;
}
if (req_md->region_cnt > 1) {
dev_err(lcdc.fbdev->dev, "only one plane is supported\n");
req_md->region_cnt = 1;
}
if (req_md->region[0].paddr == 0) {
lcdc.fbmem_allocated = 1;
if ((r = alloc_fbmem(&req_md->region[0])) < 0)
return r;
return 0;
}
lcdc.vram_phys = req_md->region[0].paddr;
lcdc.vram_size = req_md->region[0].size;
if ((r = mmap_kern()) < 0)
return r;
dev_dbg(lcdc.fbdev->dev, "vram at %08x size %08lx mapped to 0x%p\n",
lcdc.vram_phys, lcdc.vram_size, lcdc.vram_virt);
return 0;
}
static void cleanup_fbmem(void)
{
if (lcdc.fbmem_allocated)
free_fbmem();
else
unmap_kern();
}
static int omap_lcdc_init(struct omapfb_device *fbdev, int ext_mode,
struct omapfb_mem_desc *req_vram)
{
int r;
u32 l;
int rate;
struct clk *tc_ck;
lcdc.irq_mask = 0;
lcdc.fbdev = fbdev;
lcdc.ext_mode = ext_mode;
l = 0;
omap_writel(l, OMAP_LCDC_CONTROL);
/* FIXME:
* According to errata some platforms have a clock rate limitiation
*/
lcdc.lcd_ck = clk_get(fbdev->dev, "lcd_ck");
if (IS_ERR(lcdc.lcd_ck)) {
dev_err(fbdev->dev, "unable to access LCD clock\n");
r = PTR_ERR(lcdc.lcd_ck);
goto fail0;
}
tc_ck = clk_get(fbdev->dev, "tc_ck");
if (IS_ERR(tc_ck)) {
dev_err(fbdev->dev, "unable to access TC clock\n");
r = PTR_ERR(tc_ck);
goto fail1;
}
rate = clk_get_rate(tc_ck);
clk_put(tc_ck);
if (machine_is_ams_delta())
rate /= 4;
if (machine_is_omap_h3())
rate /= 3;
r = clk_set_rate(lcdc.lcd_ck, rate);
if (r) {
dev_err(fbdev->dev, "failed to adjust LCD rate\n");
goto fail1;
}
clk_enable(lcdc.lcd_ck);
r = request_irq(OMAP_LCDC_IRQ, lcdc_irq_handler, 0, MODULE_NAME, fbdev);
if (r) {
dev_err(fbdev->dev, "unable to get IRQ\n");
goto fail2;
}
r = omap_request_lcd_dma(lcdc_dma_handler, NULL);
if (r) {
dev_err(fbdev->dev, "unable to get LCD DMA\n");
goto fail3;
}
omap_set_lcd_dma_single_transfer(ext_mode);
omap_set_lcd_dma_ext_controller(ext_mode);
if (!ext_mode)
if ((r = alloc_palette_ram()) < 0)
goto fail4;
if ((r = setup_fbmem(req_vram)) < 0)
goto fail5;
pr_info("omapfb: LCDC initialized\n");
return 0;
fail5:
if (!ext_mode)
free_palette_ram();
fail4:
omap_free_lcd_dma();
fail3:
free_irq(OMAP_LCDC_IRQ, lcdc.fbdev);
fail2:
clk_disable(lcdc.lcd_ck);
fail1:
clk_put(lcdc.lcd_ck);
fail0:
return r;
}
static void omap_lcdc_cleanup(void)
{
if (!lcdc.ext_mode)
free_palette_ram();
cleanup_fbmem();
omap_free_lcd_dma();
free_irq(OMAP_LCDC_IRQ, lcdc.fbdev);
clk_disable(lcdc.lcd_ck);
clk_put(lcdc.lcd_ck);
}
const struct lcd_ctrl omap1_int_ctrl = {
.name = "internal",
.init = omap_lcdc_init,
.cleanup = omap_lcdc_cleanup,
.get_caps = omap_lcdc_get_caps,
.set_update_mode = omap_lcdc_set_update_mode,
.get_update_mode = omap_lcdc_get_update_mode,
.update_window = NULL,
.suspend = omap_lcdc_suspend,
.resume = omap_lcdc_resume,
.setup_plane = omap_lcdc_setup_plane,
.enable_plane = omap_lcdc_enable_plane,
.setcolreg = omap_lcdc_setcolreg,
};