This series has been 12 years in the making, it mostly finishes the work that was started with the founding of Linaro to clean up platform support in the kernel. The largest change here is a cleanup of the omap1 platform, which is the final ARM machine type to get converted to the common-clk subsystem. All the omap1 specific drivers are now made independent of the mach/*.h headers to allow the platform to be part of a generic ARMv4/v5 multiplatform kernel. The last bit that enables this support is still missing here while we wait for some last dependencies to make it into the mainline kernel through other subsystems. The s3c24xx, ixp4xx, iop32x, ep93xx and dove platforms were all almost at the point of allowing multiplatform kernels, this work gets completed here along with a few additional cleanup. At the same time, the s3c24xx and s3c64xx are now deprecated and expected to get removed in the future. The PXA and OMAP1 bits are in a separate branch because of dependencies. Once both branches are merged, only the three Intel StrongARM platforms (RiscPC, Footbridge/NetWinder and StrongARM1100) need separate kernels, and there are no plans to include these. -----BEGIN PGP SIGNATURE----- iQIzBAABCgAdFiEEo6/YBQwIrVS28WGKmmx57+YAGNkFAmKOP3sACgkQmmx57+YA GNk+DhAAmrPNuS8JDlCRPa76Nd9PC9aitnnEGYytQ6bgwexKd3qdvP7gdUtr7jlV 8k4KiGnnZZjEGd4i5cAVhSCyBbCt4oPKhato62KneEsO19xLsVmmTpQg1LPK75do mHYKpc+6932Lp6WrtI1F75id0phx684tpZp9P4ggXwMwgYkagq9rcO+mGUNZWDc8 D9SdAmoObtSCoBCYYbq2VhAPA79mSKKVpLGehzd+Gq5cuf/jJQD0u1E00izkdyZc r/5acQ7PHQlVXqSONYgCpkvDTqmjg9cvVCKeKLpFspV3f6vBVRgV60UGfwhpdPHY N119KUJtPf81xnLSxsqBFA34LMSerrH72YM5cYupKiiYcTDr+Yw6zrtNR6ktkt/B F1Tc/QV+A9CGergxljy39G1smEuwKtNiVA//NSlUORCHxgwa5XUB0mQIzNcWARa4 oMDLhBF7ES211CB7Yto2FR6gBQbh2A9HSpjOh6kxdHrRb4FCgoXjPhzBoMxPoSFu XIzJpMb18K4bI+hKRYddEOK5V0kHt9mzT7ViGT/2+n13IHKIGmKrZxwDH7mohAW9 4GF77gGbQsE9szajkx5EG1t+PWextQeeMyYW05bXO/mbDwA0n7EdjGpBeedvTZw3 6gUWVahfYp9hZWPdxJ4fbGnlbSovCq0y4tj5fbZHPh6AOAtmvWY= =CTtN -----END PGP SIGNATURE----- Merge tag 'arm-multiplatform-5.19-1' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc Pull ARMv4T/v5 multiplatform support from Arnd Bergmann: "This series has been 12 years in the making, it mostly finishes the work that was started with the founding of Linaro to clean up platform support in the kernel. The largest change here is a cleanup of the omap1 platform, which is the final ARM machine type to get converted to the common-clk subsystem. All the omap1 specific drivers are now made independent of the mach/*.h headers to allow the platform to be part of a generic ARMv4/v5 multiplatform kernel. The last bit that enables this support is still missing here while we wait for some last dependencies to make it into the mainline kernel through other subsystems. The s3c24xx, ixp4xx, iop32x, ep93xx and dove platforms were all almost at the point of allowing multiplatform kernels, this work gets completed here along with a few additional cleanup. At the same time, the s3c24xx and s3c64xx are now deprecated and expected to get removed in the future. The PXA and OMAP1 bits are in a separate branch because of dependencies. Once both branches are merged, only the three Intel StrongARM platforms (RiscPC, Footbridge/NetWinder and StrongARM1100) need separate kernels, and there are no plans to include these" * tag 'arm-multiplatform-5.19-1' of git://git.kernel.org/pub/scm/linux/kernel/git/soc/soc: (61 commits) ARM: ixp4xx: Consolidate Kconfig fixing issue ARM: versatile: Add missing of_node_put in dcscb_init ARM: config: Refresh IXP4xx config after multiplatform ARM: omap1: add back omap_set_dma_priority() stub ARM: omap: fix missing declaration warnings ARM: omap: fix address space warnings from sparse ARM: spear: remove include/mach/ subdirectory ARM: davinci: remove include/mach/ subdirectory ARM: omap2: remove include/mach/ subdirectory integrator: remove empty ap_init_early() ARM: s3c: fix include path MAINTAINERS: omap1: Add Janusz as an additional maintainer ARM: omap1: htc_herald: fix typos in comments ARM: OMAP1: fix typos in comments ARM: OMAP1: clock: Remove noop code ARM: OMAP1: clock: Remove unused code ARM: OMAP1: clock: Fix UART rate reporting algorithm ARM: OMAP1: clock: Fix early UART rate issues ARM: OMAP1: Prepare for conversion of OMAP1 clocks to CCF ARM: omap1: fix build with no SoC selected ...
782 lines
18 KiB
C
782 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* OMAP1 internal LCD controller
|
|
*
|
|
* Copyright (C) 2004 Nokia Corporation
|
|
* Author: Imre Deak <imre.deak@nokia.com>
|
|
*/
|
|
#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 <linux/gfp.h>
|
|
|
|
#include <linux/soc/ti/omap1-io.h>
|
|
#include <linux/soc/ti/omap1-soc.h>
|
|
#include <linux/omap-dma.h>
|
|
|
|
#include <asm/mach-types.h>
|
|
|
|
#include "omapfb.h"
|
|
|
|
#include "lcdc.h"
|
|
#include "lcd_dma.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;
|
|
|
|
dma_addr_t vram_phys;
|
|
void *vram_virt;
|
|
unsigned long vram_size;
|
|
} lcdc;
|
|
|
|
static inline void enable_irqs(int mask)
|
|
{
|
|
lcdc.irq_mask |= mask;
|
|
}
|
|
|
|
static inline void 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 inline void 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)
|
|
{
|
|
omap_lcdc_set_update_mode(OMAPFB_UPDATE_DISABLED);
|
|
}
|
|
|
|
static void omap_lcdc_resume(void)
|
|
{
|
|
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 alloc_palette_ram(void)
|
|
{
|
|
lcdc.palette_virt = dma_alloc_wc(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_wc(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_wc(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_wc(lcdc.fbdev->dev, lcdc.vram_size, lcdc.vram_virt,
|
|
lcdc.vram_phys);
|
|
}
|
|
|
|
static int setup_fbmem(struct omapfb_mem_desc *req_md)
|
|
{
|
|
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;
|
|
}
|
|
|
|
return alloc_fbmem(&req_md->region[0]);
|
|
}
|
|
|
|
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_prepare_enable(lcdc.lcd_ck);
|
|
|
|
r = request_irq(fbdev->int_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(fbdev->int_irq, lcdc.fbdev);
|
|
fail2:
|
|
clk_disable_unprepare(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();
|
|
free_fbmem();
|
|
omap_free_lcd_dma();
|
|
free_irq(lcdc.fbdev->int_irq, lcdc.fbdev);
|
|
clk_disable_unprepare(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,
|
|
};
|