Commit ab8cbc26 authored by Imre Deak's avatar Imre Deak Committed by Tony Lindgren

FB: Add support for OMAP framebuffer

Add support for OMAP framebuffer
Signed-off-by: default avatarImre Deak <imre.deak@solidboot.com>
parent 7dd3488c
......@@ -1809,6 +1809,10 @@ config FB_PS3_DEFAULT_SIZE_M
The default value can be overridden on the kernel command line
using the "ps3fb" option (e.g. "ps3fb=9M");
if ARCH_OMAP
source "drivers/video/omap/Kconfig"
endif
config FB_XILINX
tristate "Xilinx frame buffer support"
depends on FB && XILINX_VIRTEX
......
......@@ -119,6 +119,7 @@ obj-$(CONFIG_FB_VESA) += vesafb.o
obj-$(CONFIG_FB_IMAC) += imacfb.o
obj-$(CONFIG_FB_VGA16) += vga16fb.o
obj-$(CONFIG_FB_OF) += offb.o
obj-$(CONFIG_FB_OMAP) += omap/ cfbcopyarea.o cfbfillrect.o cfbimgblt.o
# the test framebuffer is last
obj-$(CONFIG_FB_VIRTUAL) += vfb.o
config FB_OMAP
tristate "OMAP frame buffer support (EXPERIMENTAL)"
depends on FB
help
Frame buffer driver for OMAP based boards.
config FB_OMAP_LCDC_EXTERNAL
bool "External LCD controller support"
depends on FB_OMAP
help
Say Y here, if you want to have support for boards with an
external LCD controller connected to the SoSSI/RFBI interface.
config FB_OMAP_LCDC_HWA742
bool "Epson HWA742 LCD controller support"
depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL
help
Say Y here if you want to have support for the external
Epson HWA742 LCD controller.
config FB_OMAP_LCDC_BLIZZARD
bool "Epson Blizzard LCD controller support"
depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL
help
Say Y here if you want to have support for the external
Epson Blizzard LCD controller.
config FB_OMAP_MANUAL_UPDATE
bool "Default to manual update mode"
depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL
help
Say Y here, if your user-space applications are capable of
notifying the frame buffer driver when a change has occured in
the frame buffer content and thus a reload of the image data to
the external frame buffer is required. If unsure, say N.
config FB_OMAP_LCD_MIPID
bool "MIPI DBI-C/DCS compatible LCD support"
select SPI
depends on FB_OMAP
help
Say Y here if you want to have support for LCDs compatible with
the Mobile Industry Processor Interface DBI-C/DCS
specification. (Supported LCDs: Philips LPH8923, Sharp LS041Y3)
config FB_OMAP_BOOTLOADER_INIT
bool "Check bootloader initializaion"
depends on FB_OMAP
help
Say Y here if you want to enable checking if the bootloader has
already initialized the display controller. In this case the
driver will skip the initialization.
config FB_OMAP_CONSISTENT_DMA_SIZE
int "Consistent DMA memory size (MB)"
depends on FB_OMAP
range 1 14
default 2
help
Increase the DMA consistent memory size according to your video
memory needs, for example if you want to use multiple planes.
The size must be 2MB aligned.
If unsure say 1.
config FB_OMAP_DMA_TUNE
bool "Set DMA SDRAM access priority high"
depends on FB_OMAP && ARCH_OMAP1
help
On systems in which video memory is in system memory
(SDRAM) this will speed up graphics DMA operations.
If you have such a system and want to use rotation
answer yes. Answer no if you have a dedicated video
memory, or don't use any of the accelerated features.
#
# Makefile for the new OMAP framebuffer device driver
#
obj-$(CONFIG_FB_OMAP) += omapfb.o
objs-yy := omapfb_main.o
objs-y$(CONFIG_ARCH_OMAP1) += lcdc.o
objs-y$(CONFIG_ARCH_OMAP2) += dispc.o
objs-$(CONFIG_ARCH_OMAP1)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += sossi.o
objs-$(CONFIG_ARCH_OMAP2)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += rfbi.o
objs-y$(CONFIG_FB_OMAP_LCDC_HWA742) += hwa742.o
objs-y$(CONFIG_FB_OMAP_LCDC_BLIZZARD) += blizzard.o
objs-y$(CONFIG_MACH_AMS_DELTA) += lcd_ams_delta.o
objs-y$(CONFIG_MACH_OMAP_H4) += lcd_h4.o
objs-y$(CONFIG_MACH_OMAP_H3) += lcd_h3.o
objs-y$(CONFIG_MACH_OMAP_H2) += lcd_h2.o
objs-y$(CONFIG_MACH_OMAP_PALMTE) += lcd_palmte.o
objs-y$(CONFIG_MACH_OMAP_PALMZ71) += lcd_palmz71.o
objs-y$(CONFIG_MACH_OMAP_PALMTT) += lcd_palmtt.o
objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1610.o
objs-$(CONFIG_ARCH_OMAP15XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1510.o
objs-y$(CONFIG_MACH_SX1) += lcd_sx1.o
objs-y$(CONFIG_MACH_OMAP_OSK) += lcd_osk.o
objs-y$(CONFIG_MACH_OMAP_PERSEUS2) += lcd_p2.o
objs-y$(CONFIG_MACH_OMAP_APOLLON) += lcd_apollon.o
objs-y$(CONFIG_FB_OMAP_LCD_MIPID) += lcd_mipid.o
omapfb-objs := $(objs-yy)
/*
* File: drivers/video/omap/blizzard.c
*
* Epson Blizzard LCD controller driver
*
* Copyright (C) 2004-2005 Nokia Corporation
* Authors: Juha Yrjola <juha.yrjola@nokia.com>
* Imre Deak <imre.deak@nokia.com>
* YUV support: Jussi Laako <jussi.laako@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/mm.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <asm/arch/dma.h>
#include <asm/arch/omapfb.h>
#include <asm/arch/blizzard.h>
#include "dispc.h"
#define MODULE_NAME "blizzard"
#define BLIZZARD_REV_CODE 0x00
#define BLIZZARD_CONFIG 0x02
#define BLIZZARD_PLL_DIV 0x04
#define BLIZZARD_PLL_LOCK_RANGE 0x06
#define BLIZZARD_PLL_CLOCK_SYNTH_0 0x08
#define BLIZZARD_PLL_CLOCK_SYNTH_1 0x0a
#define BLIZZARD_PLL_MODE 0x0c
#define BLIZZARD_CLK_SRC 0x0e
#define BLIZZARD_MEM_BANK0_ACTIVATE 0x10
#define BLIZZARD_MEM_BANK0_STATUS 0x14
#define BLIZZARD_HDISP 0x2a
#define BLIZZARD_HNDP 0x2c
#define BLIZZARD_VDISP0 0x2e
#define BLIZZARD_VDISP1 0x30
#define BLIZZARD_VNDP 0x32
#define BLIZZARD_HSW 0x34
#define BLIZZARD_VSW 0x38
#define BLIZZARD_DISPLAY_MODE 0x68
#define BLIZZARD_INPUT_WIN_X_START_0 0x6c
#define BLIZZARD_DATA_SOURCE_SELECT 0x8e
#define BLIZZARD_DISP_MEM_DATA_PORT 0x90
#define BLIZZARD_DISP_MEM_READ_ADDR0 0x92
#define BLIZZARD_POWER_SAVE 0xE6
#define BLIZZARD_NDISP_CTRL_STATUS 0xE8
/* Data source select */
/* For S1D13745 */
#define BLIZZARD_SRC_WRITE_LCD_BACKGROUND 0x00
#define BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE 0x01
#define BLIZZARD_SRC_WRITE_OVERLAY_ENABLE 0x04
#define BLIZZARD_SRC_DISABLE_OVERLAY 0x05
/* For S1D13744 */
#define BLIZZARD_SRC_WRITE_LCD 0x00
#define BLIZZARD_SRC_BLT_LCD 0x06
#define BLIZZARD_COLOR_RGB565 0x01
#define BLIZZARD_COLOR_YUV420 0x09
#define BLIZZARD_VERSION_S1D13745 0x01 /* Hailstorm */
#define BLIZZARD_VERSION_S1D13744 0x02 /* Blizzard */
#define BLIZZARD_AUTO_UPDATE_TIME (HZ / 20)
/* Reserve 4 request slots for requests in irq context */
#define REQ_POOL_SIZE 24
#define IRQ_REQ_POOL_SIZE 4
#define REQ_FROM_IRQ_POOL 0x01
#define REQ_COMPLETE 0
#define REQ_PENDING 1
struct blizzard_reg_list {
int start;
int end;
};
/* These need to be saved / restored separately from the rest. */
static struct blizzard_reg_list blizzard_pll_regs[] = {
{
.start = 0x04, /* Don't save PLL ctrl (0x0C) */
.end = 0x0a,
},
{
.start = 0x0e, /* Clock configuration */
.end = 0x0e,
},
};
static struct blizzard_reg_list blizzard_gen_regs[] = {
{
.start = 0x18, /* SDRAM control */
.end = 0x20,
},
{
.start = 0x28, /* LCD Panel configuration */
.end = 0x5a, /* HSSI interface, TV configuration */
},
};
static u8 blizzard_reg_cache[0x5a / 2];
struct update_param {
int plane;
int x, y, width, height;
int out_x, out_y;
int out_width, out_height;
int color_mode;
int bpp;
int flags;
};
struct blizzard_request {
struct list_head entry;
unsigned int flags;
int (*handler)(struct blizzard_request *req);
void (*complete)(void *data);
void *complete_data;
union {
struct update_param update;
struct completion *sync;
} par;
};
struct plane_info {
unsigned long offset;
int pos_x, pos_y;
int width, height;
int out_width, out_height;
int scr_width;
int color_mode;
int bpp;
};
struct blizzard_struct {
enum omapfb_update_mode update_mode;
enum omapfb_update_mode update_mode_before_suspend;
struct timer_list auto_update_timer;
int stop_auto_update;
struct omapfb_update_window auto_update_window;
int enabled_planes;
int vid_nonstd_color;
int vid_scaled;
int last_color_mode;
int zoom_on;
int screen_width;
int screen_height;
unsigned te_connected:1;
unsigned vsync_only:1;
struct plane_info plane[OMAPFB_PLANE_NUM];
struct blizzard_request req_pool[REQ_POOL_SIZE];
struct list_head pending_req_list;
struct list_head free_req_list;
struct semaphore req_sema;
spinlock_t req_lock;
unsigned long sys_ck_rate;
struct extif_timings reg_timings, lut_timings;
u32 max_transmit_size;
u32 extif_clk_period;
int extif_clk_div;
unsigned long pix_tx_time;
unsigned long line_upd_time;
struct omapfb_device *fbdev;
struct lcd_ctrl_extif *extif;
struct lcd_ctrl *int_ctrl;
void (*power_up)(struct device *dev);
void (*power_down)(struct device *dev);
int version;
} blizzard;
struct lcd_ctrl blizzard_ctrl;
static u8 blizzard_read_reg(u8 reg)
{
u8 data;
blizzard.extif->set_bits_per_cycle(8);
blizzard.extif->write_command(&reg, 1);
blizzard.extif->read_data(&data, 1);
return data;
}
static void blizzard_write_reg(u8 reg, u8 val)
{
blizzard.extif->set_bits_per_cycle(8);
blizzard.extif->write_command(&reg, 1);
blizzard.extif->write_data(&val, 1);
}
static void blizzard_restart_sdram(void)
{
unsigned long tmo;
blizzard_write_reg(BLIZZARD_MEM_BANK0_ACTIVATE, 0);
udelay(50);
blizzard_write_reg(BLIZZARD_MEM_BANK0_ACTIVATE, 1);
tmo = jiffies + msecs_to_jiffies(200);
while (!(blizzard_read_reg(BLIZZARD_MEM_BANK0_STATUS) & 0x01)) {
if (time_after(jiffies, tmo)) {
dev_err(blizzard.fbdev->dev,
"s1d1374x: SDRAM not ready");
break;
}
msleep(1);
}
}
static void blizzard_stop_sdram(void)
{
blizzard_write_reg(BLIZZARD_MEM_BANK0_ACTIVATE, 0);
}
/* Wait until the last window was completely written into the controllers
* SDRAM and we can start transferring the next window.
*/
static void blizzard_wait_line_buffer(void)
{
unsigned long tmo = jiffies + msecs_to_jiffies(30);
while (blizzard_read_reg(BLIZZARD_NDISP_CTRL_STATUS) & (1 << 7)) {
if (time_after(jiffies, tmo)) {
if (printk_ratelimit())
dev_err(blizzard.fbdev->dev,
"s1d1374x: line buffer not ready\n");
break;
}
}
}
/* Wait until the YYC color space converter is idle. */
static void blizzard_wait_yyc(void)
{
unsigned long tmo = jiffies + msecs_to_jiffies(30);
while (blizzard_read_reg(BLIZZARD_NDISP_CTRL_STATUS) & (1 << 4)) {
if (time_after(jiffies, tmo)) {
if (printk_ratelimit())
dev_err(blizzard.fbdev->dev,
"s1d1374x: YYC not ready\n");
break;
}
}
}
static void disable_overlay(void)
{
blizzard_write_reg(BLIZZARD_DATA_SOURCE_SELECT,
BLIZZARD_SRC_DISABLE_OVERLAY);
}
static void set_window_regs(int x_start, int y_start, int x_end, int y_end,
int x_out_start, int y_out_start,
int x_out_end, int y_out_end, int color_mode,
int zoom_off, int flags)
{
u8 tmp[18];
u8 cmd;
x_end--;
y_end--;
tmp[0] = x_start;
tmp[1] = x_start >> 8;
tmp[2] = y_start;
tmp[3] = y_start >> 8;
tmp[4] = x_end;
tmp[5] = x_end >> 8;
tmp[6] = y_end;
tmp[7] = y_end >> 8;
x_out_end--;
y_out_end--;
tmp[8] = x_out_start;
tmp[9] = x_out_start >> 8;
tmp[10] = y_out_start;
tmp[11] = y_out_start >> 8;
tmp[12] = x_out_end;
tmp[13] = x_out_end >> 8;
tmp[14] = y_out_end;
tmp[15] = y_out_end >> 8;
tmp[16] = color_mode;
if (zoom_off && blizzard.version == BLIZZARD_VERSION_S1D13745)
tmp[17] = BLIZZARD_SRC_WRITE_LCD_BACKGROUND;
else if (flags & OMAPFB_FORMAT_FLAG_ENABLE_OVERLAY)
tmp[17] = BLIZZARD_SRC_WRITE_OVERLAY_ENABLE;
else
tmp[17] = blizzard.version == BLIZZARD_VERSION_S1D13744 ?
BLIZZARD_SRC_WRITE_LCD :
BLIZZARD_SRC_WRITE_LCD_DESTRUCTIVE;
blizzard.extif->set_bits_per_cycle(8);
cmd = BLIZZARD_INPUT_WIN_X_START_0;
blizzard.extif->write_command(&cmd, 1);
blizzard.extif->write_data(tmp, 18);
}
static void enable_tearsync(int y, int width, int height, int screen_height,
int out_height, int force_vsync)
{
u8 b;
b = blizzard_read_reg(BLIZZARD_NDISP_CTRL_STATUS);
b |= 1 << 3;
blizzard_write_reg(BLIZZARD_NDISP_CTRL_STATUS, b);
if (likely(blizzard.vsync_only || force_vsync)) {
blizzard.extif->enable_tearsync(1, 0);
return;
}
if (width * blizzard.pix_tx_time < blizzard.line_upd_time) {
blizzard.extif->enable_tearsync(1, 0);
return;
}
if ((width * blizzard.pix_tx_time / 1000) * height <
(y + out_height) * (blizzard.line_upd_time / 1000)) {
blizzard.extif->enable_tearsync(1, 0);
return;
}
blizzard.extif->enable_tearsync(1, y + 1);
}
static void disable_tearsync(void)
{
u8 b;
blizzard.extif->enable_tearsync(0, 0);
b = blizzard_read_reg(BLIZZARD_NDISP_CTRL_STATUS);
b &= ~(1 << 3);
blizzard_write_reg(BLIZZARD_NDISP_CTRL_STATUS, b);
b = blizzard_read_reg(BLIZZARD_NDISP_CTRL_STATUS);
}
static inline void set_extif_timings(const struct extif_timings *t);
static inline struct blizzard_request *alloc_req(void)
{
unsigned long flags;
struct blizzard_request *req;
int req_flags = 0;
if (!in_interrupt())
down(&blizzard.req_sema);
else
req_flags = REQ_FROM_IRQ_POOL;
spin_lock_irqsave(&blizzard.req_lock, flags);
BUG_ON(list_empty(&blizzard.free_req_list));
req = list_entry(blizzard.free_req_list.next,
struct blizzard_request, entry);
list_del(&req->entry);
spin_unlock_irqrestore(&blizzard.req_lock, flags);
INIT_LIST_HEAD(&req->entry);
req->flags = req_flags;
return req;
}
static inline void free_req(struct blizzard_request *req)
{
unsigned long flags;
spin_lock_irqsave(&blizzard.req_lock, flags);
list_del(&req->entry);
list_add(&req->entry, &blizzard.free_req_list);
if (!(req->flags & REQ_FROM_IRQ_POOL))
up(&blizzard.req_sema);
spin_unlock_irqrestore(&blizzard.req_lock, flags);
}
static void process_pending_requests(void)
{
unsigned long flags;
spin_lock_irqsave(&blizzard.req_lock, flags);
while (!list_empty(&blizzard.pending_req_list)) {
struct blizzard_request *req;
void (*complete)(void *);
void *complete_data;
req = list_entry(blizzard.pending_req_list.next,
struct blizzard_request, entry);
spin_unlock_irqrestore(&blizzard.req_lock, flags);
if (req->handler(req) == REQ_PENDING)
return;
complete = req->complete;
complete_data = req->complete_data;
free_req(req);
if (complete)
complete(complete_data);
spin_lock_irqsave(&blizzard.req_lock, flags);
}
spin_unlock_irqrestore(&blizzard.req_lock, flags);
}
static void submit_req_list(struct list_head *head)
{
unsigned long flags;
int process = 1;
spin_lock_irqsave(&blizzard.req_lock, flags);
if (likely(!list_empty(&blizzard.pending_req_list)))
process = 0;
list_splice_init(head, blizzard.pending_req_list.prev);
spin_unlock_irqrestore(&blizzard.req_lock, flags);
if (process)
process_pending_requests();
}
static void request_complete(void *data)
{
struct blizzard_request *req = (struct blizzard_request *)data;
void (*complete)(void *);
void *complete_data;
complete = req->complete;
complete_data = req->complete_data;
free_req(req);
if (complete)
complete(complete_data);
process_pending_requests();
}
static int do_full_screen_update(struct blizzard_request *req)
{
int i;
int flags;
for (i = 0; i < 3; i++) {
struct plane_info *p = &blizzard.plane[i];
if (!(blizzard.enabled_planes & (1 << i))) {
blizzard.int_ctrl->enable_plane(i, 0);
continue;
}
dev_dbg(blizzard.fbdev->dev, "pw %d ph %d\n",
p->width, p->height);
blizzard.int_ctrl->setup_plane(i,
OMAPFB_CHANNEL_OUT_LCD, p->offset,
p->scr_width, p->pos_x, p->pos_y,
p->width, p->height,
p->color_mode);
blizzard.int_ctrl->enable_plane(i, 1);
}
dev_dbg(blizzard.fbdev->dev, "sw %d sh %d\n",
blizzard.screen_width, blizzard.screen_height);
blizzard_wait_line_buffer();
flags = req->par.update.flags;
if (flags & OMAPFB_FORMAT_FLAG_TEARSYNC)
enable_tearsync(0, blizzard.screen_width,
blizzard.screen_height,
blizzard.screen_height,
blizzard.screen_height,
flags & OMAPFB_FORMAT_FLAG_FORCE_VSYNC);
else
disable_tearsync();
set_window_regs(0, 0, blizzard.screen_width, blizzard.screen_height,
0, 0, blizzard.screen_width, blizzard.screen_height,
BLIZZARD_COLOR_RGB565, blizzard.zoom_on, flags);
blizzard.zoom_on = 0;
blizzard.extif->set_bits_per_cycle(16);
/* set_window_regs has left the register index at the right
* place, so no need to set it here.
*/
blizzard.extif->transfer_area(blizzard.screen_width,
blizzard.screen_height,
request_complete, req);
return REQ_PENDING;
}
/* Setup all planes with an overlapping area with the update window. */
static int do_partial_update(struct blizzard_request *req, int plane,
int x, int y, int w, int h,
int x_out, int y_out, int w_out, int h_out,
int wnd_color_mode, int bpp)
{
int i;
int gx1, gy1, gx2, gy2;
int gx1_out, gy1_out, gx2_out, gy2_out;
int color_mode;
int flags;
int zoom_off;
/* Global coordinates, relative to pixel 0,0 of the LCD */
gx1 = x + blizzard.plane[plane].pos_x;
gy1 = y + blizzard.plane[plane].pos_y;
gx2 = gx1 + w;
gy2 = gy1 + h;
flags = req->par.update.flags;
if (flags & OMAPFB_FORMAT_FLAG_DOUBLE) {
gx1_out = gx1;
gy1_out = gy1;
gx2_out = gx1 + w * 2;
gy2_out = gy1 + h * 2;
} else {
gx1_out = x_out + blizzard.plane[plane].pos_x;
gy1_out = y_out + blizzard.plane[plane].pos_y;
gx2_out = gx1_out + w_out;
gy2_out = gy1_out + h_out;
}
zoom_off = blizzard.zoom_on && gx1 == 0 && gy1 == 0 &&
w == blizzard.screen_width && h == blizzard.screen_height;
blizzard.zoom_on = (!zoom_off && blizzard.zoom_on) ||
(w < w_out || h < h_out);
for (i = 0; i < OMAPFB_PLANE_NUM; i++) {
struct plane_info *p = &blizzard.plane[i];
int px1, py1;
int px2, py2;
int pw, ph;
int pposx, pposy;
unsigned long offset;
if (!(blizzard.enabled_planes & (1 << i)) ||
(wnd_color_mode && i != plane)) {
blizzard.int_ctrl->enable_plane(i, 0);
continue;
}
/* Plane coordinates */
if (i == plane) {
/* Plane in which we are doing the update.
* Local coordinates are the one in the update
* request.
*/
px1 = x;
py1 = y;
px2 = x + w;
py2 = y + h;
pposx = 0;
pposy = 0;
} else {
/* Check if this plane has an overlapping part */
px1 = gx1 - p->pos_x;
py1 = gy1 - p->pos_y;
px2 = gx2 - p->pos_x;
py2 = gy2 - p->pos_y;
if (px1 >= p->width || py1 >= p->height ||
px2 <= 0 || py2 <= 0) {
blizzard.int_ctrl->enable_plane(i, 0);
continue;
}
/* Calculate the coordinates for the overlapping
* part in the plane's local coordinates.
*/
pposx = -px1;
pposy = -py1;
if (px1 < 0)
px1 = 0;
if (py1 < 0)
py1 = 0;
if (px2 > p->width)
px2 = p->width;
if (py2 > p->height)
py2 = p->height;
if (pposx < 0)
pposx = 0;
if (pposy < 0)
pposy = 0;
}
pw = px2 - px1;
ph = py2 - py1;
offset = p->offset + (p->scr_width * py1 + px1) * p->bpp / 8;
if (wnd_color_mode)
/* Window embedded in the plane with a differing
* color mode / bpp. Calculate the number of DMA
* transfer elements in terms of the plane's bpp.
*/
pw = (pw + 1) * bpp / p->bpp;
#ifdef VERBOSE
dev_dbg(blizzard.fbdev->dev,
"plane %d offset %#08lx pposx %d pposy %d "
"px1 %d py1 %d pw %d ph %d\n",
i, offset, pposx, pposy, px1, py1, pw, ph);
#endif
blizzard.int_ctrl->setup_plane(i,
OMAPFB_CHANNEL_OUT_LCD, offset,
p->scr_width,
pposx, pposy, pw, ph,
p->color_mode);
blizzard.int_ctrl->enable_plane(i, 1);
}
switch (wnd_color_mode) {
case OMAPFB_COLOR_YUV420:
color_mode = BLIZZARD_COLOR_YUV420;
/* Currently only the 16 bits/pixel cycle format is
* supported on the external interface. Adjust the number
* of transfer elements per line for 12bpp format.
*/
w = (w + 1) * 3 / 4;
break;
default:
color_mode = BLIZZARD_COLOR_RGB565;
break;
}
blizzard_wait_line_buffer();
if (blizzard.last_color_mode == BLIZZARD_COLOR_YUV420)
blizzard_wait_yyc();
blizzard.last_color_mode = color_mode;
if (flags & OMAPFB_FORMAT_FLAG_TEARSYNC)
enable_tearsync(gy1, w, h,
blizzard.screen_height,
h_out,
flags & OMAPFB_FORMAT_FLAG_FORCE_VSYNC);
else
disable_tearsync();
set_window_regs(gx1, gy1, gx2, gy2, gx1_out, gy1_out, gx2_out, gy2_out,
color_mode, zoom_off, flags);
blizzard.extif->set_bits_per_cycle(16);
/* set_window_regs has left the register index at the right
* place, so no need to set it here.
*/
blizzard.extif->transfer_area(w, h, request_complete, req);
return REQ_PENDING;
}
static int send_frame_handler(struct blizzard_request *req)
{
struct update_param *par = &req->par.update;
int plane = par->plane;
#ifdef VERBOSE
dev_dbg(blizzard.fbdev->dev,
"send_frame: x %d y %d w %d h %d "
"x_out %d y_out %d w_out %d h_out %d "
"color_mode %04x flags %04x planes %01x\n",
par->x, par->y, par->width, par->height,
par->out_x, par->out_y, par->out_width, par->out_height,
par->color_mode, par->flags, blizzard.enabled_planes);
#endif
if (par->flags & OMAPFB_FORMAT_FLAG_DISABLE_OVERLAY)
disable_overlay();
if ((blizzard.enabled_planes & blizzard.vid_nonstd_color) ||
(blizzard.enabled_planes & blizzard.vid_scaled))
return do_full_screen_update(req);
return do_partial_update(req, plane, par->x, par->y,
par->width, par->height,
par->out_x, par->out_y,
par->out_width, par->out_height,
par->color_mode, par->bpp);
}
static void send_frame_complete(void *data)
{
}
#define ADD_PREQ(_x, _y, _w, _h, _x_out, _y_out, _w_out, _h_out) do { \
req = alloc_req(); \
req->handler = send_frame_handler; \
req->complete = send_frame_complete; \
req->par.update.plane = plane_idx; \
req->par.update.x = _x; \
req->par.update.y = _y; \
req->par.update.width = _w; \
req->par.update.height = _h; \
req->par.update.out_x = _x_out; \
req->par.update.out_y = _y_out; \
req->par.update.out_width = _w_out; \
req->par.update.out_height = _h_out; \
req->par.update.bpp = bpp; \
req->par.update.color_mode = color_mode;\
req->par.update.flags = flags; \
list_add_tail(&req->entry, req_head); \
} while(0)
static void create_req_list(int plane_idx,
struct omapfb_update_window *win,
struct list_head *req_head)
{
struct blizzard_request *req;
int x = win->x;
int y = win->y;
int width = win->width;
int height = win->height;
int x_out = win->out_x;
int y_out = win->out_y;
int width_out = win->out_width;
int height_out = win->out_height;
int color_mode;
int bpp;
int flags;
unsigned int ystart = y;
unsigned int yspan = height;
unsigned int ystart_out = y_out;
unsigned int yspan_out = height_out;
flags = win->format & ~OMAPFB_FORMAT_MASK;
color_mode = win->format & OMAPFB_FORMAT_MASK;
switch (color_mode) {
case OMAPFB_COLOR_YUV420:
/* Embedded window with different color mode */
bpp = 12;
/* X, Y, height must be aligned at 2, width at 4 pixels */
x &= ~1;
y &= ~1;
height = yspan = height & ~1;
width = width & ~3;
break;
default:
/* Same as the plane color mode */
bpp = blizzard.plane[plane_idx].bpp;
break;
}
if (width * height * bpp / 8 > blizzard.max_transmit_size) {
yspan = blizzard.max_transmit_size / (width * bpp / 8);
yspan_out = yspan * height_out / height;
ADD_PREQ(x, ystart, width, yspan, x_out, ystart_out,
width_out, yspan_out);
ystart += yspan;
ystart_out += yspan_out;
yspan = height - yspan;
yspan_out = height_out - yspan_out;
flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC;
}
ADD_PREQ(x, ystart, width, yspan, x_out, ystart_out,
width_out, yspan_out);
}
static void auto_update_complete(void *data)
{
if (!blizzard.stop_auto_update)
mod_timer(&blizzard.auto_update_timer,
jiffies + BLIZZARD_AUTO_UPDATE_TIME);
}
static void blizzard_update_window_auto(unsigned long arg)
{
LIST_HEAD(req_list);
struct blizzard_request *last;
struct omapfb_plane_struct *plane;
plane = blizzard.fbdev->fb_info[0]->par;
create_req_list(plane->idx,
&blizzard.auto_update_window, &req_list);
last = list_entry(req_list.prev, struct blizzard_request, entry);
last->complete = auto_update_complete;
last->complete_data = NULL;
submit_req_list(&req_list);
}
int blizzard_update_window_async(struct fb_info *fbi,
struct omapfb_update_window *win,
void (*complete_callback)(void *arg),
void *complete_callback_data)
{
LIST_HEAD(req_list);
struct blizzard_request *last;
struct omapfb_plane_struct *plane = fbi->par;
if (unlikely(blizzard.update_mode != OMAPFB_MANUAL_UPDATE))
return -EINVAL;
if (unlikely(!blizzard.te_connected &&
(win->format & OMAPFB_FORMAT_FLAG_TEARSYNC)))
return -EINVAL;
create_req_list(plane->idx, win, &req_list);
last = list_entry(req_list.prev, struct blizzard_request, entry);
last->complete = complete_callback;
last->complete_data = (void *)complete_callback_data;
submit_req_list(&req_list);
return 0;
}
EXPORT_SYMBOL(blizzard_update_window_async);
static int update_full_screen(void)
{
return blizzard_update_window_async(blizzard.fbdev->fb_info[0],
&blizzard.auto_update_window, NULL, NULL);
}
static int blizzard_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 plane_info *p;
#ifdef VERBOSE
dev_dbg(blizzard.fbdev->dev,
"plane %d ch_out %d offset %#08lx scr_width %d "
"pos_x %d pos_y %d width %d height %d color_mode %d\n",
plane, channel_out, offset, screen_width,
pos_x, pos_y, width, height, color_mode);
#endif
if ((unsigned)plane > OMAPFB_PLANE_NUM)
return -EINVAL;
p = &blizzard.plane[plane];
switch (color_mode) {
case OMAPFB_COLOR_YUV422:
case OMAPFB_COLOR_YUY422:
p->bpp = 16;
blizzard.vid_nonstd_color &= ~(1 << plane);
break;
case OMAPFB_COLOR_YUV420:
p->bpp = 12;
blizzard.vid_nonstd_color |= 1 << plane;
break;
case OMAPFB_COLOR_RGB565:
p->bpp = 16;
blizzard.vid_nonstd_color &= ~(1 << plane);
break;
default:
return -EINVAL;
}
p->offset = offset;
p->pos_x = pos_x;
p->pos_y = pos_y;
p->width = width;
p->height = height;
p->scr_width = screen_width;
if (!p->out_width)
p->out_width = width;
if (!p->out_height)
p->out_height = height;
p->color_mode = color_mode;
return 0;
}
static int blizzard_set_scale(int plane, int orig_w, int orig_h,
int out_w, int out_h)
{
struct plane_info *p = &blizzard.plane[plane];
int r;
dev_dbg(blizzard.fbdev->dev,
"plane %d orig_w %d orig_h %d out_w %d out_h %d\n",
plane, orig_w, orig_h, out_w, out_h);
if ((unsigned)plane > OMAPFB_PLANE_NUM)
return -ENODEV;
r = blizzard.int_ctrl->set_scale(plane, orig_w, orig_h, out_w, out_h);
if (r < 0)
return r;
p->width = orig_w;
p->height = orig_h;
p->out_width = out_w;
p->out_height = out_h;
if (orig_w == out_w && orig_h == out_h)
blizzard.vid_scaled &= ~(1 << plane);
else
blizzard.vid_scaled |= 1 << plane;
return 0;
}
static int blizzard_enable_plane(int plane, int enable)
{
if (enable)
blizzard.enabled_planes |= 1 << plane;
else
blizzard.enabled_planes &= ~(1 << plane);
return 0;
}
static int sync_handler(struct blizzard_request *req)
{
complete(req->par.sync);
return REQ_COMPLETE;
}
static void blizzard_sync(void)
{
LIST_HEAD(req_list);
struct blizzard_request *req;
struct completion comp;
req = alloc_req();
req->handler = sync_handler;
req->complete = NULL;
init_completion(&comp);
req->par.sync = &comp;
list_add(&req->entry, &req_list);
submit_req_list(&req_list);
wait_for_completion(&comp);
}
static void blizzard_bind_client(struct omapfb_notifier_block *nb)
{
if (blizzard.update_mode == OMAPFB_MANUAL_UPDATE) {
omapfb_notify_clients(blizzard.fbdev, OMAPFB_EVENT_READY);
}
}
static int blizzard_set_update_mode(enum omapfb_update_mode mode)
{
if (unlikely(mode != OMAPFB_MANUAL_UPDATE &&
mode != OMAPFB_AUTO_UPDATE &&
mode != OMAPFB_UPDATE_DISABLED))
return -EINVAL;
if (mode == blizzard.update_mode)
return 0;
dev_info(blizzard.fbdev->dev, "s1d1374x: setting update mode to %s\n",
mode == OMAPFB_UPDATE_DISABLED ? "disabled" :
(mode == OMAPFB_AUTO_UPDATE ? "auto" : "manual"));
switch (blizzard.update_mode) {
case OMAPFB_MANUAL_UPDATE:
omapfb_notify_clients(blizzard.fbdev, OMAPFB_EVENT_DISABLED);
break;
case OMAPFB_AUTO_UPDATE:
blizzard.stop_auto_update = 1;
del_timer_sync(&blizzard.auto_update_timer);
break;
case OMAPFB_UPDATE_DISABLED:
break;
}
blizzard.update_mode = mode;
blizzard_sync();
blizzard.stop_auto_update = 0;
switch (mode) {
case OMAPFB_MANUAL_UPDATE:
omapfb_notify_clients(blizzard.fbdev, OMAPFB_EVENT_READY);
break;
case OMAPFB_AUTO_UPDATE:
blizzard_update_window_auto(0);
break;
case OMAPFB_UPDATE_DISABLED:
break;
}
return 0;
}
static enum omapfb_update_mode blizzard_get_update_mode(void)
{
return blizzard.update_mode;
}
static inline void set_extif_timings(const struct extif_timings *t)
{
blizzard.extif->set_timings(t);
}
static inline unsigned long round_to_extif_ticks(unsigned long ps, int div)
{
int bus_tick = blizzard.extif_clk_period * div;
return (ps + bus_tick - 1) / bus_tick * bus_tick;
}
static int calc_reg_timing(unsigned long sysclk, int div)
{
struct extif_timings *t;
unsigned long systim;
/* CSOnTime 0, WEOnTime 2 ns, REOnTime 2 ns,
* AccessTime 2 ns + 12.2 ns (regs),
* WEOffTime = WEOnTime + 1 ns,
* REOffTime = REOnTime + 12 ns (regs),
* CSOffTime = REOffTime + 1 ns
* ReadCycle = 2ns + 2*SYSCLK (regs),
* WriteCycle = 2*SYSCLK + 2 ns,
* CSPulseWidth = 10 ns */
systim = 1000000000 / (sysclk / 1000);
dev_dbg(blizzard.fbdev->dev,
"Blizzard systim %lu ps extif_clk_period %u div %d\n",
systim, blizzard.extif_clk_period, div);
t = &blizzard.reg_timings;
memset(t, 0, sizeof(*t));
t->clk_div = div;
t->cs_on_time = 0;
t->we_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->re_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->access_time = round_to_extif_ticks(t->re_on_time + 12200, div);
t->we_off_time = round_to_extif_ticks(t->we_on_time + 1000, div);
t->re_off_time = round_to_extif_ticks(t->re_on_time + 13000, div);
t->cs_off_time = round_to_extif_ticks(t->re_off_time + 1000, div);
t->we_cycle_time = round_to_extif_ticks(2 * systim + 2000, div);
if (t->we_cycle_time < t->we_off_time)
t->we_cycle_time = t->we_off_time;
t->re_cycle_time = round_to_extif_ticks(2 * systim + 2000, div);
if (t->re_cycle_time < t->re_off_time)
t->re_cycle_time = t->re_off_time;
t->cs_pulse_width = 0;
dev_dbg(blizzard.fbdev->dev, "[reg]cson %d csoff %d reon %d reoff %d\n",
t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time);
dev_dbg(blizzard.fbdev->dev, "[reg]weon %d weoff %d recyc %d wecyc %d\n",
t->we_on_time, t->we_off_time, t->re_cycle_time,
t->we_cycle_time);
dev_dbg(blizzard.fbdev->dev, "[reg]rdaccess %d cspulse %d\n",
t->access_time, t->cs_pulse_width);
return blizzard.extif->convert_timings(t);
}
static int calc_lut_timing(unsigned long sysclk, int div)
{
struct extif_timings *t;
unsigned long systim;
/* CSOnTime 0, WEOnTime 2 ns, REOnTime 2 ns,
* AccessTime 2 ns + 4 * SYSCLK + 26 (lut),
* WEOffTime = WEOnTime + 1 ns,
* REOffTime = REOnTime + 4*SYSCLK + 26 ns (lut),
* CSOffTime = REOffTime + 1 ns
* ReadCycle = 2ns + 4*SYSCLK + 26 ns (lut),
* WriteCycle = 2*SYSCLK + 2 ns,
* CSPulseWidth = 10 ns */
systim = 1000000000 / (sysclk / 1000);
dev_dbg(blizzard.fbdev->dev,
"Blizzard systim %lu ps extif_clk_period %u div %d\n",
systim, blizzard.extif_clk_period, div);
t = &blizzard.lut_timings;
memset(t, 0, sizeof(*t));
t->clk_div = div;
t->cs_on_time = 0;
t->we_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->re_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->access_time = round_to_extif_ticks(t->re_on_time + 4 * systim +
26000, div);
t->we_off_time = round_to_extif_ticks(t->we_on_time + 1000, div);
t->re_off_time = round_to_extif_ticks(t->re_on_time + 4 * systim +
26000, div);
t->cs_off_time = round_to_extif_ticks(t->re_off_time + 1000, div);
t->we_cycle_time = round_to_extif_ticks(2 * systim + 2000, div);
if (t->we_cycle_time < t->we_off_time)
t->we_cycle_time = t->we_off_time;
t->re_cycle_time = round_to_extif_ticks(2000 + 4 * systim + 26000, div);
if (t->re_cycle_time < t->re_off_time)
t->re_cycle_time = t->re_off_time;
t->cs_pulse_width = 0;
dev_dbg(blizzard.fbdev->dev,
"[lut]cson %d csoff %d reon %d reoff %d\n",
t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time);
dev_dbg(blizzard.fbdev->dev,
"[lut]weon %d weoff %d recyc %d wecyc %d\n",
t->we_on_time, t->we_off_time, t->re_cycle_time,
t->we_cycle_time);
dev_dbg(blizzard.fbdev->dev, "[lut]rdaccess %d cspulse %d\n",
t->access_time, t->cs_pulse_width);
return blizzard.extif->convert_timings(t);
}
static int calc_extif_timings(unsigned long sysclk, int *extif_mem_div)
{
int max_clk_div;
int div;
blizzard.extif->get_clk_info(&blizzard.extif_clk_period, &max_clk_div);
for (div = 1; div <= max_clk_div; div++) {
if (calc_reg_timing(sysclk, div) == 0)
break;
}
if (div > max_clk_div) {
dev_dbg(blizzard.fbdev->dev, "reg timing failed\n");
goto err;
}
*extif_mem_div = div;
for (div = 1; div <= max_clk_div; div++) {
if (calc_lut_timing(sysclk, div) == 0)
break;
}
if (div > max_clk_div)
goto err;
blizzard.extif_clk_div = div;
return 0;
err:
dev_err(blizzard.fbdev->dev, "can't setup timings\n");
return -1;
}
static void calc_blizzard_clk_rates(unsigned long ext_clk,
unsigned long *sys_clk, unsigned long *pix_clk)
{
int pix_clk_src;
int sys_div = 0, sys_mul = 0;
int pix_div;
pix_clk_src = blizzard_read_reg(BLIZZARD_CLK_SRC);
pix_div = ((pix_clk_src >> 3) & 0x1f) + 1;
if ((pix_clk_src & (0x3 << 1)) == 0) {
/* Source is the PLL */
sys_div = (blizzard_read_reg(BLIZZARD_PLL_DIV) & 0x3f) + 1;
sys_mul = blizzard_read_reg(BLIZZARD_PLL_CLOCK_SYNTH_0);
sys_mul |= ((blizzard_read_reg(BLIZZARD_PLL_CLOCK_SYNTH_1)
& 0x0f) << 11);
*sys_clk = ext_clk * sys_mul / sys_div;
} else /* else source is ext clk, or oscillator */
*sys_clk = ext_clk;
*pix_clk = *sys_clk / pix_div; /* HZ */
dev_dbg(blizzard.fbdev->dev,
"ext_clk %ld pix_src %d pix_div %d sys_div %d sys_mul %d\n",
ext_clk, pix_clk_src & (0x3 << 1), pix_div, sys_div, sys_mul);
dev_dbg(blizzard.fbdev->dev, "sys_clk %ld pix_clk %ld\n",
*sys_clk, *pix_clk);
}
static int setup_tearsync(unsigned long pix_clk, int extif_div)
{
int hdisp, vdisp;
int hndp, vndp;
int hsw, vsw;
int hs, vs;
int hs_pol_inv, vs_pol_inv;
int use_hsvs, use_ndp;
u8 b;
hsw = blizzard_read_reg(BLIZZARD_HSW);
vsw = blizzard_read_reg(BLIZZARD_VSW);
hs_pol_inv = !(hsw & 0x80);
vs_pol_inv = !(vsw & 0x80);
hsw = hsw & 0x7f;
vsw = vsw & 0x3f;
hdisp = blizzard_read_reg(BLIZZARD_HDISP) * 8;
vdisp = blizzard_read_reg(BLIZZARD_VDISP0) +
((blizzard_read_reg(BLIZZARD_VDISP1) & 0x3) << 8);
hndp = blizzard_read_reg(BLIZZARD_HNDP) & 0x3f;
vndp = blizzard_read_reg(BLIZZARD_VNDP);
/* time to transfer one pixel (16bpp) in ps */
blizzard.pix_tx_time = blizzard.reg_timings.we_cycle_time;
if (blizzard.extif->get_max_tx_rate != NULL) {
/* The external interface might have a rate limitation,
* if so, we have to maximize our transfer rate.
*/
unsigned long min_tx_time;
unsigned long max_tx_rate = blizzard.extif->get_max_tx_rate();
dev_dbg(blizzard.fbdev->dev, "max_tx_rate %ld HZ\n",
max_tx_rate);
min_tx_time = 1000000000 / (max_tx_rate / 1000); /* ps */
if (blizzard.pix_tx_time < min_tx_time)
blizzard.pix_tx_time = min_tx_time;
}
/* time to update one line in ps */
blizzard.line_upd_time = (hdisp + hndp) * 1000000 / (pix_clk / 1000);
blizzard.line_upd_time *= 1000;
if (hdisp * blizzard.pix_tx_time > blizzard.line_upd_time)
/* transfer speed too low, we might have to use both
* HS and VS */
use_hsvs = 1;
else
/* decent transfer speed, we'll always use only VS */
use_hsvs = 0;
if (use_hsvs && (hs_pol_inv || vs_pol_inv)) {
/* HS or'ed with VS doesn't work, use the active high
* TE signal based on HNDP / VNDP */
use_ndp = 1;
hs_pol_inv = 0;
vs_pol_inv = 0;
hs = hndp;
vs = vndp;
} else {
/* Use HS or'ed with VS as a TE signal if both are needed
* or VNDP if only vsync is needed. */
use_ndp = 0;
hs = hsw;
vs = vsw;
if (!use_hsvs) {
hs_pol_inv = 0;
vs_pol_inv = 0;
}
}
hs = hs * 1000000 / (pix_clk / 1000); /* ps */
hs *= 1000;
vs = vs * (hdisp + hndp) * 1000000 / (pix_clk / 1000); /* ps */
vs *= 1000;
if (vs <= hs)
return -EDOM;
/* set VS to 120% of HS to minimize VS detection time */
vs = hs * 12 / 10;
/* minimize HS too */
if (hs > 10000)
hs = 10000;
b = blizzard_read_reg(BLIZZARD_NDISP_CTRL_STATUS);
b &= ~0x3;
b |= use_hsvs ? 1 : 0;
b |= (use_ndp && use_hsvs) ? 0 : 2;
blizzard_write_reg(BLIZZARD_NDISP_CTRL_STATUS, b);
blizzard.vsync_only = !use_hsvs;
dev_dbg(blizzard.fbdev->dev,
"pix_clk %ld HZ pix_tx_time %ld ps line_upd_time %ld ps\n",
pix_clk, blizzard.pix_tx_time, blizzard.line_upd_time);
dev_dbg(blizzard.fbdev->dev,
"hs %d ps vs %d ps mode %d vsync_only %d\n",
hs, vs, b & 0x3, !use_hsvs);
return blizzard.extif->setup_tearsync(1, hs, vs,
hs_pol_inv, vs_pol_inv,
extif_div);
}
static void blizzard_get_caps(int plane, struct omapfb_caps *caps)
{
blizzard.int_ctrl->get_caps(plane, caps);
caps->ctrl |= OMAPFB_CAPS_MANUAL_UPDATE |
OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE |
OMAPFB_CAPS_WINDOW_SCALE |
OMAPFB_CAPS_WINDOW_OVERLAY;
if (blizzard.te_connected)
caps->ctrl |= OMAPFB_CAPS_TEARSYNC;
caps->wnd_color |= (1 << OMAPFB_COLOR_RGB565) |
(1 << OMAPFB_COLOR_YUV420);
}
static void _save_regs(struct blizzard_reg_list *list, int cnt)
{
int i;
for (i = 0; i < cnt; i++, list++) {
int reg;
for (reg = list->start; reg <= list->end; reg += 2)
blizzard_reg_cache[reg / 2] = blizzard_read_reg(reg);
}
}
static void _restore_regs(struct blizzard_reg_list *list, int cnt)
{
int i;
for (i = 0; i < cnt; i++, list++) {
int reg;
for (reg = list->start; reg <= list->end; reg += 2)
blizzard_write_reg(reg, blizzard_reg_cache[reg / 2]);
}
}
static void blizzard_save_all_regs(void)
{
_save_regs(blizzard_pll_regs, ARRAY_SIZE(blizzard_pll_regs));
_save_regs(blizzard_gen_regs, ARRAY_SIZE(blizzard_gen_regs));
}
static void blizzard_restore_pll_regs(void)
{
_restore_regs(blizzard_pll_regs, ARRAY_SIZE(blizzard_pll_regs));
}
static void blizzard_restore_gen_regs(void)
{
_restore_regs(blizzard_gen_regs, ARRAY_SIZE(blizzard_gen_regs));
}
static void blizzard_suspend(void)
{
u32 l;
unsigned long tmo;
if (blizzard.last_color_mode) {
update_full_screen();
blizzard_sync();
}
blizzard.update_mode_before_suspend = blizzard.update_mode;
/* the following will disable clocks as well */
blizzard_set_update_mode(OMAPFB_UPDATE_DISABLED);
blizzard_save_all_regs();
blizzard_stop_sdram();
l = blizzard_read_reg(BLIZZARD_POWER_SAVE);
/* Standby, Sleep. We assume we use an external clock. */
l |= 0x03;
blizzard_write_reg(BLIZZARD_POWER_SAVE, l);
tmo = jiffies + msecs_to_jiffies(100);
while (!(blizzard_read_reg(BLIZZARD_PLL_MODE) & (1 << 1))) {
if (time_after(jiffies, tmo)) {
dev_err(blizzard.fbdev->dev,
"s1d1374x: sleep timeout, stopping PLL manually\n");
l = blizzard_read_reg(BLIZZARD_PLL_MODE);
l &= ~0x03;
/* Disable PLL, counter function */
l |= 0x2;
blizzard_write_reg(BLIZZARD_PLL_MODE, l);
break;
}
msleep(1);
}
if (blizzard.power_down != NULL)
blizzard.power_down(blizzard.fbdev->dev);
}
static void blizzard_resume(void)
{
u32 l;
if (blizzard.power_up != NULL)
blizzard.power_up(blizzard.fbdev->dev);
l = blizzard_read_reg(BLIZZARD_POWER_SAVE);
/* Standby, Sleep */
l &= ~0x03;
blizzard_write_reg(BLIZZARD_POWER_SAVE, l);
blizzard_restore_pll_regs();
l = blizzard_read_reg(BLIZZARD_PLL_MODE);
l &= ~0x03;
/* Enable PLL, counter function */
l |= 0x1;
blizzard_write_reg(BLIZZARD_PLL_MODE, l);
while (!(blizzard_read_reg(BLIZZARD_PLL_DIV) & (1 << 7)))
msleep(1);
blizzard_restart_sdram();
blizzard_restore_gen_regs();
/* Enable display */
blizzard_write_reg(BLIZZARD_DISPLAY_MODE, 0x01);
/* the following will enable clocks as necessary */
blizzard_set_update_mode(blizzard.update_mode_before_suspend);
/* Force a background update */
blizzard.zoom_on = 1;
update_full_screen();
blizzard_sync();
}
static int blizzard_init(struct omapfb_device *fbdev, int ext_mode,
struct omapfb_mem_desc *req_vram)
{
int r = 0, i;
u8 rev, conf;
unsigned long ext_clk;
int extif_div;
unsigned long sys_clk, pix_clk;
struct omapfb_platform_data *omapfb_conf;
struct blizzard_platform_data *ctrl_conf;
blizzard.fbdev = fbdev;
BUG_ON(!fbdev->ext_if || !fbdev->int_ctrl);
blizzard.fbdev = fbdev;
blizzard.extif = fbdev->ext_if;
blizzard.int_ctrl = fbdev->int_ctrl;
omapfb_conf = fbdev->dev->platform_data;
ctrl_conf = omapfb_conf->ctrl_platform_data;
if (ctrl_conf == NULL || ctrl_conf->get_clock_rate == NULL) {
dev_err(fbdev->dev, "s1d1374x: missing platform data\n");
r = -ENOENT;
goto err1;
}
blizzard.power_down = ctrl_conf->power_down;
blizzard.power_up = ctrl_conf->power_up;
spin_lock_init(&blizzard.req_lock);
if ((r = blizzard.int_ctrl->init(fbdev, 1, req_vram)) < 0)
goto err1;
if ((r = blizzard.extif->init(fbdev)) < 0)
goto err2;
blizzard_ctrl.set_color_key = blizzard.int_ctrl->set_color_key;
blizzard_ctrl.get_color_key = blizzard.int_ctrl->get_color_key;
blizzard_ctrl.setup_mem = blizzard.int_ctrl->setup_mem;
blizzard_ctrl.mmap = blizzard.int_ctrl->mmap;
ext_clk = ctrl_conf->get_clock_rate(fbdev->dev);
if ((r = calc_extif_timings(ext_clk, &extif_div)) < 0)
goto err3;
set_extif_timings(&blizzard.reg_timings);
if (blizzard.power_up != NULL)
blizzard.power_up(fbdev->dev);
calc_blizzard_clk_rates(ext_clk, &sys_clk, &pix_clk);
if ((r = calc_extif_timings(sys_clk, &extif_div)) < 0)
goto err3;
set_extif_timings(&blizzard.reg_timings);
if (!(blizzard_read_reg(BLIZZARD_PLL_DIV) & 0x80)) {
dev_err(fbdev->dev,
"controller not initialized by the bootloader\n");
r = -ENODEV;
goto err3;
}
if (ctrl_conf->te_connected) {
if ((r = setup_tearsync(pix_clk, extif_div)) < 0)
goto err3;
blizzard.te_connected = 1;
}
rev = blizzard_read_reg(BLIZZARD_REV_CODE);
conf = blizzard_read_reg(BLIZZARD_CONFIG);
switch (rev & 0xfc) {
case 0x9c:
blizzard.version = BLIZZARD_VERSION_S1D13744;
pr_info("omapfb: s1d13744 LCD controller rev %d "
"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
break;
case 0xa4:
blizzard.version = BLIZZARD_VERSION_S1D13745;
pr_info("omapfb: s1d13745 LCD controller rev %d "
"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
break;
default:
dev_err(fbdev->dev, "invalid s1d1374x revision %02x\n",
rev);
r = -ENODEV;
goto err3;
}
blizzard.max_transmit_size = blizzard.extif->max_transmit_size;
blizzard.update_mode = OMAPFB_UPDATE_DISABLED;
blizzard.auto_update_window.x = 0;
blizzard.auto_update_window.y = 0;
blizzard.auto_update_window.width = fbdev->panel->x_res;
blizzard.auto_update_window.height = fbdev->panel->y_res;
blizzard.auto_update_window.out_x = 0;
blizzard.auto_update_window.out_x = 0;
blizzard.auto_update_window.out_width = fbdev->panel->x_res;
blizzard.auto_update_window.out_height = fbdev->panel->y_res;
blizzard.auto_update_window.format = 0;
blizzard.screen_width = fbdev->panel->x_res;
blizzard.screen_height = fbdev->panel->y_res;
init_timer(&blizzard.auto_update_timer);
blizzard.auto_update_timer.function = blizzard_update_window_auto;
blizzard.auto_update_timer.data = 0;
INIT_LIST_HEAD(&blizzard.free_req_list);
INIT_LIST_HEAD(&blizzard.pending_req_list);
for (i = 0; i < ARRAY_SIZE(blizzard.req_pool); i++)
list_add(&blizzard.req_pool[i].entry, &blizzard.free_req_list);
BUG_ON(i <= IRQ_REQ_POOL_SIZE);
sema_init(&blizzard.req_sema, i - IRQ_REQ_POOL_SIZE);
return 0;
err3:
if (blizzard.power_down != NULL)
blizzard.power_down(fbdev->dev);
blizzard.extif->cleanup();
err2:
blizzard.int_ctrl->cleanup();
err1:
return r;
}
static void blizzard_cleanup(void)
{
blizzard_set_update_mode(OMAPFB_UPDATE_DISABLED);
blizzard.extif->cleanup();
blizzard.int_ctrl->cleanup();
if (blizzard.power_down != NULL)
blizzard.power_down(blizzard.fbdev->dev);
}
struct lcd_ctrl blizzard_ctrl = {
.name = "blizzard",
.init = blizzard_init,
.cleanup = blizzard_cleanup,
.bind_client = blizzard_bind_client,
.get_caps = blizzard_get_caps,
.set_update_mode = blizzard_set_update_mode,
.get_update_mode = blizzard_get_update_mode,
.setup_plane = blizzard_setup_plane,
.set_scale = blizzard_set_scale,
.enable_plane = blizzard_enable_plane,
.update_window = blizzard_update_window_async,
.sync = blizzard_sync,
.suspend = blizzard_suspend,
.resume = blizzard_resume,
};
/*
* File: drivers/video/omap/omap2/dispc.c
*
* OMAP2 display controller support
*
* Copyright (C) 2005 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/kernel.h>
#include <linux/dma-mapping.h>
#include <linux/vmalloc.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/arch/sram.h>
#include <asm/arch/omapfb.h>
#include <asm/arch/board.h>
#include "dispc.h"
#define MODULE_NAME "dispc"
#define DSS_BASE 0x48050000
#define DSS_SYSCONFIG 0x0010
#define DISPC_BASE 0x48050400
/* DISPC common */
#define DISPC_REVISION 0x0000
#define DISPC_SYSCONFIG 0x0010
#define DISPC_SYSSTATUS 0x0014
#define DISPC_IRQSTATUS 0x0018
#define DISPC_IRQENABLE 0x001C
#define DISPC_CONTROL 0x0040
#define DISPC_CONFIG 0x0044
#define DISPC_CAPABLE 0x0048
#define DISPC_DEFAULT_COLOR0 0x004C
#define DISPC_DEFAULT_COLOR1 0x0050
#define DISPC_TRANS_COLOR0 0x0054
#define DISPC_TRANS_COLOR1 0x0058
#define DISPC_LINE_STATUS 0x005C
#define DISPC_LINE_NUMBER 0x0060
#define DISPC_TIMING_H 0x0064
#define DISPC_TIMING_V 0x0068
#define DISPC_POL_FREQ 0x006C
#define DISPC_DIVISOR 0x0070
#define DISPC_SIZE_DIG 0x0078
#define DISPC_SIZE_LCD 0x007C
#define DISPC_DATA_CYCLE1 0x01D4
#define DISPC_DATA_CYCLE2 0x01D8
#define DISPC_DATA_CYCLE3 0x01DC
/* DISPC GFX plane */
#define DISPC_GFX_BA0 0x0080
#define DISPC_GFX_BA1 0x0084
#define DISPC_GFX_POSITION 0x0088
#define DISPC_GFX_SIZE 0x008C
#define DISPC_GFX_ATTRIBUTES 0x00A0
#define DISPC_GFX_FIFO_THRESHOLD 0x00A4
#define DISPC_GFX_FIFO_SIZE_STATUS 0x00A8
#define DISPC_GFX_ROW_INC 0x00AC
#define DISPC_GFX_PIXEL_INC 0x00B0
#define DISPC_GFX_WINDOW_SKIP 0x00B4
#define DISPC_GFX_TABLE_BA 0x00B8
/* DISPC Video plane 1/2 */
#define DISPC_VID1_BASE 0x00BC
#define DISPC_VID2_BASE 0x014C
/* Offsets into DISPC_VID1/2_BASE */
#define DISPC_VID_BA0 0x0000
#define DISPC_VID_BA1 0x0004
#define DISPC_VID_POSITION 0x0008
#define DISPC_VID_SIZE 0x000C
#define DISPC_VID_ATTRIBUTES 0x0010
#define DISPC_VID_FIFO_THRESHOLD 0x0014
#define DISPC_VID_FIFO_SIZE_STATUS 0x0018
#define DISPC_VID_ROW_INC 0x001C
#define DISPC_VID_PIXEL_INC 0x0020
#define DISPC_VID_FIR 0x0024
#define DISPC_VID_PICTURE_SIZE 0x0028
#define DISPC_VID_ACCU0 0x002C
#define DISPC_VID_ACCU1 0x0030
/* 8 elements in 8 byte increments */
#define DISPC_VID_FIR_COEF_H0 0x0034
/* 8 elements in 8 byte increments */
#define DISPC_VID_FIR_COEF_HV0 0x0038
/* 5 elements in 4 byte increments */
#define DISPC_VID_CONV_COEF0 0x0074
#define DISPC_IRQ_FRAMEMASK 0x0001
#define DISPC_IRQ_VSYNC 0x0002
#define DISPC_IRQ_EVSYNC_EVEN 0x0004
#define DISPC_IRQ_EVSYNC_ODD 0x0008
#define DISPC_IRQ_ACBIAS_COUNT_STAT 0x0010
#define DISPC_IRQ_PROG_LINE_NUM 0x0020
#define DISPC_IRQ_GFX_FIFO_UNDERFLOW 0x0040
#define DISPC_IRQ_GFX_END_WIN 0x0080
#define DISPC_IRQ_PAL_GAMMA_MASK 0x0100
#define DISPC_IRQ_OCP_ERR 0x0200
#define DISPC_IRQ_VID1_FIFO_UNDERFLOW 0x0400
#define DISPC_IRQ_VID1_END_WIN 0x0800
#define DISPC_IRQ_VID2_FIFO_UNDERFLOW 0x1000
#define DISPC_IRQ_VID2_END_WIN 0x2000
#define DISPC_IRQ_SYNC_LOST 0x4000
#define DISPC_IRQ_MASK_ALL 0x7fff
#define DISPC_IRQ_MASK_ERROR (DISPC_IRQ_GFX_FIFO_UNDERFLOW | \
DISPC_IRQ_VID1_FIFO_UNDERFLOW | \
DISPC_IRQ_VID2_FIFO_UNDERFLOW | \
DISPC_IRQ_SYNC_LOST)
#define RFBI_CONTROL 0x48050040
#define MAX_PALETTE_SIZE (256 * 16)
#define FLD_MASK(pos, len) (((1 << len) - 1) << pos)
#define MOD_REG_FLD(reg, mask, val) \
dispc_write_reg((reg), (dispc_read_reg(reg) & ~(mask)) | (val));
#define OMAP2_SRAM_START 0x40200000
/* Maximum size, in reality this is smaller if SRAM is partially locked. */
#define OMAP2_SRAM_SIZE 0xa0000 /* 640k */
/* We support the SDRAM / SRAM types. See OMAPFB_PLANE_MEMTYPE_* in omapfb.h */
#define DISPC_MEMTYPE_NUM 2
#define RESMAP_SIZE(_page_cnt) \
((_page_cnt + (sizeof(unsigned long) * 8) - 1) / 8)
#define RESMAP_PTR(_res_map, _page_nr) \
(((_res_map)->map) + (_page_nr) / (sizeof(unsigned long) * 8))
#define RESMAP_MASK(_page_nr) \
(1 << ((_page_nr) & (sizeof(unsigned long) * 8 - 1)))
struct resmap {
unsigned long start;
unsigned page_cnt;
unsigned long *map;
};
static struct {
u32 base;
struct omapfb_mem_desc mem_desc;
struct resmap *res_map[DISPC_MEMTYPE_NUM];
atomic_t map_count[OMAPFB_PLANE_NUM];
dma_addr_t palette_paddr;
void *palette_vaddr;
int ext_mode;
unsigned long enabled_irqs;
void (*irq_callback)(void *);
void *irq_callback_data;
struct completion frame_done;
int fir_hinc[OMAPFB_PLANE_NUM];
int fir_vinc[OMAPFB_PLANE_NUM];
struct clk *dss_ick, *dss1_fck;
struct clk *dss_54m_fck;
enum omapfb_update_mode update_mode;
struct omapfb_device *fbdev;
struct omapfb_color_key color_key;
} dispc;
static void enable_lcd_clocks(int enable);
static void inline dispc_write_reg(int idx, u32 val)
{
__raw_writel(val, dispc.base + idx);
}
static u32 inline dispc_read_reg(int idx)
{
u32 l = __raw_readl(dispc.base + idx);
return l;
}
/* Select RFBI or bypass mode */
static void enable_rfbi_mode(int enable)
{
u32 l;
l = dispc_read_reg(DISPC_CONTROL);
/* Enable RFBI, GPIO0/1 */
l &= ~((1 << 11) | (1 << 15) | (1 << 16));
l |= enable ? (1 << 11) : 0;
/* RFBI En: GPIO0/1=10 RFBI Dis: GPIO0/1=11 */
l |= 1 << 15;
l |= enable ? 0 : (1 << 16);
dispc_write_reg(DISPC_CONTROL, l);
/* Set bypass mode in RFBI module */
l = __raw_readl(io_p2v(RFBI_CONTROL));
l |= enable ? 0 : (1 << 1);
__raw_writel(l, io_p2v(RFBI_CONTROL));
}
static void set_lcd_data_lines(int data_lines)
{
u32 l;
int code = 0;
switch (data_lines) {
case 12:
code = 0;
break;
case 16:
code = 1;
break;
case 18:
code = 2;
break;
case 24:
code = 3;
break;
default:
BUG();
}
l = dispc_read_reg(DISPC_CONTROL);
l &= ~(0x03 << 8);
l |= code << 8;
dispc_write_reg(DISPC_CONTROL, l);
}
static void set_load_mode(int mode)
{
BUG_ON(mode & ~(DISPC_LOAD_CLUT_ONLY | DISPC_LOAD_FRAME_ONLY |
DISPC_LOAD_CLUT_ONCE_FRAME));
MOD_REG_FLD(DISPC_CONFIG, 0x03 << 1, mode << 1);
}
void omap_dispc_set_lcd_size(int x, int y)
{
BUG_ON((x > (1 << 11)) || (y > (1 << 11)));
enable_lcd_clocks(1);
MOD_REG_FLD(DISPC_SIZE_LCD, FLD_MASK(16, 11) | FLD_MASK(0, 11),
((y - 1) << 16) | (x - 1));
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_set_lcd_size);
void omap_dispc_set_digit_size(int x, int y)
{
BUG_ON((x > (1 << 11)) || (y > (1 << 11)));
enable_lcd_clocks(1);
MOD_REG_FLD(DISPC_SIZE_DIG, FLD_MASK(16, 11) | FLD_MASK(0, 11),
((y - 1) << 16) | (x - 1));
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_set_digit_size);
static void setup_plane_fifo(int plane, int ext_mode)
{
const u32 ftrs_reg[] = { DISPC_GFX_FIFO_THRESHOLD,
DISPC_VID1_BASE + DISPC_VID_FIFO_THRESHOLD,
DISPC_VID2_BASE + DISPC_VID_FIFO_THRESHOLD };
const u32 fsz_reg[] = { DISPC_GFX_FIFO_SIZE_STATUS,
DISPC_VID1_BASE + DISPC_VID_FIFO_SIZE_STATUS,
DISPC_VID2_BASE + DISPC_VID_FIFO_SIZE_STATUS };
int low, high;
u32 l;
BUG_ON(plane > 2);
l = dispc_read_reg(fsz_reg[plane]);
l &= FLD_MASK(0, 9);
if (ext_mode) {
low = l * 3 / 4;
high = l;
} else {
low = l / 4;
high = l * 3 / 4;
}
MOD_REG_FLD(ftrs_reg[plane], FLD_MASK(16, 9) | FLD_MASK(0, 9),
(high << 16) | low);
}
void omap_dispc_enable_lcd_out(int enable)
{
enable_lcd_clocks(1);
MOD_REG_FLD(DISPC_CONTROL, 1, enable ? 1 : 0);
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_enable_lcd_out);
void omap_dispc_enable_digit_out(int enable)
{
enable_lcd_clocks(1);
MOD_REG_FLD(DISPC_CONTROL, 1 << 1, enable ? 1 << 1 : 0);
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_enable_digit_out);
static inline int _setup_plane(int plane, int channel_out,
u32 paddr, int screen_width,
int pos_x, int pos_y, int width, int height,
int color_mode)
{
const u32 at_reg[] = { DISPC_GFX_ATTRIBUTES,
DISPC_VID1_BASE + DISPC_VID_ATTRIBUTES,
DISPC_VID2_BASE + DISPC_VID_ATTRIBUTES };
const u32 ba_reg[] = { DISPC_GFX_BA0, DISPC_VID1_BASE + DISPC_VID_BA0,
DISPC_VID2_BASE + DISPC_VID_BA0 };
const u32 ps_reg[] = { DISPC_GFX_POSITION,
DISPC_VID1_BASE + DISPC_VID_POSITION,
DISPC_VID2_BASE + DISPC_VID_POSITION };
const u32 sz_reg[] = { DISPC_GFX_SIZE,
DISPC_VID1_BASE + DISPC_VID_PICTURE_SIZE,
DISPC_VID2_BASE + DISPC_VID_PICTURE_SIZE };
const u32 ri_reg[] = { DISPC_GFX_ROW_INC,
DISPC_VID1_BASE + DISPC_VID_ROW_INC,
DISPC_VID2_BASE + DISPC_VID_ROW_INC };
const u32 vs_reg[]= { 0, DISPC_VID1_BASE + DISPC_VID_SIZE,
DISPC_VID2_BASE + DISPC_VID_SIZE };
int chout_shift, burst_shift;
int chout_val;
int color_code;
int bpp;
int cconv_en;
int set_vsize;
u32 l;
#ifdef VERBOSE
dev_dbg(dispc.fbdev->dev, "plane %d channel %d paddr %#08x scr_width %d "
"pos_x %d pos_y %d width %d height %d color_mode %d\n",
plane, channel_out, paddr, screen_width, pos_x, pos_y,
width, height, color_mode);
#endif
set_vsize = 0;
switch (plane) {
case OMAPFB_PLANE_GFX:
burst_shift = 6;
chout_shift = 8;
break;
case OMAPFB_PLANE_VID1:
case OMAPFB_PLANE_VID2:
burst_shift = 14;
chout_shift = 16;
set_vsize = 1;
break;
default:
return -EINVAL;
}
switch (channel_out) {
case OMAPFB_CHANNEL_OUT_LCD:
chout_val = 0;
break;
case OMAPFB_CHANNEL_OUT_DIGIT:
chout_val = 1;
break;
default:
return -EINVAL;
}
cconv_en = 0;
switch (color_mode) {
case OMAPFB_COLOR_RGB565:
color_code = DISPC_RGB_16_BPP;
bpp = 16;
break;
case OMAPFB_COLOR_YUV422:
if (plane == 0)
return -EINVAL;
color_code = DISPC_UYVY_422;
cconv_en = 1;
bpp = 16;
break;
case OMAPFB_COLOR_YUY422:
if (plane == 0)
return -EINVAL;
color_code = DISPC_YUV2_422;
cconv_en = 1;
bpp = 16;
break;
default:
return -EINVAL;
}
l = dispc_read_reg(at_reg[plane]);
l &= ~(0x0f << 1);
l |= color_code << 1;
l &= ~(1 << 9);
l |= cconv_en << 9;
l &= ~(0x03 << burst_shift);
l |= DISPC_BURST_8x32 << burst_shift;
l &= ~(1 << chout_shift);
l |= chout_val << chout_shift;
dispc_write_reg(at_reg[plane], l);
dispc_write_reg(ba_reg[plane], paddr);
MOD_REG_FLD(ps_reg[plane],
FLD_MASK(16, 11) | FLD_MASK(0, 11), (pos_y << 16) | pos_x);
MOD_REG_FLD(sz_reg[plane], FLD_MASK(16, 11) | FLD_MASK(0, 11),
((height - 1) << 16) | (width - 1));
if (set_vsize) {
/* Set video size if set_scale hasn't set it */
if (!dispc.fir_vinc[plane])
MOD_REG_FLD(vs_reg[plane],
FLD_MASK(16, 11), (height - 1) << 16);
if (!dispc.fir_hinc[plane])
MOD_REG_FLD(vs_reg[plane],
FLD_MASK(0, 11), width - 1);
}
dispc_write_reg(ri_reg[plane], (screen_width - width) * bpp / 8 + 1);
return height * screen_width * bpp / 8;
}
static int omap_dispc_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)
{
u32 paddr;
int r;
if ((unsigned)plane > dispc.mem_desc.region_cnt)
return -EINVAL;
paddr = dispc.mem_desc.region[plane].paddr + offset;
enable_lcd_clocks(1);
r = _setup_plane(plane, channel_out, paddr,
screen_width,
pos_x, pos_y, width, height, color_mode);
enable_lcd_clocks(0);
return r;
}
static void write_firh_reg(int plane, int reg, u32 value)
{
u32 base;
if (plane == 1)
base = DISPC_VID1_BASE + DISPC_VID_FIR_COEF_H0;
else
base = DISPC_VID2_BASE + DISPC_VID_FIR_COEF_H0;
dispc_write_reg(base + reg * 8, value);
}
static void write_firhv_reg(int plane, int reg, u32 value)
{
u32 base;
if (plane == 1)
base = DISPC_VID1_BASE + DISPC_VID_FIR_COEF_HV0;
else
base = DISPC_VID2_BASE + DISPC_VID_FIR_COEF_HV0;
dispc_write_reg(base + reg * 8, value);
}
static void set_upsampling_coef_table(int plane)
{
const u32 coef[][2] = {
{ 0x00800000, 0x00800000 },
{ 0x0D7CF800, 0x037B02FF },
{ 0x1E70F5FF, 0x0C6F05FE },
{ 0x335FF5FE, 0x205907FB },
{ 0xF74949F7, 0x00404000 },
{ 0xF55F33FB, 0x075920FE },
{ 0xF5701EFE, 0x056F0CFF },
{ 0xF87C0DFF, 0x027B0300 },
};
int i;
for (i = 0; i < 8; i++) {
write_firh_reg(plane, i, coef[i][0]);
write_firhv_reg(plane, i, coef[i][1]);
}
}
static int omap_dispc_set_scale(int plane,
int orig_width, int orig_height,
int out_width, int out_height)
{
const u32 at_reg[] = { 0, DISPC_VID1_BASE + DISPC_VID_ATTRIBUTES,
DISPC_VID2_BASE + DISPC_VID_ATTRIBUTES };
const u32 vs_reg[] = { 0, DISPC_VID1_BASE + DISPC_VID_SIZE,
DISPC_VID2_BASE + DISPC_VID_SIZE };
const u32 fir_reg[] = { 0, DISPC_VID1_BASE + DISPC_VID_FIR,
DISPC_VID2_BASE + DISPC_VID_FIR };
u32 l;
int fir_hinc;
int fir_vinc;
if ((unsigned)plane > OMAPFB_PLANE_NUM)
return -ENODEV;
if (plane == OMAPFB_PLANE_GFX &&
(out_width != orig_width || out_height != orig_height))
return -EINVAL;
enable_lcd_clocks(1);
if (orig_width < out_width) {
/* Upsampling.
* Currently you can only scale both dimensions in one way.
*/
if (orig_height > out_height ||
orig_width * 8 < out_width ||
orig_height * 8 < out_height) {
enable_lcd_clocks(0);
return -EINVAL;
}
set_upsampling_coef_table(plane);
} else if (orig_width > out_width) {
/* Downsampling not yet supported
*/
enable_lcd_clocks(0);
return -EINVAL;
}
if (!orig_width || orig_width == out_width)
fir_hinc = 0;
else
fir_hinc = 1024 * orig_width / out_width;
if (!orig_height || orig_height == out_height)
fir_vinc = 0;
else
fir_vinc = 1024 * orig_height / out_height;
dispc.fir_hinc[plane] = fir_hinc;
dispc.fir_vinc[plane] = fir_vinc;
MOD_REG_FLD(fir_reg[plane],
FLD_MASK(16, 12) | FLD_MASK(0, 12),
((fir_vinc & 4095) << 16) |
(fir_hinc & 4095));
dev_dbg(dispc.fbdev->dev, "out_width %d out_height %d orig_width %d "
"orig_height %d fir_hinc %d fir_vinc %d\n",
out_width, out_height, orig_width, orig_height,
fir_hinc, fir_vinc);
MOD_REG_FLD(vs_reg[plane],
FLD_MASK(16, 11) | FLD_MASK(0, 11),
((out_height - 1) << 16) | (out_width - 1));
l = dispc_read_reg(at_reg[plane]);
l &= ~(0x03 << 5);
l |= fir_hinc ? (1 << 5) : 0;
l |= fir_vinc ? (1 << 6) : 0;
dispc_write_reg(at_reg[plane], l);
enable_lcd_clocks(0);
return 0;
}
static int omap_dispc_enable_plane(int plane, int enable)
{
const u32 at_reg[] = { DISPC_GFX_ATTRIBUTES,
DISPC_VID1_BASE + DISPC_VID_ATTRIBUTES,
DISPC_VID2_BASE + DISPC_VID_ATTRIBUTES };
if ((unsigned int)plane > dispc.mem_desc.region_cnt)
return -EINVAL;
enable_lcd_clocks(1);
MOD_REG_FLD(at_reg[plane], 1, enable ? 1 : 0);
enable_lcd_clocks(0);
return 0;
}
static int omap_dispc_set_color_key(struct omapfb_color_key *ck)
{
u32 df_reg, tr_reg;
int shift, val;
switch (ck->channel_out) {
case OMAPFB_CHANNEL_OUT_LCD:
df_reg = DISPC_DEFAULT_COLOR0;
tr_reg = DISPC_TRANS_COLOR0;
shift = 10;
break;
case OMAPFB_CHANNEL_OUT_DIGIT:
df_reg = DISPC_DEFAULT_COLOR1;
tr_reg = DISPC_TRANS_COLOR1;
shift = 12;
break;
default:
return -EINVAL;
}
switch (ck->key_type) {
case OMAPFB_COLOR_KEY_DISABLED:
val = 0;
break;
case OMAPFB_COLOR_KEY_GFX_DST:
val = 1;
break;
case OMAPFB_COLOR_KEY_VID_SRC:
val = 3;
break;
default:
return -EINVAL;
}
enable_lcd_clocks(1);
MOD_REG_FLD(DISPC_CONFIG, FLD_MASK(shift, 2), val << shift);
if (val != 0)
dispc_write_reg(tr_reg, ck->trans_key);
dispc_write_reg(df_reg, ck->background);
enable_lcd_clocks(0);
dispc.color_key = *ck;
return 0;
}
static int omap_dispc_get_color_key(struct omapfb_color_key *ck)
{
*ck = dispc.color_key;
return 0;
}
static void load_palette(void)
{
}
static int omap_dispc_set_update_mode(enum omapfb_update_mode mode)
{
int r = 0;
if (mode != dispc.update_mode) {
switch (mode) {
case OMAPFB_AUTO_UPDATE:
case OMAPFB_MANUAL_UPDATE:
enable_lcd_clocks(1);
omap_dispc_enable_lcd_out(1);
dispc.update_mode = mode;
break;
case OMAPFB_UPDATE_DISABLED:
init_completion(&dispc.frame_done);
omap_dispc_enable_lcd_out(0);
if (!wait_for_completion_timeout(&dispc.frame_done,
msecs_to_jiffies(500))) {
dev_err(dispc.fbdev->dev,
"timeout waiting for FRAME DONE\n");
}
dispc.update_mode = mode;
enable_lcd_clocks(0);
break;
default:
r = -EINVAL;
}
}
return r;
}
static void omap_dispc_get_caps(int plane, struct omapfb_caps *caps)
{
caps->ctrl |= OMAPFB_CAPS_PLANE_RELOCATE_MEM;
if (plane > 0)
caps->ctrl |= OMAPFB_CAPS_PLANE_SCALE;
caps->plane_color |= (1 << OMAPFB_COLOR_RGB565) |
(1 << OMAPFB_COLOR_YUV422) |
(1 << OMAPFB_COLOR_YUY422);
if (plane == 0)
caps->plane_color |= (1 << OMAPFB_COLOR_CLUT_8BPP) |
(1 << OMAPFB_COLOR_CLUT_4BPP) |
(1 << OMAPFB_COLOR_CLUT_2BPP) |
(1 << OMAPFB_COLOR_CLUT_1BPP) |
(1 << OMAPFB_COLOR_RGB444);
}
static enum omapfb_update_mode omap_dispc_get_update_mode(void)
{
return dispc.update_mode;
}
static void setup_color_conv_coef(void)
{
u32 mask = FLD_MASK(16, 11) | FLD_MASK(0, 11);
int cf1_reg = DISPC_VID1_BASE + DISPC_VID_CONV_COEF0;
int cf2_reg = DISPC_VID2_BASE + DISPC_VID_CONV_COEF0;
int at1_reg = DISPC_VID1_BASE + DISPC_VID_ATTRIBUTES;
int at2_reg = DISPC_VID2_BASE + DISPC_VID_ATTRIBUTES;
const struct color_conv_coef {
int ry, rcr, rcb, gy, gcr, gcb, by, bcr, bcb;
int full_range;
} ctbl_bt601_5 = {
298, 409, 0, 298, -208, -100, 298, 0, 517, 0,
};
#if 0
const struct color_conv_coef ctbl_bt601_5_full = {
256, 351, 0, 256, -179, -86, 256, 0, 443, 1,
}, ctbl_bt709 = {
298, 459, 0, 298, -137, -55, 298, 0, 541, 0,
}, ctbl_bt709_f = {
256, 394, 0, 256, -118, -47, 256, 0, 465, 1, },
#endif
const struct color_conv_coef *ct;
#define CVAL(x, y) (((x & 2047) << 16) | (y & 2047))
ct = &ctbl_bt601_5;
MOD_REG_FLD(cf1_reg, mask, CVAL(ct->rcr, ct->ry));
MOD_REG_FLD(cf1_reg + 4, mask, CVAL(ct->gy, ct->rcb));
MOD_REG_FLD(cf1_reg + 8, mask, CVAL(ct->gcb, ct->gcr));
MOD_REG_FLD(cf1_reg + 12, mask, CVAL(ct->bcr, ct->by));
MOD_REG_FLD(cf1_reg + 16, mask, CVAL(0, ct->bcb));
MOD_REG_FLD(cf2_reg, mask, CVAL(ct->rcr, ct->ry));
MOD_REG_FLD(cf2_reg + 4, mask, CVAL(ct->gy, ct->rcb));
MOD_REG_FLD(cf2_reg + 8, mask, CVAL(ct->gcb, ct->gcr));
MOD_REG_FLD(cf2_reg + 12, mask, CVAL(ct->bcr, ct->by));
MOD_REG_FLD(cf2_reg + 16, mask, CVAL(0, ct->bcb));
#undef CVAL
MOD_REG_FLD(at1_reg, (1 << 11), ct->full_range);
MOD_REG_FLD(at2_reg, (1 << 11), ct->full_range);
}
static void calc_ck_div(int is_tft, int pck, int *lck_div, int *pck_div)
{
unsigned long fck, lck;
*lck_div = 1;
pck = max(1, pck);
fck = clk_get_rate(dispc.dss1_fck);
lck = fck;
*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) {
*pck_div = 255;
lck = pck * *pck_div;
*lck_div = fck / lck;
BUG_ON(*lck_div < 1);
if (*lck_div > 255) {
*lck_div = 255;
dev_warn(dispc.fbdev->dev, "pixclock %d kHz too low.\n",
pck / 1000);
}
}
}
static void set_lcd_tft_mode(int enable)
{
u32 mask;
mask = 1 << 3;
MOD_REG_FLD(DISPC_CONTROL, mask, enable ? mask : 0);
}
static void set_lcd_timings(void)
{
u32 l;
int lck_div, pck_div;
struct lcd_panel *panel = dispc.fbdev->panel;
int is_tft = panel->config & OMAP_LCDC_PANEL_TFT;
unsigned long fck;
l = dispc_read_reg(DISPC_TIMING_H);
l &= ~(FLD_MASK(0, 6) | FLD_MASK(8, 8) | FLD_MASK(20, 8));
l |= ( max(1, (min(64, panel->hsw))) - 1 ) << 0;
l |= ( max(1, (min(256, panel->hfp))) - 1 ) << 8;
l |= ( max(1, (min(256, panel->hbp))) - 1 ) << 20;
dispc_write_reg(DISPC_TIMING_H, l);
l = dispc_read_reg(DISPC_TIMING_V);
l &= ~(FLD_MASK(0, 6) | FLD_MASK(8, 8) | FLD_MASK(20, 8));
l |= ( max(1, (min(64, panel->vsw))) - 1 ) << 0;
l |= ( max(0, (min(255, panel->vfp))) - 0 ) << 8;
l |= ( max(0, (min(255, panel->vbp))) - 0 ) << 20;
dispc_write_reg(DISPC_TIMING_V, l);
l = dispc_read_reg(DISPC_POL_FREQ);
l &= ~FLD_MASK(12, 6);
l |= (panel->config & OMAP_LCDC_SIGNAL_MASK) << 12;
l |= panel->acb & 0xff;
dispc_write_reg(DISPC_POL_FREQ, l);
calc_ck_div(is_tft, panel->pixel_clock * 1000, &lck_div, &pck_div);
l = dispc_read_reg(DISPC_DIVISOR);
l &= ~(FLD_MASK(16, 8) | FLD_MASK(0, 8));
l |= (lck_div << 16) | (pck_div << 0);
dispc_write_reg(DISPC_DIVISOR, l);
/* update panel info with the exact clock */
fck = clk_get_rate(dispc.dss1_fck);
panel->pixel_clock = fck / lck_div / pck_div / 1000;
}
int omap_dispc_request_irq(void (*callback)(void *data), void *data)
{
int r = 0;
BUG_ON(callback == NULL);
if (dispc.irq_callback)
r = -EBUSY;
else {
dispc.irq_callback = callback;
dispc.irq_callback_data = data;
}
return r;
}
EXPORT_SYMBOL(omap_dispc_request_irq);
void omap_dispc_enable_irqs(int irq_mask)
{
enable_lcd_clocks(1);
dispc.enabled_irqs = irq_mask;
irq_mask |= DISPC_IRQ_MASK_ERROR;
MOD_REG_FLD(DISPC_IRQENABLE, 0x7fff, irq_mask);
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_enable_irqs);
void omap_dispc_disable_irqs(int irq_mask)
{
enable_lcd_clocks(1);
dispc.enabled_irqs &= ~irq_mask;
irq_mask &= ~DISPC_IRQ_MASK_ERROR;
MOD_REG_FLD(DISPC_IRQENABLE, 0x7fff, irq_mask);
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_disable_irqs);
void omap_dispc_free_irq(void)
{
enable_lcd_clocks(1);
omap_dispc_disable_irqs(DISPC_IRQ_MASK_ALL);
dispc.irq_callback = NULL;
dispc.irq_callback_data = NULL;
enable_lcd_clocks(0);
}
EXPORT_SYMBOL(omap_dispc_free_irq);
static irqreturn_t omap_dispc_irq_handler(int irq, void *dev)
{
u32 stat = dispc_read_reg(DISPC_IRQSTATUS);
if (stat & DISPC_IRQ_FRAMEMASK)
complete(&dispc.frame_done);
if (stat & DISPC_IRQ_MASK_ERROR) {
if (printk_ratelimit()) {
dev_err(dispc.fbdev->dev, "irq error status %04x\n",
stat & 0x7fff);
}
}
if ((stat & dispc.enabled_irqs) && dispc.irq_callback)
dispc.irq_callback(dispc.irq_callback_data);
dispc_write_reg(DISPC_IRQSTATUS, stat);
return IRQ_HANDLED;
}
static int get_dss_clocks(void)
{
if (IS_ERR((dispc.dss_ick = clk_get(dispc.fbdev->dev, "dss_ick")))) {
dev_err(dispc.fbdev->dev, "can't get dss_ick");
return PTR_ERR(dispc.dss_ick);
}
if (IS_ERR((dispc.dss1_fck = clk_get(dispc.fbdev->dev, "dss1_fck")))) {
dev_err(dispc.fbdev->dev, "can't get dss1_fck");
clk_put(dispc.dss_ick);
return PTR_ERR(dispc.dss1_fck);
}
if (IS_ERR((dispc.dss_54m_fck =
clk_get(dispc.fbdev->dev, "dss_54m_fck")))) {
dev_err(dispc.fbdev->dev, "can't get dss_54m_fck");
clk_put(dispc.dss_ick);
clk_put(dispc.dss1_fck);
return PTR_ERR(dispc.dss_54m_fck);
}
return 0;
}
static void put_dss_clocks(void)
{
clk_put(dispc.dss_54m_fck);
clk_put(dispc.dss1_fck);
clk_put(dispc.dss_ick);
}
static void enable_lcd_clocks(int enable)
{
if (enable)
clk_enable(dispc.dss1_fck);
else
clk_disable(dispc.dss1_fck);
}
static void enable_interface_clocks(int enable)
{
if (enable)
clk_enable(dispc.dss_ick);
else
clk_disable(dispc.dss_ick);
}
static void enable_digit_clocks(int enable)
{
if (enable)
clk_enable(dispc.dss_54m_fck);
else
clk_disable(dispc.dss_54m_fck);
}
static void omap_dispc_suspend(void)
{
if (dispc.update_mode == OMAPFB_AUTO_UPDATE) {
init_completion(&dispc.frame_done);
omap_dispc_enable_lcd_out(0);
if (!wait_for_completion_timeout(&dispc.frame_done,
msecs_to_jiffies(500))) {
dev_err(dispc.fbdev->dev,
"timeout waiting for FRAME DONE\n");
}
enable_lcd_clocks(0);
}
}
static void omap_dispc_resume(void)
{
if (dispc.update_mode == OMAPFB_AUTO_UPDATE) {
enable_lcd_clocks(1);
if (!dispc.ext_mode) {
set_lcd_timings();
load_palette();
}
omap_dispc_enable_lcd_out(1);
}
}
static int omap_dispc_update_window(struct fb_info *fbi,
struct omapfb_update_window *win,
void (*complete_callback)(void *arg),
void *complete_callback_data)
{
return dispc.update_mode == OMAPFB_UPDATE_DISABLED ? -ENODEV : 0;
}
static int mmap_kern(struct omapfb_mem_region *region)
{
struct vm_struct *kvma;
struct vm_area_struct vma;
pgprot_t pgprot;
unsigned long vaddr;
kvma = get_vm_area(region->size, VM_IOREMAP);
if (kvma == NULL) {
dev_err(dispc.fbdev->dev, "can't get kernel vm area\n");
return -ENOMEM;
}
vma.vm_mm = &init_mm;
vaddr = (unsigned long)kvma->addr;
pgprot = pgprot_writecombine(pgprot_kernel);
vma.vm_start = vaddr;
vma.vm_end = vaddr + region->size;
if (io_remap_pfn_range(&vma, vaddr, region->paddr >> PAGE_SHIFT,
region->size, pgprot) < 0) {
dev_err(dispc.fbdev->dev, "kernel mmap for FBMEM failed\n");
return -EAGAIN;
}
region->vaddr = (void *)vaddr;
return 0;
}
static void mmap_user_open(struct vm_area_struct *vma)
{
int plane = (int)vma->vm_private_data;
atomic_inc(&dispc.map_count[plane]);
}
static void mmap_user_close(struct vm_area_struct *vma)
{
int plane = (int)vma->vm_private_data;
atomic_dec(&dispc.map_count[plane]);
}
static struct vm_operations_struct mmap_user_ops = {
.open = mmap_user_open,
.close = mmap_user_close,
};
static int omap_dispc_mmap_user(struct fb_info *info,
struct vm_area_struct *vma)
{
struct omapfb_plane_struct *plane = info->par;
unsigned long off;
unsigned long start;
u32 len;
if (vma->vm_end - vma->vm_start == 0)
return 0;
if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT))
return -EINVAL;
off = vma->vm_pgoff << PAGE_SHIFT;
start = info->fix.smem_start;
len = info->fix.smem_len;
if (off >= len)
return -EINVAL;
if ((vma->vm_end - vma->vm_start + off) > len)
return -EINVAL;
off += start;
vma->vm_pgoff = off >> PAGE_SHIFT;
vma->vm_flags |= VM_IO | VM_RESERVED;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
vma->vm_ops = &mmap_user_ops;
vma->vm_private_data = (void *)plane->idx;
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
return -EAGAIN;
/* vm_ops.open won't be called for mmap itself. */
atomic_inc(&dispc.map_count[plane->idx]);
return 0;
}
static void unmap_kern(struct omapfb_mem_region *region)
{
vunmap(region->vaddr);
}
static int alloc_palette_ram(void)
{
dispc.palette_vaddr = dma_alloc_writecombine(dispc.fbdev->dev,
MAX_PALETTE_SIZE, &dispc.palette_paddr, GFP_KERNEL);
if (dispc.palette_vaddr == NULL) {
dev_err(dispc.fbdev->dev, "failed to alloc palette memory\n");
return -ENOMEM;
}
return 0;
}
static void free_palette_ram(void)
{
dma_free_writecombine(dispc.fbdev->dev, MAX_PALETTE_SIZE,
dispc.palette_vaddr, dispc.palette_paddr);
}
static int alloc_fbmem(struct omapfb_mem_region *region)
{
region->vaddr = dma_alloc_writecombine(dispc.fbdev->dev,
region->size, &region->paddr, GFP_KERNEL);
if (region->vaddr == NULL) {
dev_err(dispc.fbdev->dev, "unable to allocate FB DMA memory\n");
return -ENOMEM;
}
return 0;
}
static void free_fbmem(struct omapfb_mem_region *region)
{
dma_free_writecombine(dispc.fbdev->dev, region->size,
region->vaddr, region->paddr);
}
static struct resmap *init_resmap(unsigned long start, size_t size)
{
unsigned page_cnt;
struct resmap *res_map;
page_cnt = PAGE_ALIGN(size) / PAGE_SIZE;
res_map =
kzalloc(sizeof(struct resmap) + RESMAP_SIZE(page_cnt), GFP_KERNEL);
if (res_map == NULL)
return NULL;
res_map->start = start;
res_map->page_cnt = page_cnt;
res_map->map = (unsigned long *)(res_map + 1);
return res_map;
}
static void cleanup_resmap(struct resmap *res_map)
{
kfree(res_map);
}
static inline int resmap_mem_type(unsigned long start)
{
if (start >= OMAP2_SRAM_START &&
start < OMAP2_SRAM_START + OMAP2_SRAM_SIZE)
return OMAPFB_MEMTYPE_SRAM;
else
return OMAPFB_MEMTYPE_SDRAM;
}
static inline int resmap_page_reserved(struct resmap *res_map, unsigned page_nr)
{
return *RESMAP_PTR(res_map, page_nr) & RESMAP_MASK(page_nr) ? 1 : 0;
}
static inline void resmap_reserve_page(struct resmap *res_map, unsigned page_nr)
{
BUG_ON(resmap_page_reserved(res_map, page_nr));
*RESMAP_PTR(res_map, page_nr) |= RESMAP_MASK(page_nr);
}
static inline void resmap_free_page(struct resmap *res_map, unsigned page_nr)
{
BUG_ON(!resmap_page_reserved(res_map, page_nr));
*RESMAP_PTR(res_map, page_nr) &= ~RESMAP_MASK(page_nr);
}
static void resmap_reserve_region(unsigned long start, size_t size)
{
struct resmap *res_map;
unsigned start_page;
unsigned end_page;
int mtype;
unsigned i;
mtype = resmap_mem_type(start);
res_map = dispc.res_map[mtype];
dev_dbg(dispc.fbdev->dev, "reserve mem type %d start %08lx size %d\n",
mtype, start, size);
start_page = (start - res_map->start) / PAGE_SIZE;
end_page = start_page + PAGE_ALIGN(size) / PAGE_SIZE;
for (i = start_page; i < end_page; i++)
resmap_reserve_page(res_map, i);
}
static void resmap_free_region(unsigned long start, size_t size)
{
struct resmap *res_map;
unsigned start_page;
unsigned end_page;
unsigned i;
int mtype;
mtype = resmap_mem_type(start);
res_map = dispc.res_map[mtype];
dev_dbg(dispc.fbdev->dev, "free mem type %d start %08lx size %d\n",
mtype, start, size);
start_page = (start - res_map->start) / PAGE_SIZE;
end_page = start_page + PAGE_ALIGN(size) / PAGE_SIZE;
for (i = start_page; i < end_page; i++)
resmap_free_page(res_map, i);
}
static unsigned long resmap_alloc_region(int mtype, size_t size)
{
unsigned i;
unsigned total;
unsigned start_page;
unsigned long start;
struct resmap *res_map = dispc.res_map[mtype];
BUG_ON(mtype >= DISPC_MEMTYPE_NUM || res_map == NULL || !size);
size = PAGE_ALIGN(size) / PAGE_SIZE;
start_page = 0;
total = 0;
for (i = 0; i < res_map->page_cnt; i++) {
if (resmap_page_reserved(res_map, i)) {
start_page = i + 1;
total = 0;
} else if (++total == size)
break;
}
if (total < size)
return 0;
start = res_map->start + start_page * PAGE_SIZE;
resmap_reserve_region(start, size * PAGE_SIZE);
return start;
}
/* Note that this will only work for user mappings, we don't deal with
* kernel mappings here, so fbcon will keep using the old region.
*/
static int omap_dispc_setup_mem(int plane, size_t size, int mem_type,
unsigned long *paddr)
{
struct omapfb_mem_region *rg;
unsigned long new_addr = 0;
if ((unsigned)plane > dispc.mem_desc.region_cnt)
return -EINVAL;
if (mem_type >= DISPC_MEMTYPE_NUM)
return -EINVAL;
if (dispc.res_map[mem_type] == NULL)
return -ENOMEM;
rg = &dispc.mem_desc.region[plane];
if (size == rg->size && mem_type == rg->type)
return 0;
if (atomic_read(&dispc.map_count[plane]))
return -EBUSY;
if (rg->size != 0)
resmap_free_region(rg->paddr, rg->size);
if (size != 0) {
new_addr = resmap_alloc_region(mem_type, size);
if (!new_addr) {
/* Reallocate old region. */
resmap_reserve_region(rg->paddr, rg->size);
return -ENOMEM;
}
}
rg->paddr = new_addr;
rg->size = size;
rg->type = mem_type;
*paddr = new_addr;
return 0;
}
static int setup_fbmem(struct omapfb_mem_desc *req_md)
{
struct omapfb_mem_region *rg;
int i;
int r;
unsigned long mem_start[DISPC_MEMTYPE_NUM];
unsigned long mem_end[DISPC_MEMTYPE_NUM];
if (!req_md->region_cnt) {
dev_err(dispc.fbdev->dev, "no memory regions defined\n");
return -ENOENT;
}
rg = &req_md->region[0];
memset(mem_start, 0xff, sizeof(mem_start));
memset(mem_end, 0, sizeof(mem_end));
for (i = 0; i < req_md->region_cnt; i++, rg++) {
int mtype;
if (rg->paddr) {
rg->alloc = 0;
if (rg->vaddr == NULL) {
rg->map = 1;
if ((r = mmap_kern(rg)) < 0)
return r;
}
} else {
if (rg->type != OMAPFB_MEMTYPE_SDRAM) {
dev_err(dispc.fbdev->dev,
"unsupported memory type\n");
return -EINVAL;
}
rg->alloc = rg->map = 1;
if ((r = alloc_fbmem(rg)) < 0)
return r;
}
mtype = rg->type;
if (rg->paddr < mem_start[mtype])
mem_start[mtype] = rg->paddr;
if (rg->paddr + rg->size > mem_end[mtype])
mem_end[mtype] = rg->paddr + rg->size;
}
for (i = 0; i < DISPC_MEMTYPE_NUM; i++) {
unsigned long start;
size_t size;
if (mem_end[i] == 0)
continue;
start = mem_start[i];
size = mem_end[i] - start;
dispc.res_map[i] = init_resmap(start, size);
r = -ENOMEM;
if (dispc.res_map[i] == NULL)
goto fail;
/* Initial state is that everything is reserved. This
* includes possible holes as well, which will never be
* freed.
*/
resmap_reserve_region(start, size);
}
dispc.mem_desc = *req_md;
return 0;
fail:
for (i = 0; i < DISPC_MEMTYPE_NUM; i++) {
if (dispc.res_map[i] != NULL)
cleanup_resmap(dispc.res_map[i]);
}
return r;
}
static void cleanup_fbmem(void)
{
struct omapfb_mem_region *rg;
int i;
for (i = 0; i < DISPC_MEMTYPE_NUM; i++) {
if (dispc.res_map[i] != NULL)
cleanup_resmap(dispc.res_map[i]);
}
rg = &dispc.mem_desc.region[0];
for (i = 0; i < dispc.mem_desc.region_cnt; i++, rg++) {
if (rg->alloc)
free_fbmem(rg);
else {
if (rg->map)
unmap_kern(rg);
}
}
}
static int omap_dispc_init(struct omapfb_device *fbdev, int ext_mode,
struct omapfb_mem_desc *req_vram)
{
int r;
u32 l;
struct lcd_panel *panel = fbdev->panel;
int tmo = 10000;
int skip_init = 0;
int i;
memset(&dispc, 0, sizeof(dispc));
dispc.base = io_p2v(DISPC_BASE);
dispc.fbdev = fbdev;
dispc.ext_mode = ext_mode;
init_completion(&dispc.frame_done);
if ((r = get_dss_clocks()) < 0)
return r;
enable_interface_clocks(1);
enable_lcd_clocks(1);
#ifdef CONFIG_FB_OMAP_BOOTLOADER_INIT
l = dispc_read_reg(DISPC_CONTROL);
/* LCD enabled ? */
if (l & 1) {
pr_info("omapfb: skipping hardware initialization\n");
skip_init = 1;
}
#endif
if (!skip_init) {
/* Reset monitoring works only w/ the 54M clk */
enable_digit_clocks(1);
/* Soft reset */
MOD_REG_FLD(DISPC_SYSCONFIG, 1 << 1, 1 << 1);
while (!(dispc_read_reg(DISPC_SYSSTATUS) & 1)) {
if (!--tmo) {
dev_err(dispc.fbdev->dev, "soft reset failed\n");
r = -ENODEV;
enable_digit_clocks(0);
goto fail1;
}
}
enable_digit_clocks(0);
}
/* Enable smart idle and autoidle */
l = dispc_read_reg(DISPC_CONTROL);
l &= ~((3 << 12) | (3 << 3));
l |= (2 << 12) | (2 << 3) | (1 << 0);
dispc_write_reg(DISPC_SYSCONFIG, l);
omap_writel(1 << 0, DSS_BASE + DSS_SYSCONFIG);
/* Set functional clock autogating */
l = dispc_read_reg(DISPC_CONFIG);
l |= 1 << 9;
dispc_write_reg(DISPC_CONFIG, l);
l = dispc_read_reg(DISPC_IRQSTATUS);
dispc_write_reg(l, DISPC_IRQSTATUS);
/* Enable those that we handle always */
omap_dispc_enable_irqs(DISPC_IRQ_FRAMEMASK);
if ((r = request_irq(INT_24XX_DSS_IRQ, omap_dispc_irq_handler,
0, MODULE_NAME, fbdev)) < 0) {
dev_err(dispc.fbdev->dev, "can't get DSS IRQ\n");
goto fail1;
}
/* L3 firewall setting: enable access to OCM RAM */
__raw_writel(0x402000b0, io_p2v(0x680050a0));
if ((r = alloc_palette_ram()) < 0)
goto fail2;
if ((r = setup_fbmem(req_vram)) < 0)
goto fail3;
if (!skip_init) {
for (i = 0; i < dispc.mem_desc.region_cnt; i++) {
memset(dispc.mem_desc.region[i].vaddr, 0,
dispc.mem_desc.region[i].size);
}
/* Set logic clock to fck, pixel clock to fck/2 for now */
MOD_REG_FLD(DISPC_DIVISOR, FLD_MASK(16, 8), 1 << 16);
MOD_REG_FLD(DISPC_DIVISOR, FLD_MASK(0, 8), 2 << 0);
setup_plane_fifo(0, ext_mode);
setup_plane_fifo(1, ext_mode);
setup_plane_fifo(2, ext_mode);
setup_color_conv_coef();
set_lcd_tft_mode(panel->config & OMAP_LCDC_PANEL_TFT);
set_load_mode(DISPC_LOAD_FRAME_ONLY);
if (!ext_mode) {
set_lcd_data_lines(panel->data_lines);
omap_dispc_set_lcd_size(panel->x_res, panel->y_res);
set_lcd_timings();
} else
set_lcd_data_lines(panel->bpp);
enable_rfbi_mode(ext_mode);
}
l = dispc_read_reg(DISPC_REVISION);
pr_info("omapfb: DISPC version %d.%d initialized\n",
l >> 4 & 0x0f, l & 0x0f);
enable_lcd_clocks(0);
return 0;
fail3:
free_palette_ram();
fail2:
free_irq(INT_24XX_DSS_IRQ, fbdev);
fail1:
enable_lcd_clocks(0);
enable_interface_clocks(0);
put_dss_clocks();
return r;
}
static void omap_dispc_cleanup(void)
{
int i;
omap_dispc_set_update_mode(OMAPFB_UPDATE_DISABLED);
/* This will also disable clocks that are on */
for (i = 0; i < dispc.mem_desc.region_cnt; i++)
omap_dispc_enable_plane(i, 0);
cleanup_fbmem();
free_palette_ram();
free_irq(INT_24XX_DSS_IRQ, dispc.fbdev);
enable_interface_clocks(0);
put_dss_clocks();
}
const struct lcd_ctrl omap2_int_ctrl = {
.name = "internal",
.init = omap_dispc_init,
.cleanup = omap_dispc_cleanup,
.get_caps = omap_dispc_get_caps,
.set_update_mode = omap_dispc_set_update_mode,
.get_update_mode = omap_dispc_get_update_mode,
.update_window = omap_dispc_update_window,
.suspend = omap_dispc_suspend,
.resume = omap_dispc_resume,
.setup_plane = omap_dispc_setup_plane,
.setup_mem = omap_dispc_setup_mem,
.set_scale = omap_dispc_set_scale,
.enable_plane = omap_dispc_enable_plane,
.set_color_key = omap_dispc_set_color_key,
.get_color_key = omap_dispc_get_color_key,
.mmap = omap_dispc_mmap_user,
};
#ifndef _DISPC_H
#define _DISPC_H
#include <linux/interrupt.h>
#define DISPC_PLANE_GFX 0
#define DISPC_PLANE_VID1 1
#define DISPC_PLANE_VID2 2
#define DISPC_RGB_1_BPP 0x00
#define DISPC_RGB_2_BPP 0x01
#define DISPC_RGB_4_BPP 0x02
#define DISPC_RGB_8_BPP 0x03
#define DISPC_RGB_12_BPP 0x04
#define DISPC_RGB_16_BPP 0x06
#define DISPC_RGB_24_BPP 0x08
#define DISPC_RGB_24_BPP_UNPACK_32 0x09
#define DISPC_YUV2_422 0x0a
#define DISPC_UYVY_422 0x0b
#define DISPC_BURST_4x32 0
#define DISPC_BURST_8x32 1
#define DISPC_BURST_16x32 2
#define DISPC_LOAD_CLUT_AND_FRAME 0x00
#define DISPC_LOAD_CLUT_ONLY 0x01
#define DISPC_LOAD_FRAME_ONLY 0x02
#define DISPC_LOAD_CLUT_ONCE_FRAME 0x03
#define DISPC_TFT_DATA_LINES_12 0
#define DISPC_TFT_DATA_LINES_16 1
#define DISPC_TFT_DATA_LINES_18 2
#define DISPC_TFT_DATA_LINES_24 3
extern void omap_dispc_set_lcd_size(int width, int height);
extern void omap_dispc_enable_lcd_out(int enable);
extern void omap_dispc_enable_digit_out(int enable);
extern int omap_dispc_request_irq(void (*callback)(void *data), void *data);
extern void omap_dispc_free_irq(void);
#endif
/*
* File: drivers/video/omap/hwa742.c
*
* Epson HWA742 LCD controller driver
*
* Copyright (C) 2004-2005 Nokia Corporation
* Authors: Juha Yrjölä <juha.yrjola@nokia.com>
* Imre Deak <imre.deak@nokia.com>
* YUV support: Jussi Laako <jussi.laako@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/mm.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/clk.h>
#include <asm/arch/dma.h>
#include <asm/arch/omapfb.h>
#include <asm/arch/hwa742.h>
#define HWA742_REV_CODE_REG 0x0
#define HWA742_CONFIG_REG 0x2
#define HWA742_PLL_DIV_REG 0x4
#define HWA742_PLL_0_REG 0x6
#define HWA742_PLL_1_REG 0x8
#define HWA742_PLL_2_REG 0xa
#define HWA742_PLL_3_REG 0xc
#define HWA742_PLL_4_REG 0xe
#define HWA742_CLK_SRC_REG 0x12
#define HWA742_PANEL_TYPE_REG 0x14
#define HWA742_H_DISP_REG 0x16
#define HWA742_H_NDP_REG 0x18
#define HWA742_V_DISP_1_REG 0x1a
#define HWA742_V_DISP_2_REG 0x1c
#define HWA742_V_NDP_REG 0x1e
#define HWA742_HS_W_REG 0x20
#define HWA742_HP_S_REG 0x22
#define HWA742_VS_W_REG 0x24
#define HWA742_VP_S_REG 0x26
#define HWA742_PCLK_POL_REG 0x28
#define HWA742_INPUT_MODE_REG 0x2a
#define HWA742_TRANSL_MODE_REG1 0x2e
#define HWA742_DISP_MODE_REG 0x34
#define HWA742_WINDOW_TYPE 0x36
#define HWA742_WINDOW_X_START_0 0x38
#define HWA742_WINDOW_X_START_1 0x3a
#define HWA742_WINDOW_Y_START_0 0x3c
#define HWA742_WINDOW_Y_START_1 0x3e
#define HWA742_WINDOW_X_END_0 0x40
#define HWA742_WINDOW_X_END_1 0x42
#define HWA742_WINDOW_Y_END_0 0x44
#define HWA742_WINDOW_Y_END_1 0x46
#define HWA742_MEMORY_WRITE_LSB 0x48
#define HWA742_MEMORY_WRITE_MSB 0x49
#define HWA742_MEMORY_READ_0 0x4a
#define HWA742_MEMORY_READ_1 0x4c
#define HWA742_MEMORY_READ_2 0x4e
#define HWA742_POWER_SAVE 0x56
#define HWA742_NDP_CTRL 0x58
#define HWA742_AUTO_UPDATE_TIME (HZ / 20)
/* Reserve 4 request slots for requests in irq context */
#define REQ_POOL_SIZE 24
#define IRQ_REQ_POOL_SIZE 4
#define REQ_FROM_IRQ_POOL 0x01
#define REQ_COMPLETE 0
#define REQ_PENDING 1
struct update_param {
int x, y, width, height;
int color_mode;
int flags;
};
struct hwa742_request {
struct list_head entry;
unsigned int flags;
int (*handler)(struct hwa742_request *req);
void (*complete)(void *data);
void *complete_data;
union {
struct update_param update;
struct completion *sync;
} par;
};
struct {
enum omapfb_update_mode update_mode;
enum omapfb_update_mode update_mode_before_suspend;
struct timer_list auto_update_timer;
int stop_auto_update;
struct omapfb_update_window auto_update_window;
unsigned te_connected:1;
unsigned vsync_only:1;
struct hwa742_request req_pool[REQ_POOL_SIZE];
struct list_head pending_req_list;
struct list_head free_req_list;
struct semaphore req_sema;
spinlock_t req_lock;
struct extif_timings reg_timings, lut_timings;
int prev_color_mode;
int prev_flags;
int window_type;
u32 max_transmit_size;
u32 extif_clk_period;
unsigned long pix_tx_time;
unsigned long line_upd_time;
struct omapfb_device *fbdev;
struct lcd_ctrl_extif *extif;
struct lcd_ctrl *int_ctrl;
void (*power_up)(struct device *dev);
void (*power_down)(struct device *dev);
} hwa742;
struct lcd_ctrl hwa742_ctrl;
static u8 hwa742_read_reg(u8 reg)
{
u8 data;
hwa742.extif->set_bits_per_cycle(8);
hwa742.extif->write_command(&reg, 1);
hwa742.extif->read_data(&data, 1);
return data;
}
static void hwa742_write_reg(u8 reg, u8 data)
{
hwa742.extif->set_bits_per_cycle(8);
hwa742.extif->write_command(&reg, 1);
hwa742.extif->write_data(&data, 1);
}
static void set_window_regs(int x_start, int y_start, int x_end, int y_end)
{
u8 tmp[8];
u8 cmd;
x_end--;
y_end--;
tmp[0] = x_start;
tmp[1] = x_start >> 8;
tmp[2] = y_start;
tmp[3] = y_start >> 8;
tmp[4] = x_end;
tmp[5] = x_end >> 8;
tmp[6] = y_end;
tmp[7] = y_end >> 8;
hwa742.extif->set_bits_per_cycle(8);
cmd = HWA742_WINDOW_X_START_0;
hwa742.extif->write_command(&cmd, 1);
hwa742.extif->write_data(tmp, 8);
}
static void set_format_regs(int conv, int transl, int flags)
{
if (flags & OMAPFB_FORMAT_FLAG_DOUBLE) {
hwa742.window_type = ((hwa742.window_type & 0xfc) | 0x01);
#ifdef VERBOSE
dev_dbg(hwa742.fbdev->dev, "hwa742: enabled pixel doubling\n");
#endif
} else {
hwa742.window_type = (hwa742.window_type & 0xfc);
#ifdef VERBOSE
dev_dbg(hwa742.fbdev->dev, "hwa742: disabled pixel doubling\n");
#endif
}
hwa742_write_reg(HWA742_INPUT_MODE_REG, conv);
hwa742_write_reg(HWA742_TRANSL_MODE_REG1, transl);
hwa742_write_reg(HWA742_WINDOW_TYPE, hwa742.window_type);
}
static void enable_tearsync(int y, int width, int height, int screen_height,
int force_vsync)
{
u8 b;
b = hwa742_read_reg(HWA742_NDP_CTRL);
b |= 1 << 2;
hwa742_write_reg(HWA742_NDP_CTRL, b);
if (likely(hwa742.vsync_only || force_vsync)) {
hwa742.extif->enable_tearsync(1, 0);
return;
}
if (width * hwa742.pix_tx_time < hwa742.line_upd_time) {
hwa742.extif->enable_tearsync(1, 0);
return;
}
if ((width * hwa742.pix_tx_time / 1000) * height <
(y + height) * (hwa742.line_upd_time / 1000)) {
hwa742.extif->enable_tearsync(1, 0);
return;
}
hwa742.extif->enable_tearsync(1, y + 1);
}
static void disable_tearsync(void)
{
u8 b;
hwa742.extif->enable_tearsync(0, 0);
b = hwa742_read_reg(HWA742_NDP_CTRL);
b &= ~(1 << 2);
hwa742_write_reg(HWA742_NDP_CTRL, b);
}
static inline struct hwa742_request *alloc_req(void)
{
unsigned long flags;
struct hwa742_request *req;
int req_flags = 0;
if (!in_interrupt())
down(&hwa742.req_sema);
else
req_flags = REQ_FROM_IRQ_POOL;
spin_lock_irqsave(&hwa742.req_lock, flags);
BUG_ON(list_empty(&hwa742.free_req_list));
req = list_entry(hwa742.free_req_list.next,
struct hwa742_request, entry);
list_del(&req->entry);
spin_unlock_irqrestore(&hwa742.req_lock, flags);
INIT_LIST_HEAD(&req->entry);
req->flags = req_flags;
return req;
}
static inline void free_req(struct hwa742_request *req)
{
unsigned long flags;
spin_lock_irqsave(&hwa742.req_lock, flags);
list_del(&req->entry);
list_add(&req->entry, &hwa742.free_req_list);
if (!(req->flags & REQ_FROM_IRQ_POOL))
up(&hwa742.req_sema);
spin_unlock_irqrestore(&hwa742.req_lock, flags);
}
static void process_pending_requests(void)
{
unsigned long flags;
spin_lock_irqsave(&hwa742.req_lock, flags);
while (!list_empty(&hwa742.pending_req_list)) {
struct hwa742_request *req;
void (*complete)(void *);
void *complete_data;
req = list_entry(hwa742.pending_req_list.next,
struct hwa742_request, entry);
spin_unlock_irqrestore(&hwa742.req_lock, flags);
if (req->handler(req) == REQ_PENDING)
return;
complete = req->complete;
complete_data = req->complete_data;
free_req(req);
if (complete)
complete(complete_data);
spin_lock_irqsave(&hwa742.req_lock, flags);
}
spin_unlock_irqrestore(&hwa742.req_lock, flags);
}
static void submit_req_list(struct list_head *head)
{
unsigned long flags;
int process = 1;
spin_lock_irqsave(&hwa742.req_lock, flags);
if (likely(!list_empty(&hwa742.pending_req_list)))
process = 0;
list_splice_init(head, hwa742.pending_req_list.prev);
spin_unlock_irqrestore(&hwa742.req_lock, flags);
if (process)
process_pending_requests();
}
static void request_complete(void *data)
{
struct hwa742_request *req = (struct hwa742_request *)data;
void (*complete)(void *);
void *complete_data;
complete = req->complete;
complete_data = req->complete_data;
free_req(req);
if (complete)
complete(complete_data);
process_pending_requests();
}
static int send_frame_handler(struct hwa742_request *req)
{
struct update_param *par = &req->par.update;
int x = par->x;
int y = par->y;
int w = par->width;
int h = par->height;
int bpp;
int conv, transl;
unsigned long offset;
int color_mode = par->color_mode;
int flags = par->flags;
int scr_width = hwa742.fbdev->panel->x_res;
int scr_height = hwa742.fbdev->panel->y_res;
#ifdef VERBOSE
dev_dbg(hwa742.fbdev->dev, "x %d y %d w %d h %d scr_width %d "
"color_mode %d flags %d\n",
x, y, w, h, scr_width, color_mode, flags);
#endif
switch (color_mode) {
case OMAPFB_COLOR_YUV422:
bpp = 16;
conv = 0x08;
transl = 0x25;
break;
case OMAPFB_COLOR_YUV420:
bpp = 12;
conv = 0x09;
transl = 0x25;
break;
case OMAPFB_COLOR_RGB565:
bpp = 16;
conv = 0x01;
transl = 0x05;
break;
default:
return -EINVAL;
}
if (hwa742.prev_flags != flags ||
hwa742.prev_color_mode != color_mode) {
set_format_regs(conv, transl, flags);
hwa742.prev_color_mode = color_mode;
hwa742.prev_flags = flags;
}
flags = req->par.update.flags;
if (flags & OMAPFB_FORMAT_FLAG_TEARSYNC)
enable_tearsync(y, scr_width, h, scr_height,
flags & OMAPFB_FORMAT_FLAG_FORCE_VSYNC);
else
disable_tearsync();
set_window_regs(x, y, x + w, y + h);
offset = (scr_width * y + x) * bpp / 8;
hwa742.int_ctrl->setup_plane(OMAPFB_PLANE_GFX,
OMAPFB_CHANNEL_OUT_LCD, offset, scr_width, 0, 0, w, h,
color_mode);
hwa742.extif->set_bits_per_cycle(16);
hwa742.int_ctrl->enable_plane(OMAPFB_PLANE_GFX, 1);
hwa742.extif->transfer_area(w, h, request_complete, req);
return REQ_PENDING;
}
static void send_frame_complete(void *data)
{
hwa742.int_ctrl->enable_plane(OMAPFB_PLANE_GFX, 0);
}
#define ADD_PREQ(_x, _y, _w, _h) do { \
req = alloc_req(); \
req->handler = send_frame_handler; \
req->complete = send_frame_complete; \
req->par.update.x = _x; \
req->par.update.y = _y; \
req->par.update.width = _w; \
req->par.update.height = _h; \
req->par.update.color_mode = color_mode;\
req->par.update.flags = flags; \
list_add_tail(&req->entry, req_head); \
} while(0)
static void create_req_list(struct omapfb_update_window *win,
struct list_head *req_head)
{
struct hwa742_request *req;
int x = win->x;
int y = win->y;
int width = win->width;
int height = win->height;
int color_mode;
int flags;
flags = win->format & ~OMAPFB_FORMAT_MASK;
color_mode = win->format & OMAPFB_FORMAT_MASK;
if (x & 1) {
ADD_PREQ(x, y, 1, height);
width--;
x++;
flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC;
}
if (width & ~1) {
unsigned int xspan = width & ~1;
unsigned int ystart = y;
unsigned int yspan = height;
if (xspan * height * 2 > hwa742.max_transmit_size) {
yspan = hwa742.max_transmit_size / (xspan * 2);
ADD_PREQ(x, ystart, xspan, yspan);
ystart += yspan;
yspan = height - yspan;
flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC;
}
ADD_PREQ(x, ystart, xspan, yspan);
x += xspan;
width -= xspan;
flags &= ~OMAPFB_FORMAT_FLAG_TEARSYNC;
}
if (width)
ADD_PREQ(x, y, 1, height);
}
static void auto_update_complete(void *data)
{
if (!hwa742.stop_auto_update)
mod_timer(&hwa742.auto_update_timer,
jiffies + HWA742_AUTO_UPDATE_TIME);
}
static void hwa742_update_window_auto(unsigned long arg)
{
LIST_HEAD(req_list);
struct hwa742_request *last;
create_req_list(&hwa742.auto_update_window, &req_list);
last = list_entry(req_list.prev, struct hwa742_request, entry);
last->complete = auto_update_complete;
last->complete_data = NULL;
submit_req_list(&req_list);
}
int hwa742_update_window_async(struct fb_info *fbi,
struct omapfb_update_window *win,
void (*complete_callback)(void *arg),
void *complete_callback_data)
{
LIST_HEAD(req_list);
struct hwa742_request *last;
int r = 0;
if (hwa742.update_mode != OMAPFB_MANUAL_UPDATE) {
dev_dbg(hwa742.fbdev->dev, "invalid update mode\n");
r = -EINVAL;
goto out;
}
if (unlikely(win->format &
~(0x03 | OMAPFB_FORMAT_FLAG_DOUBLE |
OMAPFB_FORMAT_FLAG_TEARSYNC | OMAPFB_FORMAT_FLAG_FORCE_VSYNC))) {
dev_dbg(hwa742.fbdev->dev, "invalid window flag");
r = -EINVAL;
goto out;
}
create_req_list(win, &req_list);
last = list_entry(req_list.prev, struct hwa742_request, entry);
last->complete = complete_callback;
last->complete_data = (void *)complete_callback_data;
submit_req_list(&req_list);
out:
return r;
}
EXPORT_SYMBOL(hwa742_update_window_async);
static int hwa742_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)
{
if (plane != OMAPFB_PLANE_GFX ||
channel_out != OMAPFB_CHANNEL_OUT_LCD)
return -EINVAL;
return 0;
}
static int hwa742_enable_plane(int plane, int enable)
{
if (plane != 0)
return -EINVAL;
hwa742.int_ctrl->enable_plane(plane, enable);
return 0;
}
static int sync_handler(struct hwa742_request *req)
{
complete(req->par.sync);
return REQ_COMPLETE;
}
static void hwa742_sync(void)
{
LIST_HEAD(req_list);
struct hwa742_request *req;
struct completion comp;
req = alloc_req();
req->handler = sync_handler;
req->complete = NULL;
init_completion(&comp);
req->par.sync = &comp;
list_add(&req->entry, &req_list);
submit_req_list(&req_list);
wait_for_completion(&comp);
}
static void hwa742_bind_client(struct omapfb_notifier_block *nb)
{
dev_dbg(hwa742.fbdev->dev, "update_mode %d\n", hwa742.update_mode);
if (hwa742.update_mode == OMAPFB_MANUAL_UPDATE) {
omapfb_notify_clients(hwa742.fbdev, OMAPFB_EVENT_READY);
}
}
static int hwa742_set_update_mode(enum omapfb_update_mode mode)
{
if (mode != OMAPFB_MANUAL_UPDATE && mode != OMAPFB_AUTO_UPDATE &&
mode != OMAPFB_UPDATE_DISABLED)
return -EINVAL;
if (mode == hwa742.update_mode)
return 0;
dev_info(hwa742.fbdev->dev, "HWA742: setting update mode to %s\n",
mode == OMAPFB_UPDATE_DISABLED ? "disabled" :
(mode == OMAPFB_AUTO_UPDATE ? "auto" : "manual"));
switch (hwa742.update_mode) {
case OMAPFB_MANUAL_UPDATE:
omapfb_notify_clients(hwa742.fbdev, OMAPFB_EVENT_DISABLED);
break;
case OMAPFB_AUTO_UPDATE:
hwa742.stop_auto_update = 1;
del_timer_sync(&hwa742.auto_update_timer);
break;
case OMAPFB_UPDATE_DISABLED:
break;
}
hwa742.update_mode = mode;
hwa742_sync();
hwa742.stop_auto_update = 0;
switch (mode) {
case OMAPFB_MANUAL_UPDATE:
omapfb_notify_clients(hwa742.fbdev, OMAPFB_EVENT_READY);
break;
case OMAPFB_AUTO_UPDATE:
hwa742_update_window_auto(0);
break;
case OMAPFB_UPDATE_DISABLED:
break;
}
return 0;
}
static enum omapfb_update_mode hwa742_get_update_mode(void)
{
return hwa742.update_mode;
}
static unsigned long round_to_extif_ticks(unsigned long ps, int div)
{
int bus_tick = hwa742.extif_clk_period * div;
return (ps + bus_tick - 1) / bus_tick * bus_tick;
}
static int calc_reg_timing(unsigned long sysclk, int div)
{
struct extif_timings *t;
unsigned long systim;
/* CSOnTime 0, WEOnTime 2 ns, REOnTime 2 ns,
* AccessTime 2 ns + 12.2 ns (regs),
* WEOffTime = WEOnTime + 1 ns,
* REOffTime = REOnTime + 16 ns (regs),
* CSOffTime = REOffTime + 1 ns
* ReadCycle = 2ns + 2*SYSCLK (regs),
* WriteCycle = 2*SYSCLK + 2 ns,
* CSPulseWidth = 10 ns */
systim = 1000000000 / (sysclk / 1000);
dev_dbg(hwa742.fbdev->dev, "HWA742 systim %lu ps extif_clk_period %u ps"
"extif_clk_div %d\n", systim, hwa742.extif_clk_period, div);
t = &hwa742.reg_timings;
memset(t, 0, sizeof(*t));
t->clk_div = div;
t->cs_on_time = 0;
t->we_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->re_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->access_time = round_to_extif_ticks(t->re_on_time + 12200, div);
t->we_off_time = round_to_extif_ticks(t->we_on_time + 1000, div);
t->re_off_time = round_to_extif_ticks(t->re_on_time + 16000, div);
t->cs_off_time = round_to_extif_ticks(t->re_off_time + 1000, div);
t->we_cycle_time = round_to_extif_ticks(2 * systim + 2000, div);
if (t->we_cycle_time < t->we_off_time)
t->we_cycle_time = t->we_off_time;
t->re_cycle_time = round_to_extif_ticks(2 * systim + 2000, div);
if (t->re_cycle_time < t->re_off_time)
t->re_cycle_time = t->re_off_time;
t->cs_pulse_width = 0;
dev_dbg(hwa742.fbdev->dev, "[reg]cson %d csoff %d reon %d reoff %d\n",
t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time);
dev_dbg(hwa742.fbdev->dev, "[reg]weon %d weoff %d recyc %d wecyc %d\n",
t->we_on_time, t->we_off_time, t->re_cycle_time,
t->we_cycle_time);
dev_dbg(hwa742.fbdev->dev, "[reg]rdaccess %d cspulse %d\n",
t->access_time, t->cs_pulse_width);
return hwa742.extif->convert_timings(t);
}
static int calc_lut_timing(unsigned long sysclk, int div)
{
struct extif_timings *t;
unsigned long systim;
/* CSOnTime 0, WEOnTime 2 ns, REOnTime 2 ns,
* AccessTime 2 ns + 4 * SYSCLK + 26 (lut),
* WEOffTime = WEOnTime + 1 ns,
* REOffTime = REOnTime + 4*SYSCLK + 26 ns (lut),
* CSOffTime = REOffTime + 1 ns
* ReadCycle = 2ns + 4*SYSCLK + 26 ns (lut),
* WriteCycle = 2*SYSCLK + 2 ns,
* CSPulseWidth = 10 ns
*/
systim = 1000000000 / (sysclk / 1000);
dev_dbg(hwa742.fbdev->dev, "HWA742 systim %lu ps extif_clk_period %u ps"
"extif_clk_div %d\n", systim, hwa742.extif_clk_period, div);
t = &hwa742.lut_timings;
memset(t, 0, sizeof(*t));
t->clk_div = div;
t->cs_on_time = 0;
t->we_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->re_on_time = round_to_extif_ticks(t->cs_on_time + 2000, div);
t->access_time = round_to_extif_ticks(t->re_on_time + 4 * systim +
26000, div);
t->we_off_time = round_to_extif_ticks(t->we_on_time + 1000, div);
t->re_off_time = round_to_extif_ticks(t->re_on_time + 4 * systim +
26000, div);
t->cs_off_time = round_to_extif_ticks(t->re_off_time + 1000, div);
t->we_cycle_time = round_to_extif_ticks(2 * systim + 2000, div);
if (t->we_cycle_time < t->we_off_time)
t->we_cycle_time = t->we_off_time;
t->re_cycle_time = round_to_extif_ticks(2000 + 4 * systim + 26000, div);
if (t->re_cycle_time < t->re_off_time)
t->re_cycle_time = t->re_off_time;
t->cs_pulse_width = 0;
dev_dbg(hwa742.fbdev->dev, "[lut]cson %d csoff %d reon %d reoff %d\n",
t->cs_on_time, t->cs_off_time, t->re_on_time, t->re_off_time);
dev_dbg(hwa742.fbdev->dev, "[lut]weon %d weoff %d recyc %d wecyc %d\n",
t->we_on_time, t->we_off_time, t->re_cycle_time,
t->we_cycle_time);
dev_dbg(hwa742.fbdev->dev, "[lut]rdaccess %d cspulse %d\n",
t->access_time, t->cs_pulse_width);
return hwa742.extif->convert_timings(t);
}
static int calc_extif_timings(unsigned long sysclk, int *extif_mem_div)
{
int max_clk_div;
int div;
hwa742.extif->get_clk_info(&hwa742.extif_clk_period, &max_clk_div);
for (div = 1; div < max_clk_div; div++) {
if (calc_reg_timing(sysclk, div) == 0)
break;
}
if (div > max_clk_div)
goto err;
*extif_mem_div = div;
for (div = 1; div < max_clk_div; div++) {
if (calc_lut_timing(sysclk, div) == 0)
break;
}
if (div > max_clk_div)
goto err;
return 0;
err:
dev_err(hwa742.fbdev->dev, "can't setup timings\n");
return -1;
}
static void calc_hwa742_clk_rates(unsigned long ext_clk,
unsigned long *sys_clk, unsigned long *pix_clk)
{
int pix_clk_src;
int sys_div = 0, sys_mul = 0;
int pix_div;
pix_clk_src = hwa742_read_reg(HWA742_CLK_SRC_REG);
pix_div = ((pix_clk_src >> 3) & 0x1f) + 1;
if ((pix_clk_src & (0x3 << 1)) == 0) {
/* Source is the PLL */
sys_div = (hwa742_read_reg(HWA742_PLL_DIV_REG) & 0x3f) + 1;
sys_mul = (hwa742_read_reg(HWA742_PLL_4_REG) & 0x7f) + 1;
*sys_clk = ext_clk * sys_mul / sys_div;
} else /* else source is ext clk, or oscillator */
*sys_clk = ext_clk;
*pix_clk = *sys_clk / pix_div; /* HZ */
dev_dbg(hwa742.fbdev->dev,
"ext_clk %ld pix_src %d pix_div %d sys_div %d sys_mul %d\n",
ext_clk, pix_clk_src & (0x3 << 1), pix_div, sys_div, sys_mul);
dev_dbg(hwa742.fbdev->dev, "sys_clk %ld pix_clk %ld\n",
*sys_clk, *pix_clk);
}
static int setup_tearsync(unsigned long pix_clk, int extif_div)
{
int hdisp, vdisp;
int hndp, vndp;
int hsw, vsw;
int hs, vs;
int hs_pol_inv, vs_pol_inv;
int use_hsvs, use_ndp;
u8 b;
hsw = hwa742_read_reg(HWA742_HS_W_REG);
vsw = hwa742_read_reg(HWA742_VS_W_REG);
hs_pol_inv = !(hsw & 0x80);
vs_pol_inv = !(vsw & 0x80);
hsw = hsw & 0x7f;
vsw = vsw & 0x3f;
hdisp = (hwa742_read_reg(HWA742_H_DISP_REG) & 0x7f) * 8;
vdisp = hwa742_read_reg(HWA742_V_DISP_1_REG) +
((hwa742_read_reg(HWA742_V_DISP_2_REG) & 0x3) << 8);
hndp = hwa742_read_reg(HWA742_H_NDP_REG) & 0x7f;
vndp = hwa742_read_reg(HWA742_V_NDP_REG);
/* time to transfer one pixel (16bpp) in ps */
hwa742.pix_tx_time = hwa742.reg_timings.we_cycle_time;
if (hwa742.extif->get_max_tx_rate != NULL) {
/* The external interface might have a rate limitation,
* if so, we have to maximize our transfer rate.
*/
unsigned long min_tx_time;
unsigned long max_tx_rate = hwa742.extif->get_max_tx_rate();
dev_dbg(hwa742.fbdev->dev, "max_tx_rate %ld HZ\n",
max_tx_rate);
min_tx_time = 1000000000 / (max_tx_rate / 1000); /* ps */
if (hwa742.pix_tx_time < min_tx_time)
hwa742.pix_tx_time = min_tx_time;
}
/* time to update one line in ps */
hwa742.line_upd_time = (hdisp + hndp) * 1000000 / (pix_clk / 1000);
hwa742.line_upd_time *= 1000;
if (hdisp * hwa742.pix_tx_time > hwa742.line_upd_time)
/* transfer speed too low, we might have to use both
* HS and VS */
use_hsvs = 1;
else
/* decent transfer speed, we'll always use only VS */
use_hsvs = 0;
if (use_hsvs && (hs_pol_inv || vs_pol_inv)) {
/* HS or'ed with VS doesn't work, use the active high
* TE signal based on HNDP / VNDP */
use_ndp = 1;
hs_pol_inv = 0;
vs_pol_inv = 0;
hs = hndp;
vs = vndp;
} else {
/* Use HS or'ed with VS as a TE signal if both are needed
* or VNDP if only vsync is needed. */
use_ndp = 0;
hs = hsw;
vs = vsw;
if (!use_hsvs) {
hs_pol_inv = 0;
vs_pol_inv = 0;
}
}
hs = hs * 1000000 / (pix_clk / 1000); /* ps */
hs *= 1000;
vs = vs * (hdisp + hndp) * 1000000 / (pix_clk / 1000); /* ps */
vs *= 1000;
if (vs <= hs)
return -EDOM;
/* set VS to 120% of HS to minimize VS detection time */
vs = hs * 12 / 10;
/* minimize HS too */
hs = 10000;
b = hwa742_read_reg(HWA742_NDP_CTRL);
b &= ~0x3;
b |= use_hsvs ? 1 : 0;
b |= (use_ndp && use_hsvs) ? 0 : 2;
hwa742_write_reg(HWA742_NDP_CTRL, b);
hwa742.vsync_only = !use_hsvs;
dev_dbg(hwa742.fbdev->dev,
"pix_clk %ld HZ pix_tx_time %ld ps line_upd_time %ld ps\n",
pix_clk, hwa742.pix_tx_time, hwa742.line_upd_time);
dev_dbg(hwa742.fbdev->dev,
"hs %d ps vs %d ps mode %d vsync_only %d\n",
hs, vs, (b & 0x3), !use_hsvs);
return hwa742.extif->setup_tearsync(1, hs, vs,
hs_pol_inv, vs_pol_inv, extif_div);
}
static void hwa742_get_caps(int plane, struct omapfb_caps *caps)
{
hwa742.int_ctrl->get_caps(plane, caps);
caps->ctrl |= OMAPFB_CAPS_MANUAL_UPDATE |
OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE;
if (hwa742.te_connected)
caps->ctrl |= OMAPFB_CAPS_TEARSYNC;
caps->wnd_color |= (1 << OMAPFB_COLOR_RGB565) |
(1 << OMAPFB_COLOR_YUV420);
}
static void hwa742_suspend(void)
{
hwa742.update_mode_before_suspend = hwa742.update_mode;
hwa742_set_update_mode(OMAPFB_UPDATE_DISABLED);
/* Enable sleep mode */
hwa742_write_reg(HWA742_POWER_SAVE, 1 << 1);
if (hwa742.power_down != NULL)
hwa742.power_down(hwa742.fbdev->dev);
}
static void hwa742_resume(void)
{
if (hwa742.power_up != NULL)
hwa742.power_up(hwa742.fbdev->dev);
/* Disable sleep mode */
hwa742_write_reg(HWA742_POWER_SAVE, 0);
while (1) {
/* Loop until PLL output is stabilized */
if (hwa742_read_reg(HWA742_PLL_DIV_REG) & (1 << 7))
break;
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(msecs_to_jiffies(5));
}
hwa742_set_update_mode(hwa742.update_mode_before_suspend);
}
static int hwa742_init(struct omapfb_device *fbdev, int ext_mode,
struct omapfb_mem_desc *req_vram)
{
int r = 0, i;
u8 rev, conf;
unsigned long ext_clk;
unsigned long sys_clk, pix_clk;
int extif_mem_div;
struct omapfb_platform_data *omapfb_conf;
struct hwa742_platform_data *ctrl_conf;
BUG_ON(!fbdev->ext_if || !fbdev->int_ctrl);
hwa742.fbdev = fbdev;
hwa742.extif = fbdev->ext_if;
hwa742.int_ctrl = fbdev->int_ctrl;
omapfb_conf = fbdev->dev->platform_data;
ctrl_conf = omapfb_conf->ctrl_platform_data;
if (ctrl_conf == NULL || ctrl_conf->get_clock_rate == NULL) {
dev_err(fbdev->dev, "HWA742: missing platform data\n");
r = -ENOENT;
goto err1;
}
hwa742.power_down = ctrl_conf->power_down;
hwa742.power_up = ctrl_conf->power_up;
spin_lock_init(&hwa742.req_lock);
if ((r = hwa742.int_ctrl->init(fbdev, 1, req_vram)) < 0)
goto err1;
if ((r = hwa742.extif->init(fbdev)) < 0)
goto err2;
ext_clk = ctrl_conf->get_clock_rate(fbdev->dev);
if ((r = calc_extif_timings(ext_clk, &extif_mem_div)) < 0)
goto err3;
hwa742.extif->set_timings(&hwa742.reg_timings);
if (hwa742.power_up != NULL)
hwa742.power_up(fbdev->dev);
calc_hwa742_clk_rates(ext_clk, &sys_clk, &pix_clk);
if ((r = calc_extif_timings(sys_clk, &extif_mem_div)) < 0)
goto err4;
hwa742.extif->set_timings(&hwa742.reg_timings);
rev = hwa742_read_reg(HWA742_REV_CODE_REG);
if ((rev & 0xfc) != 0x80) {
dev_err(fbdev->dev, "HWA742: invalid revision %02x\n", rev);
r = -ENODEV;
goto err4;
}
if (!(hwa742_read_reg(HWA742_PLL_DIV_REG) & 0x80)) {
dev_err(fbdev->dev,
"HWA742: controller not initialized by the bootloader\n");
r = -ENODEV;
goto err4;
}
if (ctrl_conf->te_connected) {
if ((r = setup_tearsync(pix_clk, extif_mem_div)) < 0) {
dev_err(hwa742.fbdev->dev,
"HWA742: can't setup tearing synchronization\n");
goto err4;
}
hwa742.te_connected = 1;
}
hwa742.max_transmit_size = hwa742.extif->max_transmit_size;
hwa742.update_mode = OMAPFB_UPDATE_DISABLED;
hwa742.auto_update_window.x = 0;
hwa742.auto_update_window.y = 0;
hwa742.auto_update_window.width = fbdev->panel->x_res;
hwa742.auto_update_window.height = fbdev->panel->y_res;
hwa742.auto_update_window.format = 0;
init_timer(&hwa742.auto_update_timer);
hwa742.auto_update_timer.function = hwa742_update_window_auto;
hwa742.auto_update_timer.data = 0;
hwa742.prev_color_mode = -1;
hwa742.prev_flags = 0;
hwa742.fbdev = fbdev;
INIT_LIST_HEAD(&hwa742.free_req_list);
INIT_LIST_HEAD(&hwa742.pending_req_list);
for (i = 0; i < ARRAY_SIZE(hwa742.req_pool); i++)
list_add(&hwa742.req_pool[i].entry, &hwa742.free_req_list);
BUG_ON(i <= IRQ_REQ_POOL_SIZE);
sema_init(&hwa742.req_sema, i - IRQ_REQ_POOL_SIZE);
conf = hwa742_read_reg(HWA742_CONFIG_REG);
dev_info(fbdev->dev, ": Epson HWA742 LCD controller rev %d "
"initialized (CNF pins %x)\n", rev & 0x03, conf & 0x07);
return 0;
err4:
if (hwa742.power_down != NULL)
hwa742.power_down(fbdev->dev);
err3:
hwa742.extif->cleanup();
err2:
hwa742.int_ctrl->cleanup();
err1:
return r;
}
static void hwa742_cleanup(void)
{
hwa742_set_update_mode(OMAPFB_UPDATE_DISABLED);
hwa742.extif->cleanup();
hwa742.int_ctrl->cleanup();
if (hwa742.power_down != NULL)
hwa742.power_down(hwa742.fbdev->dev);
}
struct lcd_ctrl hwa742_ctrl = {
.name = "hwa742",
.init = hwa742_init,
.cleanup = hwa742_cleanup,
.bind_client = hwa742_bind_client,
.get_caps = hwa742_get_caps,
.set_update_mode = hwa742_set_update_mode,
.get_update_mode = hwa742_get_update_mode,
.setup_plane = hwa742_setup_plane,
.enable_plane = hwa742_enable_plane,
.update_window = hwa742_update_window_async,
.sync = hwa742_sync,
.suspend = hwa742_suspend,
.resume = hwa742_resume,
};
/*
* File: drivers/video/omap/lcd_ams_delta.c
*
* Based on drivers/video/omap/lcd_inn1510.c
*
* LCD panel support for the Amstrad E3 (Delta) videophone.
*
* Copyright (C) 2006 Jonathan McDowell <noodles@earth.li>
*
* 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/platform_device.h>
#include <asm/delay.h>
#include <asm/io.h>
#include <asm/arch/board-ams-delta.h>
#include <asm/arch/hardware.h>
#include <asm/arch/omapfb.h>
#define AMS_DELTA_DEFAULT_CONTRAST 112
static int ams_delta_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
return 0;
}
static void ams_delta_panel_cleanup(struct lcd_panel *panel)
{
}
static int ams_delta_panel_enable(struct lcd_panel *panel)
{
ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP,
AMS_DELTA_LATCH2_LCD_NDISP);
ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN,
AMS_DELTA_LATCH2_LCD_VBLEN);
omap_writeb(1, OMAP_PWL_CLK_ENABLE);
omap_writeb(AMS_DELTA_DEFAULT_CONTRAST, OMAP_PWL_ENABLE);
return 0;
}
static void ams_delta_panel_disable(struct lcd_panel *panel)
{
ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, 0);
ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, 0);
}
static unsigned long ams_delta_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
static struct lcd_panel ams_delta_panel = {
.name = "ams-delta",
.config = 0,
.bpp = 12,
.data_lines = 16,
.x_res = 480,
.y_res = 320,
.pixel_clock = 4687,
.hsw = 3,
.hfp = 1,
.hbp = 1,
.vsw = 1,
.vfp = 0,
.vbp = 0,
.pcd = 0,
.acb = 37,
.init = ams_delta_panel_init,
.cleanup = ams_delta_panel_cleanup,
.enable = ams_delta_panel_enable,
.disable = ams_delta_panel_disable,
.get_caps = ams_delta_panel_get_caps,
};
static int ams_delta_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&ams_delta_panel);
return 0;
}
static int ams_delta_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int ams_delta_panel_suspend(struct platform_device *pdev,
pm_message_t mesg)
{
return 0;
}
static int ams_delta_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver ams_delta_panel_driver = {
.probe = ams_delta_panel_probe,
.remove = ams_delta_panel_remove,
.suspend = ams_delta_panel_suspend,
.resume = ams_delta_panel_resume,
.driver = {
.name = "lcd_ams_delta",
.owner = THIS_MODULE,
},
};
static int ams_delta_panel_drv_init(void)
{
return platform_driver_register(&ams_delta_panel_driver);
}
static void ams_delta_panel_drv_cleanup(void)
{
platform_driver_unregister(&ams_delta_panel_driver);
}
module_init(ams_delta_panel_drv_init);
module_exit(ams_delta_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd_apollon.c
*
* LCD panel support for the Samsung OMAP2 Apollon board
*
* Copyright (C) 2005,2006 Samsung Electronics
* Author: Kyungmin Park <kyungmin.park@samsung.com>
*
* Derived from drivers/video/omap/lcd-h4.c
*
* 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/platform_device.h>
#include <asm/arch/gpio.h>
#include <asm/arch/mux.h>
#include <asm/arch/omapfb.h>
/* #define USE_35INCH_LCD 1 */
static int apollon_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
/* configure LCD PWR_EN */
omap_cfg_reg(M21_242X_GPIO11);
return 0;
}
static void apollon_panel_cleanup(struct lcd_panel *panel)
{
}
static int apollon_panel_enable(struct lcd_panel *panel)
{
return 0;
}
static void apollon_panel_disable(struct lcd_panel *panel)
{
}
static unsigned long apollon_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel apollon_panel = {
.name = "apollon",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
OMAP_LCDC_INV_HSYNC,
.bpp = 16,
.data_lines = 18,
#ifdef USE_35INCH_LCD
.x_res = 240,
.y_res = 320,
.hsw = 2,
.hfp = 3,
.hbp = 9,
.vsw = 4,
.vfp = 3,
.vbp = 5,
#else
.x_res = 480,
.y_res = 272,
.hsw = 41,
.hfp = 2,
.hbp = 2,
.vsw = 10,
.vfp = 2,
.vbp = 2,
#endif
.pixel_clock = 6250,
.init = apollon_panel_init,
.cleanup = apollon_panel_cleanup,
.enable = apollon_panel_enable,
.disable = apollon_panel_disable,
.get_caps = apollon_panel_get_caps,
};
static int apollon_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&apollon_panel);
return 0;
}
static int apollon_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int apollon_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int apollon_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver apollon_panel_driver = {
.probe = apollon_panel_probe,
.remove = apollon_panel_remove,
.suspend = apollon_panel_suspend,
.resume = apollon_panel_resume,
.driver = {
.name = "apollon_lcd",
.owner = THIS_MODULE,
},
};
static int __init apollon_panel_drv_init(void)
{
return platform_driver_register(&apollon_panel_driver);
}
static void __exit apollon_panel_drv_exit(void)
{
platform_driver_unregister(&apollon_panel_driver);
}
module_init(apollon_panel_drv_init);
module_exit(apollon_panel_drv_exit);
/*
* File: drivers/video/omap/lcd-h2.c
*
* LCD panel support for the TI OMAP H2 board
*
* 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/platform_device.h>
#include <linux/spi/tsc2101.h>
#include <asm/arch/mux.h>
#include <asm/arch/omapfb.h>
static struct {
struct platform_device *lcd_dev;
struct spi_device *tsc2101_dev;
} h2_panel_dev;
static int h2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
{
return 0;
}
static void h2_panel_cleanup(struct lcd_panel *panel)
{
}
static int h2_panel_enable(struct lcd_panel *panel)
{
int r;
/* Assert LCD_EN, BKLIGHT_EN pins on LCD panel
* page2, GPIO config reg, GPIO(0,1) to out and asserted
*/
r = tsc2101_write_sync(h2_panel_dev.tsc2101_dev, 2, 0x23, 0xcc00);
if (r < 0)
dev_err(&h2_panel_dev.lcd_dev->dev,
"failed to enable LCD panel\n");
return r;
}
static void h2_panel_disable(struct lcd_panel *panel)
{
/* Deassert LCD_EN and BKLIGHT_EN pins on LCD panel
* page2, GPIO config reg, GPIO(0,1) to out and deasserted
*/
if (tsc2101_write_sync(h2_panel_dev.tsc2101_dev, 2, 0x23, 0x8800))
dev_err(&h2_panel_dev.lcd_dev->dev,
"failed to disable LCD panel\n");
}
static unsigned long h2_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel h2_panel = {
.name = "h2",
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.data_lines = 16,
.x_res = 240,
.y_res = 320,
.pixel_clock = 5000,
.hsw = 12,
.hfp = 12,
.hbp = 46,
.vsw = 1,
.vfp = 1,
.vbp = 0,
.init = h2_panel_init,
.cleanup = h2_panel_cleanup,
.enable = h2_panel_enable,
.disable = h2_panel_disable,
.get_caps = h2_panel_get_caps,
};
static int h2_panel_probe(struct platform_device *pdev)
{
struct spi_device *tsc2101;
tsc2101 = pdev->dev.platform_data;
if (tsc2101 == NULL) {
dev_err(&pdev->dev, "no platform data\n");
return -ENODEV;
}
if (strncmp(tsc2101->modalias, "tsc2101", 8) != 0) {
dev_err(&pdev->dev, "tsc2101 not found\n");
return -EINVAL;
}
h2_panel_dev.lcd_dev = pdev;
h2_panel_dev.tsc2101_dev = tsc2101;
omapfb_register_panel(&h2_panel);
return 0;
}
static int h2_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int h2_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int h2_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver h2_panel_driver = {
.probe = h2_panel_probe,
.remove = h2_panel_remove,
.suspend = h2_panel_suspend,
.resume = h2_panel_resume,
.driver = {
.name = "lcd_h2",
.owner = THIS_MODULE,
},
};
static int h2_panel_drv_init(void)
{
return platform_driver_register(&h2_panel_driver);
}
static void h2_panel_drv_cleanup(void)
{
platform_driver_unregister(&h2_panel_driver);
}
module_init(h2_panel_drv_init);
module_exit(h2_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd-h3.c
*
* LCD panel support for the TI OMAP H3 board
*
* 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/platform_device.h>
#include <asm/arch/gpio.h>
#include <asm/arch/tps65010.h>
#include <asm/arch/omapfb.h>
#define MODULE_NAME "omapfb-lcd_h3"
#define pr_err(fmt, args...) printk(KERN_ERR MODULE_NAME ": " fmt, ## args)
static int h3_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
{
return 0;
}
static void h3_panel_cleanup(struct lcd_panel *panel)
{
}
static int h3_panel_enable(struct lcd_panel *panel)
{
int r = 0;
/* GPIO1 and GPIO2 of TPS65010 send LCD_ENBKL and LCD_ENVDD signals */
r = tps65010_set_gpio_out_value(GPIO1, HIGH);
if (!r)
r = tps65010_set_gpio_out_value(GPIO2, HIGH);
if (r)
pr_err("Unable to turn on LCD panel\n");
return r;
}
static void h3_panel_disable(struct lcd_panel *panel)
{
int r = 0;
/* GPIO1 and GPIO2 of TPS65010 send LCD_ENBKL and LCD_ENVDD signals */
r = tps65010_set_gpio_out_value(GPIO1, LOW);
if (!r)
tps65010_set_gpio_out_value(GPIO2, LOW);
if (r)
pr_err("Unable to turn off LCD panel\n");
}
static unsigned long h3_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel h3_panel = {
.name = "h3",
.config = OMAP_LCDC_PANEL_TFT,
.data_lines = 16,
.bpp = 16,
.x_res = 240,
.y_res = 320,
.pixel_clock = 12000,
.hsw = 12,
.hfp = 14,
.hbp = 72 - 12,
.vsw = 1,
.vfp = 1,
.vbp = 0,
.pcd = 0,
.init = h3_panel_init,
.cleanup = h3_panel_cleanup,
.enable = h3_panel_enable,
.disable = h3_panel_disable,
.get_caps = h3_panel_get_caps,
};
static int h3_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&h3_panel);
return 0;
}
static int h3_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int h3_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int h3_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver h3_panel_driver = {
.probe = h3_panel_probe,
.remove = h3_panel_remove,
.suspend = h3_panel_suspend,
.resume = h3_panel_resume,
.driver = {
.name = "lcd_h3",
.owner = THIS_MODULE,
},
};
static int h3_panel_drv_init(void)
{
return platform_driver_register(&h3_panel_driver);
}
static void h3_panel_drv_cleanup(void)
{
platform_driver_unregister(&h3_panel_driver);
}
module_init(h3_panel_drv_init);
module_exit(h3_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd-h4.c
*
* LCD panel support for the TI OMAP H4 board
*
* 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/platform_device.h>
#include <asm/arch/omapfb.h>
static int h4_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
{
return 0;
}
static void h4_panel_cleanup(struct lcd_panel *panel)
{
}
static int h4_panel_enable(struct lcd_panel *panel)
{
return 0;
}
static void h4_panel_disable(struct lcd_panel *panel)
{
}
static unsigned long h4_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel h4_panel = {
.name = "h4",
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.data_lines = 16,
.x_res = 240,
.y_res = 320,
.pixel_clock = 6250,
.hsw = 15,
.hfp = 15,
.hbp = 60,
.vsw = 1,
.vfp = 1,
.vbp = 1,
.init = h4_panel_init,
.cleanup = h4_panel_cleanup,
.enable = h4_panel_enable,
.disable = h4_panel_disable,
.get_caps = h4_panel_get_caps,
};
static int h4_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&h4_panel);
return 0;
}
static int h4_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int h4_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int h4_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver h4_panel_driver = {
.probe = h4_panel_probe,
.remove = h4_panel_remove,
.suspend = h4_panel_suspend,
.resume = h4_panel_resume,
.driver = {
.name = "lcd_h4",
.owner = THIS_MODULE,
},
};
static int h4_panel_drv_init(void)
{
return platform_driver_register(&h4_panel_driver);
}
static void h4_panel_drv_cleanup(void)
{
platform_driver_unregister(&h4_panel_driver);
}
module_init(h4_panel_drv_init);
module_exit(h4_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd-inn1510.c
*
* LCD panel support for the TI OMAP1510 Innovator board
*
* 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/platform_device.h>
#include <asm/io.h>
#include <asm/arch/fpga.h>
#include <asm/arch/omapfb.h>
static int innovator1510_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
return 0;
}
static void innovator1510_panel_cleanup(struct lcd_panel *panel)
{
}
static int innovator1510_panel_enable(struct lcd_panel *panel)
{
fpga_write(0x7, OMAP1510_FPGA_LCD_PANEL_CONTROL);
return 0;
}
static void innovator1510_panel_disable(struct lcd_panel *panel)
{
fpga_write(0x0, OMAP1510_FPGA_LCD_PANEL_CONTROL);
}
static unsigned long innovator1510_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel innovator1510_panel = {
.name = "inn1510",
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.data_lines = 16,
.x_res = 240,
.y_res = 320,
.pixel_clock = 12500,
.hsw = 40,
.hfp = 40,
.hbp = 72,
.vsw = 1,
.vfp = 1,
.vbp = 0,
.pcd = 12,
.init = innovator1510_panel_init,
.cleanup = innovator1510_panel_cleanup,
.enable = innovator1510_panel_enable,
.disable = innovator1510_panel_disable,
.get_caps = innovator1510_panel_get_caps,
};
static int innovator1510_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&innovator1510_panel);
return 0;
}
static int innovator1510_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int innovator1510_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int innovator1510_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver innovator1510_panel_driver = {
.probe = innovator1510_panel_probe,
.remove = innovator1510_panel_remove,
.suspend = innovator1510_panel_suspend,
.resume = innovator1510_panel_resume,
.driver = {
.name = "lcd_inn1510",
.owner = THIS_MODULE,
},
};
static int innovator1510_panel_drv_init(void)
{
return platform_driver_register(&innovator1510_panel_driver);
}
static void innovator1510_panel_drv_cleanup(void)
{
platform_driver_unregister(&innovator1510_panel_driver);
}
module_init(innovator1510_panel_drv_init);
module_exit(innovator1510_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd-inn1610.c
*
* LCD panel support for the TI OMAP1610 Innovator board
*
* 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/platform_device.h>
#include <asm/arch/gpio.h>
#include <asm/arch/omapfb.h>
#define MODULE_NAME "omapfb-lcd_h3"
#define pr_err(fmt, args...) printk(KERN_ERR MODULE_NAME ": " fmt, ## args)
static int innovator1610_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
int r = 0;
if (omap_request_gpio(14)) {
pr_err("can't request GPIO 14\n");
r = -1;
goto exit;
}
if (omap_request_gpio(15)) {
pr_err("can't request GPIO 15\n");
omap_free_gpio(14);
r = -1;
goto exit;
}
/* configure GPIO(14, 15) as outputs */
omap_set_gpio_direction(14, 0);
omap_set_gpio_direction(15, 0);
exit:
return r;
}
static void innovator1610_panel_cleanup(struct lcd_panel *panel)
{
omap_free_gpio(15);
omap_free_gpio(14);
}
static int innovator1610_panel_enable(struct lcd_panel *panel)
{
/* set GPIO14 and GPIO15 high */
omap_set_gpio_dataout(14, 1);
omap_set_gpio_dataout(15, 1);
return 0;
}
static void innovator1610_panel_disable(struct lcd_panel *panel)
{
/* set GPIO13, GPIO14 and GPIO15 low */
omap_set_gpio_dataout(14, 0);
omap_set_gpio_dataout(15, 0);
}
static unsigned long innovator1610_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel innovator1610_panel = {
.name = "inn1610",
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.data_lines = 16,
.x_res = 320,
.y_res = 240,
.pixel_clock = 12500,
.hsw = 40,
.hfp = 40,
.hbp = 72,
.vsw = 1,
.vfp = 1,
.vbp = 0,
.pcd = 12,
.init = innovator1610_panel_init,
.cleanup = innovator1610_panel_cleanup,
.enable = innovator1610_panel_enable,
.disable = innovator1610_panel_disable,
.get_caps = innovator1610_panel_get_caps,
};
static int innovator1610_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&innovator1610_panel);
return 0;
}
static int innovator1610_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int innovator1610_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int innovator1610_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver innovator1610_panel_driver = {
.probe = innovator1610_panel_probe,
.remove = innovator1610_panel_remove,
.suspend = innovator1610_panel_suspend,
.resume = innovator1610_panel_resume,
.driver = {
.name = "lcd_inn1610",
.owner = THIS_MODULE,
},
};
static int innovator1610_panel_drv_init(void)
{
return platform_driver_register(&innovator1610_panel_driver);
}
static void innovator1610_panel_drv_cleanup(void)
{
platform_driver_unregister(&innovator1610_panel_driver);
}
module_init(innovator1610_panel_drv_init);
module_exit(innovator1610_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd_mipid.c
*
* LCD driver for MIPI DBI-C / DCS compatible LCDs
*
* Copyright (C) 2006 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/device.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/spi/spi.h>
#include <asm/arch/omapfb.h>
#include <asm/arch/lcd_mipid.h>
#include "../../cbus/tahvo.h"
#define MIPID_MODULE_NAME "lcd_mipid"
#define MIPID_CMD_READ_DISP_ID 0x04
#define MIPID_CMD_READ_RED 0x06
#define MIPID_CMD_READ_GREEN 0x07
#define MIPID_CMD_READ_BLUE 0x08
#define MIPID_CMD_READ_DISP_STATUS 0x09
#define MIPID_CMD_RDDSDR 0x0F
#define MIPID_CMD_SLEEP_IN 0x10
#define MIPID_CMD_SLEEP_OUT 0x11
#define MIPID_CMD_DISP_OFF 0x28
#define MIPID_CMD_DISP_ON 0x29
#define MIPID_VER_LPH8923 3
#define MIPID_VER_LS041Y3 4
#define MIPID_ESD_CHECK_PERIOD msecs_to_jiffies(5000)
#define to_mipid_device(p) container_of(p, struct mipid_device, \
panel)
struct mipid_device {
int enabled;
int model;
int revision;
u8 display_id[3];
unsigned int saved_bklight_level;
unsigned long hw_guard_end; /* next value of jiffies
when we can issue the
next sleep in/out command */
unsigned long hw_guard_wait; /* max guard time in jiffies */
struct omapfb_device *fbdev;
struct spi_device *spi;
struct mutex mutex;
struct lcd_panel panel;
struct workqueue_struct *esd_wq;
struct delayed_work esd_work;
void (*esd_check)(struct mipid_device *m);
};
static void mipid_transfer(struct mipid_device *md, int cmd, const u8 *wbuf,
int wlen, u8 *rbuf, int rlen)
{
struct spi_message m;
struct spi_transfer *x, xfer[4];
u16 w;
int r;
BUG_ON(md->spi == NULL);
spi_message_init(&m);
memset(xfer, 0, sizeof(xfer));
x = &xfer[0];
cmd &= 0xff;
x->tx_buf = &cmd;
x->bits_per_word= 9;
x->len = 2;
spi_message_add_tail(x, &m);
if (wlen) {
x++;
x->tx_buf = wbuf;
x->len = wlen;
x->bits_per_word= 9;
spi_message_add_tail(x, &m);
}
if (rlen) {
x++;
x->rx_buf = &w;
x->len = 1;
spi_message_add_tail(x, &m);
if (rlen > 1) {
/* Arrange for the extra clock before the first
* data bit.
*/
x->bits_per_word = 9;
x->len = 2;
x++;
x->rx_buf = &rbuf[1];
x->len = rlen - 1;
spi_message_add_tail(x, &m);
}
}
r = spi_sync(md->spi, &m);
if (r < 0)
dev_dbg(&md->spi->dev, "spi_sync %d\n", r);
if (rlen)
rbuf[0] = w & 0xff;
}
static inline void mipid_cmd(struct mipid_device *md, int cmd)
{
mipid_transfer(md, cmd, NULL, 0, NULL, 0);
}
static inline void mipid_write(struct mipid_device *md,
int reg, const u8 *buf, int len)
{
mipid_transfer(md, reg, buf, len, NULL, 0);
}
static inline void mipid_read(struct mipid_device *md,
int reg, u8 *buf, int len)
{
mipid_transfer(md, reg, NULL, 0, buf, len);
}
static void set_data_lines(struct mipid_device *md, int data_lines)
{
u16 par;
switch (data_lines) {
case 16:
par = 0x150;
break;
case 18:
par = 0x160;
break;
case 24:
par = 0x170;
break;
}
mipid_write(md, 0x3a, (u8 *)&par, 2);
}
static void send_init_string(struct mipid_device *md)
{
u16 initpar[] = { 0x0102, 0x0100, 0x0100 };
mipid_write(md, 0xc2, (u8 *)initpar, sizeof(initpar));
set_data_lines(md, md->panel.data_lines);
}
static void hw_guard_start(struct mipid_device *md, int guard_msec)
{
md->hw_guard_wait = msecs_to_jiffies(guard_msec);
md->hw_guard_end = jiffies + md->hw_guard_wait;
}
static void hw_guard_wait(struct mipid_device *md)
{
unsigned long wait = md->hw_guard_end - jiffies;
if ((long)wait > 0 && wait <= md->hw_guard_wait) {
set_current_state(TASK_UNINTERRUPTIBLE);
schedule_timeout(wait);
}
}
static void set_sleep_mode(struct mipid_device *md, int on)
{
int cmd, sleep_time = 50;
if (on)
cmd = MIPID_CMD_SLEEP_IN;
else
cmd = MIPID_CMD_SLEEP_OUT;
hw_guard_wait(md);
mipid_cmd(md, cmd);
hw_guard_start(md, 120);
/* When we enable the panel, it seems we _have_ to sleep
* 120 ms before sending the init string. When disabling the
* panel we'll sleep for the duration of 2 frames, so that the
* controller can still provide the PCLK,HS,VS signals. */
if (!on)
sleep_time = 120;
msleep(sleep_time);
}
static void set_display_state(struct mipid_device *md, int enabled)
{
int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
mipid_cmd(md, cmd);
}
static int mipid_set_bklight_level(struct lcd_panel *panel, unsigned int level)
{
struct mipid_device *md = to_mipid_device(panel);
if (level > tahvo_get_max_backlight_level())
return -EINVAL;
if (!md->enabled) {
md->saved_bklight_level = level;
return 0;
}
tahvo_set_backlight_level(level);
return 0;
}
static unsigned int mipid_get_bklight_level(struct lcd_panel *panel)
{
return tahvo_get_backlight_level();
}
static unsigned int mipid_get_bklight_max(struct lcd_panel *panel)
{
return tahvo_get_max_backlight_level();
}
static unsigned long mipid_get_caps(struct lcd_panel *panel)
{
return OMAPFB_CAPS_SET_BACKLIGHT;
}
static u16 read_first_pixel(struct mipid_device *md)
{
u16 pixel;
u8 red, green, blue;
mutex_lock(&md->mutex);
mipid_read(md, MIPID_CMD_READ_RED, &red, 1);
mipid_read(md, MIPID_CMD_READ_GREEN, &green, 1);
mipid_read(md, MIPID_CMD_READ_BLUE, &blue, 1);
mutex_unlock(&md->mutex);
switch (md->panel.data_lines) {
case 16:
pixel = ((red >> 1) << 11) | (green << 5) | (blue >> 1);
break;
case 24:
/* 24 bit -> 16 bit */
pixel = ((red >> 3) << 11) | ((green >> 2) << 5) |
(blue >> 3);
break;
default:
BUG();
}
return pixel;
}
static int mipid_run_test(struct lcd_panel *panel, int test_num)
{
struct mipid_device *md = to_mipid_device(panel);
static const u16 test_values[4] = {
0x0000, 0xffff, 0xaaaa, 0x5555,
};
int i;
if (test_num != MIPID_TEST_RGB_LINES)
return MIPID_TEST_INVALID;
for (i = 0; i < ARRAY_SIZE(test_values); i++) {
int delay;
unsigned long tmo;
omapfb_write_first_pixel(md->fbdev, test_values[i]);
tmo = jiffies + msecs_to_jiffies(100);
delay = 25;
while (1) {
u16 pixel;
msleep(delay);
pixel = read_first_pixel(md);
if (pixel == test_values[i])
break;
if (time_after(jiffies, tmo)) {
dev_err(&md->spi->dev,
"MIPI LCD RGB I/F test failed: "
"expecting %04x, got %04x\n",
test_values[i], pixel);
return MIPID_TEST_FAILED;
}
delay = 10;
}
}
return 0;
}
static void ls041y3_esd_recover(struct mipid_device *md)
{
dev_err(&md->spi->dev, "performing LCD ESD recovery\n");
set_sleep_mode(md, 1);
set_sleep_mode(md, 0);
}
static void ls041y3_esd_check_mode1(struct mipid_device *md)
{
u8 state1, state2;
mipid_read(md, MIPID_CMD_RDDSDR, &state1, 1);
set_sleep_mode(md, 0);
mipid_read(md, MIPID_CMD_RDDSDR, &state2, 1);
dev_dbg(&md->spi->dev, "ESD mode 1 state1 %02x state2 %02x\n",
state1, state2);
/* Each sleep out command will trigger a self diagnostic and flip
* Bit6 if the test passes.
*/
if (!((state1 ^ state2) & (1 << 6)))
ls041y3_esd_recover(md);
}
static void ls041y3_esd_check_mode2(struct mipid_device *md)
{
int i;
u8 rbuf[2];
static const struct {
int cmd;
int wlen;
u16 wbuf[3];
} *rd, rd_ctrl[7] = {
{ 0xb0, 4, { 0x0101, 0x01fe, } },
{ 0xb1, 4, { 0x01de, 0x0121, } },
{ 0xc2, 4, { 0x0100, 0x0100, } },
{ 0xbd, 2, { 0x0100, } },
{ 0xc2, 4, { 0x01fc, 0x0103, } },
{ 0xb4, 0, },
{ 0x00, 0, },
};
rd = rd_ctrl;
for (i = 0; i < 3; i++, rd++)
mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen);
udelay(10);
mipid_read(md, rd->cmd, rbuf, 2);
rd++;
for (i = 0; i < 3; i++, rd++) {
udelay(10);
mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen);
}
dev_dbg(&md->spi->dev, "ESD mode 2 state %02x\n", rbuf[1]);
if (rbuf[1] == 0x00)
ls041y3_esd_recover(md);
}
static void ls041y3_esd_check(struct mipid_device *md)
{
ls041y3_esd_check_mode1(md);
if (md->revision >= 0x88)
ls041y3_esd_check_mode2(md);
}
static void mipid_esd_start_check(struct mipid_device *md)
{
if (md->esd_check != NULL)
queue_delayed_work(md->esd_wq, &md->esd_work,
MIPID_ESD_CHECK_PERIOD);
}
static void mipid_esd_stop_check(struct mipid_device *md)
{
if (md->esd_check != NULL)
cancel_rearming_delayed_workqueue(md->esd_wq, &md->esd_work);
}
static void mipid_esd_work(struct work_struct *work)
{
struct mipid_device *md = container_of(work, struct mipid_device, esd_work.work);
mutex_lock(&md->mutex);
md->esd_check(md);
mutex_unlock(&md->mutex);
mipid_esd_start_check(md);
}
static int mipid_enable(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
mutex_lock(&md->mutex);
if (md->enabled) {
mutex_unlock(&md->mutex);
return 0;
}
set_sleep_mode(md, 0);
md->enabled = 1;
send_init_string(md);
set_display_state(md, 1);
mipid_set_bklight_level(panel, md->saved_bklight_level);
mipid_esd_start_check(md);
mutex_unlock(&md->mutex);
return 0;
}
static void mipid_disable(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
/* A final ESD work might be called before returning,
* so do this without holding the lock. */
mipid_esd_stop_check(md);
mutex_lock(&md->mutex);
if (!md->enabled) {
mutex_unlock(&md->mutex);
return;
}
md->saved_bklight_level = mipid_get_bklight_level(panel);
mipid_set_bklight_level(panel, 0);
set_display_state(md, 0);
set_sleep_mode(md, 1);
md->enabled = 0;
mutex_unlock(&md->mutex);
}
static int panel_enabled(struct mipid_device *md)
{
u32 disp_status;
int enabled;
mipid_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4);
disp_status = __be32_to_cpu(disp_status);
enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10));
dev_dbg(&md->spi->dev,
"LCD panel %s enabled by bootloader (status 0x%04x)\n",
enabled ? "" : "not ", disp_status);
return enabled;
}
static int mipid_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
struct mipid_device *md = to_mipid_device(panel);
md->fbdev = fbdev;
md->esd_wq = create_singlethread_workqueue("mipid_esd");
if (md->esd_wq == NULL) {
dev_err(&md->spi->dev, "can't create ESD workqueue\n");
return -ENOMEM;
}
INIT_DELAYED_WORK(&md->esd_work, mipid_esd_work);
mutex_init(&md->mutex);
md->enabled = panel_enabled(md);
if (md->enabled)
mipid_esd_start_check(md);
else
md->saved_bklight_level = mipid_get_bklight_level(panel);
return 0;
}
static void mipid_cleanup(struct lcd_panel *panel)
{
struct mipid_device *md = to_mipid_device(panel);
if (md->enabled)
mipid_esd_stop_check(md);
destroy_workqueue(md->esd_wq);
}
static struct lcd_panel mipid_panel = {
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.x_res = 800,
.y_res = 480,
.pixel_clock = 21940,
.hsw = 50,
.hfp = 20,
.hbp = 15,
.vsw = 2,
.vfp = 1,
.vbp = 3,
.init = mipid_init,
.cleanup = mipid_cleanup,
.enable = mipid_enable,
.disable = mipid_disable,
.get_caps = mipid_get_caps,
.set_bklight_level= mipid_set_bklight_level,
.get_bklight_level= mipid_get_bklight_level,
.get_bklight_max= mipid_get_bklight_max,
.run_test = mipid_run_test,
};
static int mipid_detect(struct mipid_device *md)
{
struct mipid_platform_data *pdata;
pdata = md->spi->dev.platform_data;
if (pdata == NULL) {
dev_err(&md->spi->dev, "missing platform data\n");
return -ENOENT;
}
mipid_read(md, MIPID_CMD_READ_DISP_ID, md->display_id, 3);
dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n",
md->display_id[0], md->display_id[1], md->display_id[2]);
switch (md->display_id[0]) {
case 0x45:
md->model = MIPID_VER_LPH8923;
md->panel.name = "lph8923";
break;
case 0x83:
md->model = MIPID_VER_LS041Y3;
md->panel.name = "ls041y3";
md->esd_check = ls041y3_esd_check;
break;
default:
md->panel.name = "unknown";
dev_err(&md->spi->dev, "invalid display ID\n");
return -ENODEV;
}
md->revision = md->display_id[1];
md->panel.data_lines = pdata->data_lines;
pr_info("omapfb: %s rev %02x LCD detected\n",
md->panel.name, md->revision);
return 0;
}
static int mipid_spi_probe(struct spi_device *spi)
{
struct mipid_device *md;
int r;
md = kzalloc(sizeof(*md), GFP_KERNEL);
if (md == NULL) {
dev_err(&md->spi->dev, "out of memory\n");
return -ENOMEM;
}
spi->mode = SPI_MODE_1;
md->spi = spi;
dev_set_drvdata(&spi->dev, md);
md->panel = mipid_panel;
r = mipid_detect(md);
if (r < 0)
return r;
omapfb_register_panel(&md->panel);
return 0;
}
static int mipid_spi_remove(struct spi_device *spi)
{
struct mipid_device *md = dev_get_drvdata(&spi->dev);
mipid_disable(&md->panel);
kfree(md);
return 0;
}
static struct spi_driver mipid_spi_driver = {
.driver = {
.name = MIPID_MODULE_NAME,
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = mipid_spi_probe,
.remove = __devexit_p(mipid_spi_remove),
};
static int mipid_drv_init(void)
{
spi_register_driver(&mipid_spi_driver);
return 0;
}
module_init(mipid_drv_init);
static void mipid_drv_cleanup(void)
{
spi_unregister_driver(&mipid_spi_driver);
}
module_exit(mipid_drv_cleanup);
MODULE_DESCRIPTION("MIPI display driver");
MODULE_LICENSE("GPL");
/*
* File: drivers/video/omap/lcd-osk.c
*
* LCD panel support for the TI OMAP OSK board
*
* Copyright (C) 2004 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
* Adapted for OSK by <dirk.behme@de.bosch.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/platform_device.h>
#include <asm/arch/gpio.h>
#include <asm/arch/mux.h>
#include <asm/arch/omapfb.h>
static int osk_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
{
return 0;
}
static void osk_panel_cleanup(struct lcd_panel *panel)
{
}
static int osk_panel_enable(struct lcd_panel *panel)
{
/* configure PWL pin */
omap_cfg_reg(PWL);
/* Enable PWL unit */
omap_writeb(0x01, OMAP_PWL_CLK_ENABLE);
/* Set PWL level */
omap_writeb(0xFF, OMAP_PWL_ENABLE);
/* configure GPIO2 as output */
omap_set_gpio_direction(2, 0);
/* set GPIO2 high */
omap_set_gpio_dataout(2, 1);
return 0;
}
static void osk_panel_disable(struct lcd_panel *panel)
{
/* Set PWL level to zero */
omap_writeb(0x00, OMAP_PWL_ENABLE);
/* Disable PWL unit */
omap_writeb(0x00, OMAP_PWL_CLK_ENABLE);
/* set GPIO2 low */
omap_set_gpio_dataout(2, 0);
}
static unsigned long osk_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel osk_panel = {
.name = "osk",
.config = OMAP_LCDC_PANEL_TFT,
.bpp = 16,
.data_lines = 16,
.x_res = 240,
.y_res = 320,
.pixel_clock = 12500,
.hsw = 40,
.hfp = 40,
.hbp = 72,
.vsw = 1,
.vfp = 1,
.vbp = 0,
.pcd = 12,
.init = osk_panel_init,
.cleanup = osk_panel_cleanup,
.enable = osk_panel_enable,
.disable = osk_panel_disable,
.get_caps = osk_panel_get_caps,
};
static int osk_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&osk_panel);
return 0;
}
static int osk_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int osk_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int osk_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver osk_panel_driver = {
.probe = osk_panel_probe,
.remove = osk_panel_remove,
.suspend = osk_panel_suspend,
.resume = osk_panel_resume,
.driver = {
.name = "lcd_osk",
.owner = THIS_MODULE,
},
};
static int osk_panel_drv_init(void)
{
return platform_driver_register(&osk_panel_driver);
}
static void osk_panel_drv_cleanup(void)
{
platform_driver_unregister(&osk_panel_driver);
}
module_init(osk_panel_drv_init);
module_exit(osk_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd-p2.c
*
* LCD panel support for the TI OMAP P2 board
*
* Authors:
* jekyll <jekyll@mail.jekyll.idv.tw>
* B Jp <lastjp_fr@yahoo.fr>
* Brian Swetland <swetland@android.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/delay.h>
#include <linux/platform_device.h>
#include <asm/arch/mux.h>
#include <asm/arch/gpio.h>
#include <asm/arch/omapfb.h>
/*
* File: epson-md-tft.h
*
* This file contains definitions for Epsons MD-TF LCD Module
*
* Copyright (C) 2004 MPC-Data Limited (http://www.mpc-data.co.uk)
* Author: Dave Peverley <dpeverley at mpc-data.co.uk>
*
* 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 SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*
* Please report all bugs and problems to the author.
*
*/
/* LCD uWire commands & params
* All values from Epson
*/
#define LCD_DISON 0xAF
#define LCD_DISOFF 0xAE
#define LCD_DISNOR 0xA6
#define LCD_DISINV 0xA7
#define LCD_DISCTL 0xCA
#define LCD_GCP64 0xCB
#define LCD_GCP16 0xCC
#define LCD_GSSET 0xCD
#define LCD_SLPIN 0x95
#define LCD_SLPOUT 0x94
#define LCD_SD_PSET 0x75
#define LCD_MD_PSET 0x76
#define LCD_SD_CSET 0x15
#define LCD_MD_CSET 0x16
#define LCD_DATCTL 0xBC
#define LCD_RAMWR 0x5C
#define LCD_RAMRD 0x5D
#define LCD_PTLIN 0xA8
#define LCD_PTLOUT 0xA9
#define LCD_ASCSET 0xAA
#define LCD_SCSTART 0xAB
#define LCD_VOLCTL 0xC6
#define LCD_NOP 0x25
#define LCD_OSCISEL 0x7
#define LCD_3500KSET 0xD1
#define LCD_3500KEND 0xD2
#define LCD_14MSET 0xD3
#define LCD_14MEND 0xD4
#define INIT_3500KSET 0x45
#define INIT_14MSET 0x4B
#define INIT_DATCTL 0x08 /* 6.6.6 bits for D-Sample */
#define INIT_OSCISEL 0x05
#define INIT_VOLCTL 0x77 /* Nominel "volume" */
#define INIT_VOLCTL_Ton 0x98 /* Activate power-IC timer */
#define INIT_GSSET 0x00
const unsigned short INIT_DISCTL[11] =
{
0xDE, 0x01, 0x64, 0x00, 0x1B, 0xF4, 0x00, 0xDC, 0x00, 0x02, 0x00
};
const unsigned short INIT_GCP64[126] =
{
0x3B,0x00,0x42,0x00,0x4A,0x00,0x51,0x00,
0x58,0x00,0x5F,0x00,0x66,0x00,0x6E,0x00,
0x75,0x00,0x7C,0x00,0x83,0x00,0x8A,0x00,
0x92,0x00,0x99,0x00,0xA0,0x00,0xA7,0x00,
0xAE,0x00,0xB6,0x00,0xBD,0x00,0xC4,0x00,
0xCB,0x00,0xD2,0x00,0xDA,0x00,0xE1,0x00,
0xE8,0x00,0xEF,0x00,0xF6,0x00,0xFE,0x00,
0x05,0x01,0x0C,0x01,0x13,0x01,0x1A,0x01,
0x22,0x01,0x29,0x01,0x30,0x01,0x37,0x01,
0x3E,0x01,0x46,0x01,0x4D,0x01,0x54,0x01,
0x5B,0x01,0x62,0x01,0x6A,0x01,0x71,0x01,
0x78,0x01,0x7F,0x01,0x86,0x01,0x8E,0x01,
0x95,0x01,0x9C,0x01,0xA3,0x01,0xAA,0x01,
0xB2,0x01,0xB9,0x01,0xC0,0x01,0xC7,0x01,
0xCE,0x01,0xD6,0x01,0xDD,0x01,0xE4,0x01,
0xEB,0x01,0xF2,0x01,0xFA,0x01
};
const unsigned short INIT_GCP16[15] =
{
0x1A,0x31,0x48,0x54,0x5F,0x67,0x70,0x76,0x7C,0x80,0x83,0x84,0x85,0x87,0x96
};
const unsigned short INIT_MD_PSET[4] = { 0, 0, 219, 0 };
const unsigned short INIT_MD_CSET[4] = { 2, 0, 177, 0 };
const unsigned short INIT_SD_PSET[4] = { 0x00, 0x01, 0x00, 0x01 };
const unsigned short INIT_SD_CSET[4] = { 0x00, 0x02, 0x00, 0x02 };
const unsigned short INIT_ASCSET[7] = { 0x00, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0x01 };
const unsigned short INIT_SCSTART[2] = { 0x00, 0x00 };
/* ----- end of epson_md_tft.h ----- */
#include "../drivers/ssi/omap-uwire.h"
#define LCD_UWIRE_CS 0
static int p2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
{
return 0;
}
static void p2_panel_cleanup(struct lcd_panel *panel)
{
}
static int p2_panel_enable(struct lcd_panel *panel)
{
int i;
unsigned long value;
/* thwack the reset line */
omap_set_gpio_direction(19, 0);
omap_set_gpio_dataout(19, 0);
mdelay(2);
omap_set_gpio_dataout(19, 1);
/* bits 31:28 -> 0 LCD_PXL_15 .. 12 */
value = omap_readl(OMAP730_IO_CONF_3) & 0x0FFFFFFF;
omap_writel(value, OMAP730_IO_CONF_3);
/* bits 19:0 -> 0 LCD_VSYNC, AC, PXL_0, PCLK, HSYNC,
** PXL_9..1, PXL_10, PXL_11
*/
value = omap_readl(OMAP730_IO_CONF_4) & 0xFFF00000;
omap_writel(value, OMAP730_IO_CONF_4);
omap_uwire_configure_mode(0,16);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISOFF, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPIN, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISNOR, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GSSET, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GSSET | 0x100), 9, 0,NULL,1);
/* DISCTL */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISCTL, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_DISCTL)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DISCTL[i] | 0x100), 9, 0,NULL,1);
/* GCP64 */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP64, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_GCP64)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP64[i] | 0x100), 9, 0,NULL,1);
/* GCP16 */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP16, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_GCP16)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP16[i] | 0x100), 9, 0,NULL,1);
/* MD_CSET */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_CSET, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_MD_CSET)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_CSET[i] | 0x100), 9, 0,NULL,1);
/* MD_PSET */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_PSET, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_MD_PSET)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_PSET[i] | 0x100), 9, 0,NULL,1);
/* SD_CSET */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_CSET, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_SD_CSET)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_CSET[i] | 0x100), 9, 0,NULL,1);
/* SD_PSET */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_PSET, 9, 0,NULL,1);
for (i = 0; i < (sizeof(INIT_SD_PSET)/sizeof(unsigned short)); i++)
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_PSET[i] | 0x100), 9, 0,NULL,1);
/* DATCTL */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DATCTL, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DATCTL | 0x100), 9, 0,NULL,1);
/* OSSISEL = d'5 */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_OSCISEL, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_OSCISEL | 0x100), 9, 0,NULL,1);
/* 14MSET = d'74 */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MSET, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_14MSET | 0x100), 9, 0,NULL,1);
/* 14MEND */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MEND, 9, 0,NULL,1);
/* 3500KSET = d'69 */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KSET, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_3500KSET | 0x100), 9, 0,NULL,1);
/* 3500KEND */
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KEND, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPOUT, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL_Ton | 0x100), 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL | 0x100), 9, 0,NULL,1);
omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISON, 9, 0,NULL,1);
/* enable backlight */
omap_set_gpio_direction(134, 0);
omap_set_gpio_dataout(134, 1);
return 0;
}
static void p2_panel_disable(struct lcd_panel *panel)
{
}
static unsigned long p2_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel p2_panel = {
.name = "p2",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_PIX_CLOCK,
.bpp = 16,
.data_lines = 16,
.x_res = 176,
.y_res = 220,
.pixel_clock = 12500,
.hsw = 5,
.hfp = 1,
.hbp = 1,
.vsw = 2,
.vfp = 12,
.vbp = 1,
.init = p2_panel_init,
.cleanup = p2_panel_cleanup,
.enable = p2_panel_enable,
.disable = p2_panel_disable,
.get_caps = p2_panel_get_caps,
};
static int p2_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&p2_panel);
return 0;
}
static int p2_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int p2_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int p2_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver p2_panel_driver = {
.probe = p2_panel_probe,
.remove = p2_panel_remove,
.suspend = p2_panel_suspend,
.resume = p2_panel_resume,
.driver = {
.name = "lcd_p2",
.owner = THIS_MODULE,
},
};
static int p2_panel_drv_init(void)
{
return platform_driver_register(&p2_panel_driver);
}
static void p2_panel_drv_cleanup(void)
{
platform_driver_unregister(&p2_panel_driver);
}
module_init(p2_panel_drv_init);
module_exit(p2_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd_palmte.c
*
* LCD panel support for the Palm Tungsten E
*
* Original version : Romain Goyet
* Current version : Laurent Gonzalez
*
* 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/platform_device.h>
#include <asm/io.h>
#include <asm/arch/fpga.h>
#include <asm/arch/omapfb.h>
static int palmte_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
return 0;
}
static void palmte_panel_cleanup(struct lcd_panel *panel)
{
}
static int palmte_panel_enable(struct lcd_panel *panel)
{
return 0;
}
static void palmte_panel_disable(struct lcd_panel *panel)
{
}
static unsigned long palmte_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel palmte_panel = {
.name = "palmte",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE |
OMAP_LCDC_HSVS_OPPOSITE,
.data_lines = 16,
.bpp = 8,
.pixel_clock = 12000,
.x_res = 320,
.y_res = 320,
.hsw = 4,
.hfp = 8,
.hbp = 28,
.vsw = 1,
.vfp = 8,
.vbp = 7,
.pcd = 0,
.init = palmte_panel_init,
.cleanup = palmte_panel_cleanup,
.enable = palmte_panel_enable,
.disable = palmte_panel_disable,
.get_caps = palmte_panel_get_caps,
};
static int palmte_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&palmte_panel);
return 0;
}
static int palmte_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int palmte_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int palmte_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver palmte_panel_driver = {
.probe = palmte_panel_probe,
.remove = palmte_panel_remove,
.suspend = palmte_panel_suspend,
.resume = palmte_panel_resume,
.driver = {
.name = "lcd_palmte",
.owner = THIS_MODULE,
},
};
static int palmte_panel_drv_init(void)
{
return platform_driver_register(&palmte_panel_driver);
}
static void palmte_panel_drv_cleanup(void)
{
platform_driver_unregister(&palmte_panel_driver);
}
module_init(palmte_panel_drv_init);
module_exit(palmte_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd_palmtt.c
*
* LCD panel support for Palm Tungsten|T
* Current version : Marek Vasut <marek.vasut@gmail.com>
*
* Modified from lcd_inn1510.c
*
* 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.
*/
/*
GPIO11 - backlight
GPIO12 - screen blanking
GPIO13 - screen blanking
*/
#include <linux/platform_device.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/arch/gpio.h>
#include "asm/arch/omapfb.h"
static int palmtt_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
return 0;
}
static void palmtt_panel_cleanup(struct lcd_panel *panel)
{
}
static int palmtt_panel_enable(struct lcd_panel *panel)
{
return 0;
}
static void palmtt_panel_disable(struct lcd_panel *panel)
{
}
static unsigned long palmtt_panel_get_caps(struct lcd_panel *panel)
{
return OMAPFB_CAPS_SET_BACKLIGHT;
}
struct lcd_panel palmtt_panel = {
.name = "palmtt",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE |
OMAP_LCDC_HSVS_OPPOSITE,
.bpp = 16,
.data_lines = 16,
.x_res = 320,
.y_res = 320,
.pixel_clock = 10000,
.hsw = 4,
.hfp = 8,
.hbp = 28,
.vsw = 1,
.vfp = 8,
.vbp = 7,
.pcd = 0,
.init= palmtt_panel_init,
.cleanup = palmtt_panel_cleanup,
.enable= palmtt_panel_enable,
.disable = palmtt_panel_disable,
.get_caps = palmtt_panel_get_caps,
};
static int palmtt_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&palmtt_panel);
return 0;
}
static int palmtt_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int palmtt_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int palmtt_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver palmtt_panel_driver = {
.probe = palmtt_panel_probe,
.remove = palmtt_panel_remove,
.suspend = palmtt_panel_suspend,
.resume = palmtt_panel_resume,
.driver = {
.name = "lcd_palmtt",
.owner = THIS_MODULE,
},
};
static int palmtt_panel_drv_init(void)
{
return platform_driver_register(&palmtt_panel_driver);
}
static void palmtt_panel_drv_cleanup(void)
{
platform_driver_unregister(&palmtt_panel_driver);
}
module_init(palmtt_panel_drv_init);
module_exit(palmtt_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd_palmz71.c
*
* LCD panel support for the Palm Zire71
*
* Original version : Romain Goyet
* Current version : Laurent Gonzalez
* Modified for zire71 : Marek Vasut
*
* 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/platform_device.h>
#include <asm/io.h>
#include <asm/arch/gpio.h>
#include <asm/arch/omapfb.h>
static int palmz71_panel_init(struct lcd_panel *panel,
struct omapfb_device *fbdev)
{
return 0;
}
static void palmz71_panel_cleanup(struct lcd_panel *panel)
{
}
static int palmz71_panel_enable(struct lcd_panel *panel)
{
return 0;
}
static void palmz71_panel_disable(struct lcd_panel *panel)
{
}
static unsigned long palmz71_panel_get_caps(struct lcd_panel *panel)
{
return OMAPFB_CAPS_SET_BACKLIGHT;
}
struct lcd_panel palmz71_panel = {
.name = "palmz71",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
OMAP_LCDC_INV_HSYNC | OMAP_LCDC_HSVS_RISING_EDGE |
OMAP_LCDC_HSVS_OPPOSITE,
.data_lines = 16,
.bpp = 16,
.pixel_clock = 24000,
.x_res = 320,
.y_res = 320,
.hsw = 4,
.hfp = 8,
.hbp = 28,
.vsw = 1,
.vfp = 8,
.vbp = 7,
.pcd = 0,
.init = palmz71_panel_init,
.cleanup = palmz71_panel_cleanup,
.enable = palmz71_panel_enable,
.disable = palmz71_panel_disable,
.get_caps = palmz71_panel_get_caps,
};
static int palmz71_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&palmz71_panel);
return 0;
}
static int palmz71_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int palmz71_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int palmz71_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver palmz71_panel_driver = {
.probe = palmz71_panel_probe,
.remove = palmz71_panel_remove,
.suspend = palmz71_panel_suspend,
.resume = palmz71_panel_resume,
.driver = {
.name = "lcd_palmz71",
.owner = THIS_MODULE,
},
};
static int palmz71_panel_drv_init(void)
{
return platform_driver_register(&palmz71_panel_driver);
}
static void palmz71_panel_drv_cleanup(void)
{
platform_driver_unregister(&palmz71_panel_driver);
}
module_init(palmz71_panel_drv_init);
module_exit(palmz71_panel_drv_cleanup);
/*
* File: drivers/video/omap/lcd_sx1.c
*
* LCD panel support for the Siemens SX1 mobile phone
*
* Current version : Vovan888 at gmail com, great help from FCA00000
*
* 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/platform_device.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <asm/arch/gpio.h>
#include <asm/arch/omapfb.h>
#include <asm/arch/mcbsp.h>
#include <asm/arch/mux.h>
/*
* OMAP310 GPIO registers
*/
#define GPIO_DATA_INPUT 0xfffce000
#define GPIO_DATA_OUTPUT 0xfffce004
#define GPIO_DIR_CONTROL 0xfffce008
#define GPIO_INT_CONTROL 0xfffce00c
#define GPIO_INT_MASK 0xfffce010
#define GPIO_INT_STATUS 0xfffce014
#define GPIO_PIN_CONTROL 0xfffce018
#define A_LCD_SSC_RD 3
#define A_LCD_SSC_SD 7
#define _A_LCD_RESET 9
#define _A_LCD_SSC_CS 12
#define _A_LCD_SSC_A0 13
#define DSP_REG 0xE1017024
const unsigned char INIT_1[12] = {
0x1C, 0x02, 0x88, 0x00, 0x1E, 0xE0, 0x00, 0xDC, 0x00, 0x02, 0x00
};
const unsigned char INIT_2[127] = {
0x15, 0x00, 0x29, 0x00, 0x3E, 0x00, 0x51, 0x00, 0x65, 0x00, 0x7A, 0x00, 0x8D, 0x00, 0xA1, 0x00,
0xB6, 0x00, 0xC7, 0x00, 0xD8, 0x00, 0xEB, 0x00, 0xFB, 0x00, 0x0B, 0x01, 0x1B, 0x01, 0x27, 0x01,
0x34, 0x01, 0x41, 0x01, 0x4C, 0x01, 0x55, 0x01, 0x5F, 0x01, 0x68, 0x01, 0x70, 0x01, 0x78, 0x01,
0x7E, 0x01, 0x86, 0x01, 0x8C, 0x01, 0x94, 0x01, 0x9B, 0x01, 0xA1, 0x01, 0xA4, 0x01, 0xA9, 0x01,
0xAD, 0x01, 0xB2, 0x01, 0xB7, 0x01, 0xBC, 0x01, 0xC0, 0x01, 0xC4, 0x01, 0xC8, 0x01, 0xCB, 0x01,
0xCF, 0x01, 0xD2, 0x01, 0xD5, 0x01, 0xD8, 0x01, 0xDB, 0x01, 0xE0, 0x01, 0xE3, 0x01, 0xE6, 0x01,
0xE8, 0x01, 0xEB, 0x01, 0xEE, 0x01, 0xF1, 0x01, 0xF3, 0x01, 0xF8, 0x01, 0xF9, 0x01, 0xFC, 0x01,
0x00, 0x02, 0x03, 0x02, 0x07, 0x02, 0x09, 0x02, 0x0E, 0x02, 0x13, 0x02, 0x1C, 0x02, 0x00
};
const unsigned char INIT_3[15] = {
0x14, 0x26, 0x33, 0x3D, 0x45, 0x4D, 0x53, 0x59,
0x5E, 0x63, 0x67, 0x6D, 0x71, 0x78, 0xFF
};
static void epson_sendbyte(int flag, unsigned char byte)
{
int i, shifter = 0x80;
if (!flag)
omap_set_gpio_dataout(_A_LCD_SSC_A0, 0);
mdelay(2);
omap_set_gpio_dataout(A_LCD_SSC_RD, 1);
omap_set_gpio_dataout(A_LCD_SSC_SD, flag);
OMAP_MCBSP_WRITE(OMAP1510_MCBSP3_BASE, PCR0, 0x2200);
OMAP_MCBSP_WRITE(OMAP1510_MCBSP3_BASE, PCR0, 0x2202);
for (i = 0; i < 8; i++) {
OMAP_MCBSP_WRITE(OMAP1510_MCBSP3_BASE, PCR0, 0x2200);
omap_set_gpio_dataout(A_LCD_SSC_SD, shifter & byte);
OMAP_MCBSP_WRITE(OMAP1510_MCBSP3_BASE, PCR0, 0x2202);
shifter >>= 1;
}
omap_set_gpio_dataout(_A_LCD_SSC_A0, 1);
}
static void init_system(void)
{
omap_mcbsp_request(OMAP_MCBSP3);
omap_mcbsp_stop(OMAP_MCBSP3);
}
static void setup_GPIO(void)
{
/* new wave */
omap_request_gpio(A_LCD_SSC_RD);
omap_request_gpio(A_LCD_SSC_SD);
omap_request_gpio(_A_LCD_RESET);
omap_request_gpio(_A_LCD_SSC_CS);
omap_request_gpio(_A_LCD_SSC_A0);
/* set all GPIOs to output */
omap_set_gpio_direction(A_LCD_SSC_RD, 0);
omap_set_gpio_direction(A_LCD_SSC_SD, 0);
omap_set_gpio_direction(_A_LCD_RESET, 0);
omap_set_gpio_direction(_A_LCD_SSC_CS, 0);
omap_set_gpio_direction(_A_LCD_SSC_A0, 0);
/* set GPIO data */
omap_set_gpio_dataout(A_LCD_SSC_RD, 1);
omap_set_gpio_dataout(A_LCD_SSC_SD, 0);
omap_set_gpio_dataout(_A_LCD_RESET, 0);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_A0, 1);
}
static void display_init(void)
{
int i;
omap_cfg_reg(MCBSP3_CLKX);
mdelay(2);
setup_GPIO();
mdelay(2);
/* reset LCD */
omap_set_gpio_dataout(A_LCD_SSC_SD, 1);
epson_sendbyte(0, 0x25);
omap_set_gpio_dataout(_A_LCD_RESET, 0);
mdelay(10);
omap_set_gpio_dataout(_A_LCD_RESET, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
mdelay(2);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD, phase 1 */
epson_sendbyte(0, 0xCA);
for (i = 0; i < 10; i++)
epson_sendbyte(1, INIT_1[i]);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 2 */
epson_sendbyte(0, 0xCB);
for( i = 0; i < 125; i++)
epson_sendbyte(1, INIT_2[i]);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 2a */
epson_sendbyte(0, 0xCC);
for( i = 0; i < 14; i++)
epson_sendbyte(1, INIT_3[i]);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 3 */
epson_sendbyte(0, 0xBC);
epson_sendbyte(1, 0x08);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 4 */
epson_sendbyte(0, 0x07);
epson_sendbyte(1, 0x05);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 5 */
epson_sendbyte(0, 0x94);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 6 */
epson_sendbyte(0, 0xC6);
epson_sendbyte(1, 0x80);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
mdelay(100); /* used to be 1000 */
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 7 */
epson_sendbyte(0, 0x16);
epson_sendbyte(1, 0x02);
epson_sendbyte(1, 0x00);
epson_sendbyte(1, 0xB1);
epson_sendbyte(1, 0x00);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 8 */
epson_sendbyte(0, 0x76);
epson_sendbyte(1, 0x00);
epson_sendbyte(1, 0x00);
epson_sendbyte(1, 0xDB);
epson_sendbyte(1, 0x00);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
/* init LCD phase 9 */
epson_sendbyte(0, 0xAF);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
}
static int sx1_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
{
return 0;
}
static void sx1_panel_cleanup(struct lcd_panel *panel)
{
}
static void sx1_panel_disable(struct lcd_panel *panel)
{
printk(KERN_INFO "SX1: LCD panel disable\n");
sx1_setmmipower(0);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
epson_sendbyte(0, 0x25);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
epson_sendbyte(0, 0xAE);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
mdelay(100);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 0);
epson_sendbyte(0, 0x95);
omap_set_gpio_dataout(_A_LCD_SSC_CS, 1);
}
static int sx1_panel_enable(struct lcd_panel *panel)
{
#if 0
sx1_panel_disable(); /* try to disable panel first, if we boot from Symbian */
#endif
printk(KERN_INFO "lcd_sx1: LCD panel enable\n");
init_system();
display_init();
sx1_setmmipower(1);
sx1_setbacklight(0x18);
sx1_setkeylight (0x06);
return 0;
}
static unsigned long sx1_panel_get_caps(struct lcd_panel *panel)
{
return 0;
}
struct lcd_panel sx1_panel = {
.name = "sx1",
.config = OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
OMAP_LCDC_INV_HSYNC | OMAP_LCDC_INV_PIX_CLOCK |
OMAP_LCDC_INV_OUTPUT_EN,
.x_res = 176,
.y_res = 220,
.data_lines = 16,
.bpp = 16,
.hsw = 5,
.hfp = 5,
.hbp = 5,
.vsw = 2,
.vfp = 1,
.vbp = 1,
.pixel_clock = 1500,
.init = sx1_panel_init,
.cleanup = sx1_panel_cleanup,
.enable = sx1_panel_enable,
.disable = sx1_panel_disable,
.get_caps = sx1_panel_get_caps,
};
static int sx1_panel_probe(struct platform_device *pdev)
{
omapfb_register_panel(&sx1_panel);
return 0;
}
static int sx1_panel_remove(struct platform_device *pdev)
{
return 0;
}
static int sx1_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
{
return 0;
}
static int sx1_panel_resume(struct platform_device *pdev)
{
return 0;
}
struct platform_driver sx1_panel_driver = {
.probe = sx1_panel_probe,
.remove = sx1_panel_remove,
.suspend = sx1_panel_suspend,
.resume = sx1_panel_resume,
.driver = {
.name = "lcd_sx1",
.owner = THIS_MODULE,
},
};
static int sx1_panel_drv_init(void)
{
return platform_driver_register(&sx1_panel_driver);
}
static void sx1_panel_drv_cleanup(void)
{
platform_driver_unregister(&sx1_panel_driver);
}
module_init(sx1_panel_drv_init);
module_exit(sx1_panel_drv_cleanup);
/*
* File: drivers/video/omap/omap1/lcdc.c
*
* 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 <asm/arch/dma.h>
#include <asm/arch/omapfb.h>
#include <asm/mach-types.h>
#define MODULE_NAME "lcdc"
#define OMAP_LCDC_BASE 0xfffec000
#define OMAP_LCDC_SIZE 256
#define OMAP_LCDC_IRQ INT_LCD_CTRL
#define OMAP_LCDC_CONTROL (OMAP_LCDC_BASE + 0x00)
#define OMAP_LCDC_TIMING0 (OMAP_LCDC_BASE + 0x04)
#define OMAP_LCDC_TIMING1 (OMAP_LCDC_BASE + 0x08)
#define OMAP_LCDC_TIMING2 (OMAP_LCDC_BASE + 0x0c)
#define OMAP_LCDC_STATUS (OMAP_LCDC_BASE + 0x10)
#define OMAP_LCDC_SUBPANEL (OMAP_LCDC_BASE + 0x14)
#define OMAP_LCDC_LINE_INT (OMAP_LCDC_BASE + 0x18)
#define OMAP_LCDC_DISPLAY_STATUS (OMAP_LCDC_BASE + 0x1c)
#define OMAP_LCDC_STAT_DONE (1 << 0)
#define OMAP_LCDC_STAT_VSYNC (1 << 1)
#define OMAP_LCDC_STAT_SYNC_LOST (1 << 2)
#define OMAP_LCDC_STAT_ABC (1 << 3)
#define OMAP_LCDC_STAT_LINE_INT (1 << 4)
#define OMAP_LCDC_STAT_FUF (1 << 5)
#define OMAP_LCDC_STAT_LOADED_PALETTE (1 << 6)
#define OMAP_LCDC_CTRL_LCD_EN (1 << 0)
#define OMAP_LCDC_CTRL_LCD_TFT (1 << 7)
#define OMAP_LCDC_CTRL_LINE_IRQ_CLR_SEL (1 << 10)
#define OMAP_LCDC_IRQ_VSYNC (1 << 2)
#define OMAP_LCDC_IRQ_DONE (1 << 3)
#define OMAP_LCDC_IRQ_LOADED_PALETTE (1 << 4)
#define OMAP_LCDC_IRQ_LINE_NIRQ (1 << 5)
#define OMAP_LCDC_IRQ_LINE (1 << 6)
#define OMAP_LCDC_IRQ_MASK (((1 << 5) - 1) << 2)
#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 = 0;
static unsigned long last_jiffies = 0;
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 controler, 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 seperately.
*/
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)
{
if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) {
disable_controller();
omap_stop_lcd_dma();
}
}
static void omap_lcdc_resume(void)
{
if (lcdc.update_mode == OMAPFB_AUTO_UPDATE) {
setup_regs();
load_palette();
setup_lcd_dma();
set_load_mode(OMAP_LCDC_LOAD_FRAME);
enable_irqs(OMAP_LCDC_IRQ_DONE);
enable_controller();
}
}
static unsigned long omap_lcdc_get_caps(void)
{
return 0;
}
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(NULL, "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(NULL, "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,
};
#ifndef LCDC_H
#define LCDC_H
int omap_lcdc_set_dma_callback(void (*callback)(void *data), void *data);
void omap_lcdc_free_dma_callback(void);
#endif
/*
* File: drivers/video/omap/omapfb_main.c
*
* Framebuffer driver for TI OMAP boards
*
* Copyright (C) 2004 Nokia Corporation
* Author: Imre Deak <imre.deak@nokia.com>
*
* Acknowledgements:
* Alex McMains <aam@ridgerun.com> - Original driver
* Juha Yrjola <juha.yrjola@nokia.com> - Original driver and improvements
* Dirk Behme <dirk.behme@de.bosch.com> - changes for 2.6 kernel API
* Texas Instruments - H3 support
*
* 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/platform_device.h>
#include <asm/uaccess.h>
#include <asm/mach-types.h>
#include <asm/arch/dma.h>
#include <asm/arch/omapfb.h>
#define MODULE_NAME "omapfb"
static unsigned int def_accel;
static unsigned long def_vram[OMAPFB_PLANE_NUM];
static int def_vram_cnt;
static unsigned long def_vxres;
static unsigned long def_vyres;
static unsigned int def_rotate;
static unsigned int def_mirror;
#ifdef CONFIG_FB_OMAP_MANUAL_UPDATE
static int manual_update = 1;
#else
static int manual_update;
#endif
static struct platform_device *fbdev_pdev;
static struct lcd_panel *fbdev_panel;
static struct omapfb_device *omapfb_dev;
struct caps_table_struct {
unsigned long flag;
const char *name;
};
static struct caps_table_struct ctrl_caps[] = {
{ OMAPFB_CAPS_MANUAL_UPDATE, "manual update" },
{ OMAPFB_CAPS_TEARSYNC, "tearing synchronization" },
{ OMAPFB_CAPS_PLANE_RELOCATE_MEM, "relocate plane memory" },
{ OMAPFB_CAPS_PLANE_SCALE, "scale plane" },
{ OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE, "pixel double window" },
{ OMAPFB_CAPS_WINDOW_SCALE, "scale window" },
{ OMAPFB_CAPS_WINDOW_OVERLAY,"overlay window" },
{ OMAPFB_CAPS_SET_BACKLIGHT, "backlight setting" },
};
static struct caps_table_struct color_caps[] = {
{ 1 << OMAPFB_COLOR_RGB565, "RGB565", },
{ 1 << OMAPFB_COLOR_YUV422, "YUV422", },
{ 1 << OMAPFB_COLOR_YUV420, "YUV420", },
{ 1 << OMAPFB_COLOR_CLUT_8BPP, "CLUT8", },
{ 1 << OMAPFB_COLOR_CLUT_4BPP, "CLUT4", },
{ 1 << OMAPFB_COLOR_CLUT_2BPP, "CLUT2", },
{ 1 << OMAPFB_COLOR_CLUT_1BPP, "CLUT1", },
{ 1 << OMAPFB_COLOR_RGB444, "RGB444", },
{ 1 << OMAPFB_COLOR_YUY422, "YUY422", },
};
/*
* ---------------------------------------------------------------------------
* LCD panel
* ---------------------------------------------------------------------------
*/
extern struct lcd_ctrl omap1_int_ctrl;
extern struct lcd_ctrl omap2_int_ctrl;
extern struct lcd_ctrl hwa742_ctrl;
extern struct lcd_ctrl blizzard_ctrl;
static struct lcd_ctrl *ctrls[] = {
#ifdef CONFIG_ARCH_OMAP1
&omap1_int_ctrl,
#else
&omap2_int_ctrl,
#endif
#ifdef CONFIG_FB_OMAP_LCDC_HWA742
&hwa742_ctrl,
#endif
#ifdef CONFIG_FB_OMAP_LCDC_BLIZZARD
&blizzard_ctrl,
#endif
};
#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL
#ifdef CONFIG_ARCH_OMAP1
extern struct lcd_ctrl_extif omap1_ext_if;
#else
extern struct lcd_ctrl_extif omap2_ext_if;
#endif
#endif
static void omapfb_rqueue_lock(struct omapfb_device *fbdev)
{
mutex_lock(&fbdev->rqueue_mutex);
}
static void omapfb_rqueue_unlock(struct omapfb_device *fbdev)
{
mutex_unlock(&fbdev->rqueue_mutex);
}
/*
* ---------------------------------------------------------------------------
* LCD controller and LCD DMA
* ---------------------------------------------------------------------------
*/
/* Lookup table to map elem size to elem type. */
static const int dma_elem_type[] = {
0,
OMAP_DMA_DATA_TYPE_S8,
OMAP_DMA_DATA_TYPE_S16,
0,
OMAP_DMA_DATA_TYPE_S32,
};
/* Allocate resources needed for LCD controller and LCD DMA operations. Video
* memory is allocated from system memory according to the virtual display
* size, except if a bigger memory size is specified explicitly as a kernel
* parameter.
*/
static int ctrl_init(struct omapfb_device *fbdev)
{
int r;
int i;
/* kernel/module vram parameters override boot tags/board config */
if (def_vram_cnt) {
for (i = 0; i < def_vram_cnt; i++)
fbdev->mem_desc.region[i].size =
PAGE_ALIGN(def_vram[i]);
fbdev->mem_desc.region_cnt = i;
} else {
struct omapfb_platform_data *conf;
conf = fbdev->dev->platform_data;
fbdev->mem_desc = conf->mem_desc;
}
if (!fbdev->mem_desc.region_cnt) {
struct lcd_panel *panel = fbdev->panel;
int def_size;
int bpp = panel->bpp;
/* 12 bpp is packed in 16 bits */
if (bpp == 12)
bpp = 16;
def_size = def_vxres * def_vyres * bpp / 8;
fbdev->mem_desc.region_cnt = 1;
fbdev->mem_desc.region[0].size = PAGE_ALIGN(def_size);
}
r = fbdev->ctrl->init(fbdev, 0, &fbdev->mem_desc);
if (r < 0) {
dev_err(fbdev->dev, "controller initialization failed (%d)\n", r);
return r;
}
#ifdef DEBUG
for (i = 0; i < fbdev->mem_desc.region_cnt; i++) {
dev_dbg(fbdev->dev, "region%d phys %08x virt %p size=%lu\n",
i,
fbdev->mem_desc.region[i].paddr,
fbdev->mem_desc.region[i].vaddr,
fbdev->mem_desc.region[i].size);
}
#endif
return 0;
}
static void ctrl_cleanup(struct omapfb_device *fbdev)
{
fbdev->ctrl->cleanup();
}
/* Must be called with fbdev->rqueue_mutex held. */
static int ctrl_change_mode(struct fb_info *fbi)
{
int r;
unsigned long offset;
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct fb_var_screeninfo *var = &fbi->var;
offset = var->yoffset * fbi->fix.line_length +
var->xoffset * var->bits_per_pixel / 8;
if (fbdev->ctrl->sync)
fbdev->ctrl->sync();
r = fbdev->ctrl->setup_plane(plane->idx, plane->info.channel_out,
offset, var->xres_virtual,
plane->info.pos_x, plane->info.pos_y,
var->xres, var->yres, plane->color_mode);
if (fbdev->ctrl->set_scale != NULL)
r = fbdev->ctrl->set_scale(plane->idx,
var->xres, var->yres,
plane->info.out_width,
plane->info.out_height);
return r;
}
/*
* ---------------------------------------------------------------------------
* fbdev framework callbacks and the ioctl interface
* ---------------------------------------------------------------------------
*/
/* Called each time the omapfb device is opened */
static int omapfb_open(struct fb_info *info, int user)
{
return 0;
}
static void omapfb_sync(struct fb_info *info);
/* Called when the omapfb device is closed. We make sure that any pending
* gfx DMA operations are ended, before we return. */
static int omapfb_release(struct fb_info *info, int user)
{
omapfb_sync(info);
return 0;
}
/* Store a single color palette entry into a pseudo palette or the hardware
* palette if one is available. For now we support only 16bpp and thus store
* the entry only to the pseudo palette.
*/
static int _setcolreg(struct fb_info *info, u_int regno, u_int red, u_int green,
u_int blue, u_int transp, int update_hw_pal)
{
struct omapfb_plane_struct *plane = info->par;
struct omapfb_device *fbdev = plane->fbdev;
struct fb_var_screeninfo *var = &info->var;
int r = 0;
switch (plane->color_mode) {
case OMAPFB_COLOR_YUV422:
case OMAPFB_COLOR_YUV420:
case OMAPFB_COLOR_YUY422:
r = -EINVAL;
break;
case OMAPFB_COLOR_CLUT_8BPP:
case OMAPFB_COLOR_CLUT_4BPP:
case OMAPFB_COLOR_CLUT_2BPP:
case OMAPFB_COLOR_CLUT_1BPP:
if (fbdev->ctrl->setcolreg)
r = fbdev->ctrl->setcolreg(regno, red, green, blue,
transp, update_hw_pal);
/* Fallthrough */
case OMAPFB_COLOR_RGB565:
case OMAPFB_COLOR_RGB444:
if (r != 0)
break;
if (regno < 0) {
r = -EINVAL;
break;
}
if (regno < 16) {
u16 pal;
pal = ((red >> (16 - var->red.length)) <<
var->red.offset) |
((green >> (16 - var->green.length)) <<
var->green.offset) |
(blue >> (16 - var->blue.length));
((u32 *)(info->pseudo_palette))[regno] = pal;
}
break;
default:
BUG();
}
return r;
}
static int omapfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
u_int transp, struct fb_info *info)
{
return _setcolreg(info, regno, red, green, blue, transp, 1);
}
static int omapfb_setcmap(struct fb_cmap *cmap, struct fb_info *info)
{
int count, index, r;
u16 *red, *green, *blue, *transp;
u16 trans = 0xffff;
red = cmap->red;
green = cmap->green;
blue = cmap->blue;
transp = cmap->transp;
index = cmap->start;
for (count = 0; count < cmap->len; count++) {
if (transp)
trans = *transp++;
r = _setcolreg(info, index++, *red++, *green++, *blue++, trans,
count == cmap->len - 1);
if (r != 0)
return r;
}
return 0;
}
static int omapfb_update_full_screen(struct fb_info *fbi);
static int omapfb_blank(int blank, struct fb_info *fbi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
int do_update = 0;
int r = 0;
omapfb_rqueue_lock(fbdev);
switch (blank) {
case VESA_NO_BLANKING:
if (fbdev->state == OMAPFB_SUSPENDED) {
if (fbdev->ctrl->resume)
fbdev->ctrl->resume();
fbdev->panel->enable(fbdev->panel);
fbdev->state = OMAPFB_ACTIVE;
if (fbdev->ctrl->get_update_mode() ==
OMAPFB_MANUAL_UPDATE)
do_update = 1;
}
break;
case VESA_POWERDOWN:
if (fbdev->state == OMAPFB_ACTIVE) {
fbdev->panel->disable(fbdev->panel);
if (fbdev->ctrl->suspend)
fbdev->ctrl->suspend();
fbdev->state = OMAPFB_SUSPENDED;
}
break;
default:
r = -EINVAL;
}
omapfb_rqueue_unlock(fbdev);
if (r == 0 && do_update)
r = omapfb_update_full_screen(fbi);
return r;
}
static void omapfb_sync(struct fb_info *fbi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
omapfb_rqueue_lock(fbdev);
if (fbdev->ctrl->sync)
fbdev->ctrl->sync();
omapfb_rqueue_unlock(fbdev);
}
/* Set fb_info.fix fields and also updates fbdev.
* When calling this fb_info.var must be set up already.
*/
static void set_fb_fix(struct fb_info *fbi)
{
struct fb_fix_screeninfo *fix = &fbi->fix;
struct fb_var_screeninfo *var = &fbi->var;
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_mem_region *rg;
int bpp;
rg = &plane->fbdev->mem_desc.region[plane->idx];
fbi->screen_base = (char __iomem *)rg->vaddr;
fix->smem_start = rg->paddr;
fix->smem_len = rg->size;
fix->type = FB_TYPE_PACKED_PIXELS;
bpp = var->bits_per_pixel;
if (var->nonstd)
fix->visual = FB_VISUAL_PSEUDOCOLOR;
else switch (var->bits_per_pixel) {
case 16:
case 12:
fix->visual = FB_VISUAL_TRUECOLOR;
/* 12bpp is stored in 16 bits */
bpp = 16;
break;
case 1:
case 2:
case 4:
case 8:
fix->visual = FB_VISUAL_PSEUDOCOLOR;
break;
}
fix->accel = FB_ACCEL_OMAP1610;
fix->line_length = var->xres_virtual * bpp / 8;
}
static int set_color_mode(struct omapfb_plane_struct *plane,
struct fb_var_screeninfo *var)
{
switch (var->nonstd) {
case 0:
break;
case OMAPFB_COLOR_YUV422:
var->bits_per_pixel = 16;
plane->color_mode = var->nonstd;
return 0;
case OMAPFB_COLOR_YUV420:
var->bits_per_pixel = 12;
plane->color_mode = var->nonstd;
return 0;
case OMAPFB_COLOR_YUY422:
var->bits_per_pixel = 16;
plane->color_mode = var->nonstd;
return 0;
default:
return -EINVAL;
}
switch (var->bits_per_pixel) {
case 1:
plane->color_mode = OMAPFB_COLOR_CLUT_1BPP;
return 0;
case 2:
plane->color_mode = OMAPFB_COLOR_CLUT_2BPP;
return 0;
case 4:
plane->color_mode = OMAPFB_COLOR_CLUT_4BPP;
return 0;
case 8:
plane->color_mode = OMAPFB_COLOR_CLUT_8BPP;
return 0;
case 12:
var->bits_per_pixel = 16;
plane->color_mode = OMAPFB_COLOR_RGB444;
return 0;
case 16:
plane->color_mode = OMAPFB_COLOR_RGB565;
return 0;
default:
return -EINVAL;
}
}
/* Check the values in var against our capabilities and in case of out of
* bound values try to adjust them.
*/
static int set_fb_var(struct fb_info *fbi,
struct fb_var_screeninfo *var)
{
int bpp;
unsigned long max_frame_size;
unsigned long line_size;
int xres_min, xres_max;
int yres_min, yres_max;
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct lcd_panel *panel = fbdev->panel;
if (set_color_mode(plane, var) < 0)
return -EINVAL;
bpp = var->bits_per_pixel;
if (plane->color_mode == OMAPFB_COLOR_RGB444)
bpp = 16;
switch (var->rotate) {
case 0:
case 180:
xres_min = OMAPFB_PLANE_XRES_MIN;
xres_max = panel->x_res;
yres_min = OMAPFB_PLANE_YRES_MIN;
yres_max = panel->y_res;
if (cpu_is_omap15xx()) {
var->xres = panel->x_res;
var->yres = panel->y_res;
}
break;
case 90:
case 270:
xres_min = OMAPFB_PLANE_YRES_MIN;
xres_max = panel->y_res;
yres_min = OMAPFB_PLANE_XRES_MIN;
yres_max = panel->x_res;
if (cpu_is_omap15xx()) {
var->xres = panel->y_res;
var->yres = panel->x_res;
}
break;
default:
return -EINVAL;
}
if (var->xres < xres_min)
var->xres = xres_min;
if (var->yres < yres_min)
var->yres = yres_min;
if (var->xres > xres_max)
var->xres = xres_max;
if (var->yres > yres_max)
var->yres = yres_max;
if (var->xres_virtual < var->xres)
var->xres_virtual = var->xres;
if (var->yres_virtual < var->yres)
var->yres_virtual = var->yres;
max_frame_size = fbdev->mem_desc.region[plane->idx].size;
line_size = var->xres_virtual * bpp / 8;
if (line_size * var->yres_virtual > max_frame_size) {
/* Try to keep yres_virtual first */
line_size = max_frame_size / var->yres_virtual;
var->xres_virtual = line_size * 8 / bpp;
if (var->xres_virtual < var->xres) {
/* Still doesn't fit. Shrink yres_virtual too */
var->xres_virtual = var->xres;
line_size = var->xres * bpp / 8;
var->yres_virtual = max_frame_size / line_size;
}
/* Recheck this, as the virtual size changed. */
if (var->xres_virtual < var->xres)
var->xres = var->xres_virtual;
if (var->yres_virtual < var->yres)
var->yres = var->yres_virtual;
if (var->xres < xres_min || var->yres < yres_min)
return -EINVAL;
}
if (var->xres + var->xoffset > var->xres_virtual)
var->xoffset = var->xres_virtual - var->xres;
if (var->yres + var->yoffset > var->yres_virtual)
var->yoffset = var->yres_virtual - var->yres;
line_size = var->xres * bpp / 8;
if (plane->color_mode == OMAPFB_COLOR_RGB444) {
var->red.offset = 8; var->red.length = 4;
var->red.msb_right = 0;
var->green.offset = 4; var->green.length = 4;
var->green.msb_right = 0;
var->blue.offset = 0; var->blue.length = 4;
var->blue.msb_right = 0;
} else {
var->red.offset = 11; var->red.length = 5;
var->red.msb_right = 0;
var->green.offset= 5; var->green.length = 6;
var->green.msb_right = 0;
var->blue.offset = 0; var->blue.length = 5;
var->blue.msb_right = 0;
}
var->height = -1;
var->width = -1;
var->grayscale = 0;
/* pixclock in ps, the rest in pixclock */
var->pixclock = 10000000 / (panel->pixel_clock / 100);
var->left_margin = panel->hfp;
var->right_margin = panel->hbp;
var->upper_margin = panel->vfp;
var->lower_margin = panel->vbp;
var->hsync_len = panel->hsw;
var->vsync_len = panel->vsw;
/* TODO: get these from panel->config */
var->vmode = FB_VMODE_NONINTERLACED;
var->sync = 0;
return 0;
}
/* Set rotation (0, 90, 180, 270 degree), and switch to the new mode. */
static void omapfb_rotate(struct fb_info *fbi, int rotate)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
omapfb_rqueue_lock(fbdev);
if (cpu_is_omap15xx() && rotate != fbi->var.rotate) {
struct fb_var_screeninfo *new_var = &fbdev->new_var;
memcpy(new_var, &fbi->var, sizeof(*new_var));
new_var->rotate = rotate;
if (set_fb_var(fbi, new_var) == 0 &&
memcmp(new_var, &fbi->var, sizeof(*new_var))) {
memcpy(&fbi->var, new_var, sizeof(*new_var));
ctrl_change_mode(fbi);
}
}
omapfb_rqueue_unlock(fbdev);
}
/* Set new x,y offsets in the virtual display for the visible area and switch
* to the new mode.
*/
static int omapfb_pan_display(struct fb_var_screeninfo *var,
struct fb_info *fbi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
int r = 0;
omapfb_rqueue_lock(fbdev);
if (var->xoffset != fbi->var.xoffset ||
var->yoffset != fbi->var.yoffset) {
struct fb_var_screeninfo *new_var = &fbdev->new_var;
memcpy(new_var, &fbi->var, sizeof(*new_var));
new_var->xoffset = var->xoffset;
new_var->yoffset = var->yoffset;
if (set_fb_var(fbi, new_var))
r = -EINVAL;
else {
memcpy(&fbi->var, new_var, sizeof(*new_var));
ctrl_change_mode(fbi);
}
}
omapfb_rqueue_unlock(fbdev);
return r;
}
/* Set mirror to vertical axis and switch to the new mode. */
static int omapfb_mirror(struct fb_info *fbi, int mirror)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
int r = 0;
omapfb_rqueue_lock(fbdev);
mirror = mirror ? 1 : 0;
if (cpu_is_omap15xx())
r = -EINVAL;
else if (mirror != plane->info.mirror) {
plane->info.mirror = mirror;
r = ctrl_change_mode(fbi);
}
omapfb_rqueue_unlock(fbdev);
return r;
}
/* Check values in var, try to adjust them in case of out of bound values if
* possible, or return error.
*/
static int omapfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
int r;
omapfb_rqueue_lock(fbdev);
if (fbdev->ctrl->sync != NULL)
fbdev->ctrl->sync();
r = set_fb_var(fbi, var);
omapfb_rqueue_unlock(fbdev);
return r;
}
/* Switch to a new mode. The parameters for it has been check already by
* omapfb_check_var.
*/
static int omapfb_set_par(struct fb_info *fbi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
int r = 0;
omapfb_rqueue_lock(fbdev);
set_fb_fix(fbi);
r = ctrl_change_mode(fbi);
omapfb_rqueue_unlock(fbdev);
return r;
}
int omapfb_update_window_async(struct fb_info *fbi,
struct omapfb_update_window *win,
void (*callback)(void *),
void *callback_data)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct fb_var_screeninfo *var;
var = &fbi->var;
if (win->x >= var->xres || win->y >= var->yres ||
win->out_x > var->xres || win->out_y >= var->yres)
return -EINVAL;
if (!fbdev->ctrl->update_window ||
fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE)
return -ENODEV;
if (win->x + win->width >= var->xres)
win->width = var->xres - win->x;
if (win->y + win->height >= var->yres)
win->height = var->yres - win->y;
/* The out sizes should be cropped to the LCD size */
if (win->out_x + win->out_width > fbdev->panel->x_res)
win->out_width = fbdev->panel->x_res - win->out_x;
if (win->out_y + win->out_height > fbdev->panel->y_res)
win->out_height = fbdev->panel->y_res - win->out_y;
if (!win->width || !win->height || !win->out_width || !win->out_height)
return 0;
return fbdev->ctrl->update_window(fbi, win, callback, callback_data);
}
EXPORT_SYMBOL(omapfb_update_window_async);
static int omapfb_update_win(struct fb_info *fbi,
struct omapfb_update_window *win)
{
struct omapfb_plane_struct *plane = fbi->par;
int ret;
omapfb_rqueue_lock(plane->fbdev);
ret = omapfb_update_window_async(fbi, win, NULL, 0);
omapfb_rqueue_unlock(plane->fbdev);
return ret;
}
static int omapfb_update_full_screen(struct fb_info *fbi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct omapfb_update_window win;
int r;
if (!fbdev->ctrl->update_window ||
fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE)
return -ENODEV;
win.x = 0;
win.y = 0;
win.width = fbi->var.xres;
win.height = fbi->var.yres;
win.out_x = 0;
win.out_y = 0;
win.out_width = fbi->var.xres;
win.out_height = fbi->var.yres;
win.format = 0;
omapfb_rqueue_lock(fbdev);
r = fbdev->ctrl->update_window(fbi, &win, NULL, 0);
omapfb_rqueue_unlock(fbdev);
return r;
}
static int omapfb_setup_plane(struct fb_info *fbi, struct omapfb_plane_info *pi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct lcd_panel *panel = fbdev->panel;
struct omapfb_plane_info old_info;
int r = 0;
if (pi->pos_x + pi->out_width > panel->x_res ||
pi->pos_y + pi->out_height > panel->y_res)
return -EINVAL;
omapfb_rqueue_lock(fbdev);
if (pi->enabled && !fbdev->mem_desc.region[plane->idx].size) {
/* This plane's memory was freed, can't enable it
* until it's reallocated.
*/
r = -EINVAL;
goto out;
}
old_info = plane->info;
plane->info = *pi;
if (pi->enabled) {
r = ctrl_change_mode(fbi);
if (r < 0) {
plane->info = old_info;
goto out;
}
}
r = fbdev->ctrl->enable_plane(plane->idx, pi->enabled);
if (r < 0) {
plane->info = old_info;
goto out;
}
out:
omapfb_rqueue_unlock(fbdev);
return r;
}
static int omapfb_query_plane(struct fb_info *fbi, struct omapfb_plane_info *pi)
{
struct omapfb_plane_struct *plane = fbi->par;
*pi = plane->info;
return 0;
}
static int omapfb_setup_mem(struct fb_info *fbi, struct omapfb_mem_info *mi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct omapfb_mem_region *rg = &fbdev->mem_desc.region[plane->idx];
size_t size;
int r = 0;
if (fbdev->ctrl->setup_mem == NULL)
return -ENODEV;
if (mi->type > OMAPFB_MEMTYPE_MAX)
return -EINVAL;
size = PAGE_ALIGN(mi->size);
omapfb_rqueue_lock(fbdev);
if (plane->info.enabled) {
r = -EBUSY;
goto out;
}
if (rg->size != size || rg->type != mi->type) {
struct fb_var_screeninfo *new_var = &fbdev->new_var;
unsigned long old_size = rg->size;
u8 old_type = rg->type;
unsigned long paddr;
rg->size = size;
rg->type = mi->type;
/* size == 0 is a special case, for which we
* don't check / adjust the screen parameters.
* This isn't a problem since the plane can't
* be reenabled unless its size is > 0.
*/
if (old_size != size && size) {
if (size) {
memcpy(new_var, &fbi->var, sizeof(*new_var));
r = set_fb_var(fbi, new_var);
if (r < 0)
goto out;
}
}
if (fbdev->ctrl->sync)
fbdev->ctrl->sync();
r = fbdev->ctrl->setup_mem(plane->idx, size, mi->type, &paddr);
if (r < 0) {
/* Revert changes. */
rg->size = old_size;
rg->type = old_type;
goto out;
}
rg->paddr = paddr;
if (old_size != size) {
if (size) {
memcpy(&fbi->var, new_var, sizeof(fbi->var));
set_fb_fix(fbi);
} else {
/* Set these explicitly to indicate that the
* plane memory is dealloce'd, the other
* screen parameters in var / fix are invalid.
*/
fbi->fix.smem_start = 0;
fbi->fix.smem_len = 0;
}
}
}
out:
omapfb_rqueue_unlock(fbdev);
return r;
}
static int omapfb_query_mem(struct fb_info *fbi, struct omapfb_mem_info *mi)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct omapfb_mem_region *rg;
rg = &fbdev->mem_desc.region[plane->idx];
memset(mi, 0, sizeof(*mi));
mi->size = rg->size;
mi->type = rg->type;
return 0;
}
static int omapfb_set_color_key(struct omapfb_device *fbdev,
struct omapfb_color_key *ck)
{
int r;
if (!fbdev->ctrl->set_color_key)
return -ENODEV;
omapfb_rqueue_lock(fbdev);
r = fbdev->ctrl->set_color_key(ck);
omapfb_rqueue_unlock(fbdev);
return r;
}
static int omapfb_get_color_key(struct omapfb_device *fbdev,
struct omapfb_color_key *ck)
{
int r;
if (!fbdev->ctrl->get_color_key)
return -ENODEV;
omapfb_rqueue_lock(fbdev);
r = fbdev->ctrl->get_color_key(ck);
omapfb_rqueue_unlock(fbdev);
return r;
}
static struct blocking_notifier_head omapfb_client_list[OMAPFB_PLANE_NUM];
static int notifier_inited;
static void omapfb_init_notifier(void)
{
int i;
for (i = 0; i < OMAPFB_PLANE_NUM; i++)
BLOCKING_INIT_NOTIFIER_HEAD(&omapfb_client_list[i]);
}
int omapfb_register_client(struct omapfb_notifier_block *omapfb_nb,
omapfb_notifier_callback_t callback,
void *callback_data)
{
int r;
if ((unsigned)omapfb_nb->plane_idx > OMAPFB_PLANE_NUM)
return -EINVAL;
if (!notifier_inited) {
omapfb_init_notifier();
notifier_inited = 1;
}
omapfb_nb->nb.notifier_call = (int (*)(struct notifier_block *,
unsigned long, void *))callback;
omapfb_nb->data = callback_data;
r = blocking_notifier_chain_register(
&omapfb_client_list[omapfb_nb->plane_idx],
&omapfb_nb->nb);
if (r)
return r;
if (omapfb_dev != NULL &&
omapfb_dev->ctrl && omapfb_dev->ctrl->bind_client) {
omapfb_dev->ctrl->bind_client(omapfb_nb);
}
return 0;
}
EXPORT_SYMBOL(omapfb_register_client);
int omapfb_unregister_client(struct omapfb_notifier_block *omapfb_nb)
{
return blocking_notifier_chain_unregister(
&omapfb_client_list[omapfb_nb->plane_idx], &omapfb_nb->nb);
}
EXPORT_SYMBOL(omapfb_unregister_client);
void omapfb_notify_clients(struct omapfb_device *fbdev, unsigned long event)
{
int i;
if (!notifier_inited)
/* no client registered yet */
return;
for (i = 0; i < OMAPFB_PLANE_NUM; i++)
blocking_notifier_call_chain(&omapfb_client_list[i], event,
fbdev->fb_info[i]);
}
EXPORT_SYMBOL(omapfb_notify_clients);
static int omapfb_set_update_mode(struct omapfb_device *fbdev,
enum omapfb_update_mode mode)
{
int r;
omapfb_rqueue_lock(fbdev);
r = fbdev->ctrl->set_update_mode(mode);
omapfb_rqueue_unlock(fbdev);
return r;
}
static enum omapfb_update_mode omapfb_get_update_mode(struct omapfb_device *fbdev)
{
int r;
omapfb_rqueue_lock(fbdev);
r = fbdev->ctrl->get_update_mode();
omapfb_rqueue_unlock(fbdev);
return r;
}
static void omapfb_get_caps(struct omapfb_device *fbdev, int plane,
struct omapfb_caps *caps)
{
memset(caps, 0, sizeof(*caps));
fbdev->ctrl->get_caps(plane, caps);
caps->ctrl |= fbdev->panel->get_caps(fbdev->panel);
}
/* For lcd testing */
void omapfb_write_first_pixel(struct omapfb_device *fbdev, u16 pixval)
{
omapfb_rqueue_lock(fbdev);
*(u16 *)fbdev->mem_desc.region[0].vaddr = pixval;
if (fbdev->ctrl->get_update_mode() == OMAPFB_MANUAL_UPDATE) {
struct omapfb_update_window win;
memset(&win, 0, sizeof(win));
win.width = 2;
win.height = 2;
win.out_width = 2;
win.out_height = 2;
fbdev->ctrl->update_window(fbdev->fb_info[0], &win, NULL, 0);
}
omapfb_rqueue_unlock(fbdev);
}
EXPORT_SYMBOL(omapfb_write_first_pixel);
/* Ioctl interface. Part of the kernel mode frame buffer API is duplicated
* here to be accessible by user mode code.
*/
static int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd,
unsigned long arg)
{
struct omapfb_plane_struct *plane = fbi->par;
struct omapfb_device *fbdev = plane->fbdev;
struct fb_ops *ops = fbi->fbops;
union {
struct omapfb_update_window update_window;
struct omapfb_plane_info plane_info;
struct omapfb_mem_info mem_info;
struct omapfb_color_key color_key;
enum omapfb_update_mode update_mode;
struct omapfb_caps caps;
unsigned int mirror;
int plane_out;
int enable_plane;
} p;
int r = 0;
BUG_ON(!ops);
switch (cmd)
{
case OMAPFB_MIRROR:
if (get_user(p.mirror, (int __user *)arg))
r = -EFAULT;
else
omapfb_mirror(fbi, p.mirror);
break;
case OMAPFB_SYNC_GFX:
omapfb_sync(fbi);
break;
case OMAPFB_VSYNC:
break;
case OMAPFB_SET_UPDATE_MODE:
if (get_user(p.update_mode, (int __user *)arg))
r = -EFAULT;
else
r = omapfb_set_update_mode(fbdev, p.update_mode);
break;
case OMAPFB_GET_UPDATE_MODE:
p.update_mode = omapfb_get_update_mode(fbdev);
if (put_user(p.update_mode,
(enum omapfb_update_mode __user *)arg))
r = -EFAULT;
break;
case OMAPFB_UPDATE_WINDOW_OLD:
if (copy_from_user(&p.update_window, (void __user *)arg,
sizeof(struct omapfb_update_window_old)))
r = -EFAULT;
else {
struct omapfb_update_window *u = &p.update_window;
u->out_x = u->x;
u->out_y = u->y;
u->out_width = u->width;
u->out_height = u->height;
memset(u->reserved, 0, sizeof(u->reserved));
r = omapfb_update_win(fbi, u);
}
break;
case OMAPFB_UPDATE_WINDOW:
if (copy_from_user(&p.update_window, (void __user *)arg,
sizeof(p.update_window)))
r = -EFAULT;
else
r = omapfb_update_win(fbi, &p.update_window);
break;
case OMAPFB_SETUP_PLANE:
if (copy_from_user(&p.plane_info, (void __user *)arg,
sizeof(p.plane_info)))
r = -EFAULT;
else
r = omapfb_setup_plane(fbi, &p.plane_info);
break;
case OMAPFB_QUERY_PLANE:
if ((r = omapfb_query_plane(fbi, &p.plane_info)) < 0)
break;
if (copy_to_user((void __user *)arg, &p.plane_info,
sizeof(p.plane_info)))
r = -EFAULT;
break;
case OMAPFB_SETUP_MEM:
if (copy_from_user(&p.mem_info, (void __user *)arg,
sizeof(p.mem_info)))
r = -EFAULT;
else
r = omapfb_setup_mem(fbi, &p.mem_info);
break;
case OMAPFB_QUERY_MEM:
if ((r = omapfb_query_mem(fbi, &p.mem_info)) < 0)
break;
if (copy_to_user((void __user *)arg, &p.mem_info,
sizeof(p.mem_info)))
r = -EFAULT;
break;
case OMAPFB_SET_COLOR_KEY:
if (copy_from_user(&p.color_key, (void __user *)arg,
sizeof(p.color_key)))
r = -EFAULT;
else
r = omapfb_set_color_key(fbdev, &p.color_key);
break;
case OMAPFB_GET_COLOR_KEY:
if ((r = omapfb_get_color_key(fbdev, &p.color_key)) < 0)
break;
if (copy_to_user((void __user *)arg, &p.color_key,
sizeof(p.color_key)))
r = -EFAULT;
break;
case OMAPFB_GET_CAPS:
omapfb_get_caps(fbdev, plane->idx, &p.caps);
if (copy_to_user((void __user *)arg, &p.caps, sizeof(p.caps)))
r = -EFAULT;
break;
case OMAPFB_LCD_TEST:
{
int test_num;
if (get_user(test_num, (int __user *)arg)) {
r = -EFAULT;
break;
}
if (!fbdev->panel->run_test) {
r = -EINVAL;
break;
}
r = fbdev->panel->run_test(fbdev->panel, test_num);
break;
}
case OMAPFB_CTRL_TEST:
{
int test_num;
if (get_user(test_num, (int __user *)arg)) {
r = -EFAULT;
break;
}
if (!fbdev->ctrl->run_test) {
r = -EINVAL;
break;
}
r = fbdev->ctrl->run_test(test_num);
break;
}
default:
r = -EINVAL;
}
return r;
}
static int omapfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
struct omapfb_plane_struct *plane = info->par;
struct omapfb_device *fbdev = plane->fbdev;
int r;
omapfb_rqueue_lock(fbdev);
r = fbdev->ctrl->mmap(info, vma);
omapfb_rqueue_unlock(fbdev);
return r;
}
/* Callback table for the frame buffer framework. Some of these pointers
* will be changed according to the current setting of fb_info->accel_flags.
*/
static struct fb_ops omapfb_ops = {
.owner = THIS_MODULE,
.fb_open = omapfb_open,
.fb_release = omapfb_release,
.fb_setcolreg = omapfb_setcolreg,
.fb_setcmap = omapfb_setcmap,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
.fb_blank = omapfb_blank,
.fb_ioctl = omapfb_ioctl,
.fb_check_var = omapfb_check_var,
.fb_set_par = omapfb_set_par,
.fb_rotate = omapfb_rotate,
.fb_pan_display = omapfb_pan_display,
};
/*
* ---------------------------------------------------------------------------
* Sysfs interface
* ---------------------------------------------------------------------------
*/
/* omapfbX sysfs entries */
static ssize_t omapfb_show_caps_num(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
int plane;
size_t size;
struct omapfb_caps caps;
plane = 0;
size = 0;
while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) {
omapfb_get_caps(fbdev, plane, &caps);
size += snprintf(&buf[size], PAGE_SIZE - size,
"plane#%d %#010x %#010x %#010x\n",
plane, caps.ctrl, caps.plane_color, caps.wnd_color);
plane++;
}
return size;
}
static ssize_t omapfb_show_caps_text(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
int i;
struct omapfb_caps caps;
int plane;
size_t size;
plane = 0;
size = 0;
while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) {
omapfb_get_caps(fbdev, plane, &caps);
size += snprintf(&buf[size], PAGE_SIZE - size,
"plane#%d:\n", plane);
for (i = 0; i < ARRAY_SIZE(ctrl_caps) &&
size < PAGE_SIZE; i++) {
if (ctrl_caps[i].flag & caps.ctrl)
size += snprintf(&buf[size], PAGE_SIZE - size,
" %s\n", ctrl_caps[i].name);
}
size += snprintf(&buf[size], PAGE_SIZE - size,
" plane colors:\n");
for (i = 0; i < ARRAY_SIZE(color_caps) &&
size < PAGE_SIZE; i++) {
if (color_caps[i].flag & caps.plane_color)
size += snprintf(&buf[size], PAGE_SIZE - size,
" %s\n", color_caps[i].name);
}
size += snprintf(&buf[size], PAGE_SIZE - size,
" window colors:\n");
for (i = 0; i < ARRAY_SIZE(color_caps) &&
size < PAGE_SIZE; i++) {
if (color_caps[i].flag & caps.wnd_color)
size += snprintf(&buf[size], PAGE_SIZE - size,
" %s\n", color_caps[i].name);
}
plane++;
}
return size;
}
static DEVICE_ATTR(caps_num, 0444, omapfb_show_caps_num, NULL);
static DEVICE_ATTR(caps_text, 0444, omapfb_show_caps_text, NULL);
/* panel sysfs entries */
static ssize_t omapfb_show_panel_name(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
return snprintf(buf, PAGE_SIZE, "%s\n", fbdev->panel->name);
}
static ssize_t omapfb_show_bklight_level(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
int r;
if (fbdev->panel->get_bklight_level) {
r = snprintf(buf, PAGE_SIZE, "%d\n",
fbdev->panel->get_bklight_level(fbdev->panel));
} else
r = -ENODEV;
return r;
}
static ssize_t omapfb_store_bklight_level(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
int r;
if (fbdev->panel->set_bklight_level) {
unsigned int level;
if (sscanf(buf, "%10d", &level) == 1) {
r = fbdev->panel->set_bklight_level(fbdev->panel, level);
} else
r = -EINVAL;
} else
r = -ENODEV;
return r ? r : size;
}
static ssize_t omapfb_show_bklight_max(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
int r;
if (fbdev->panel->get_bklight_level) {
r = snprintf(buf, PAGE_SIZE, "%d\n",
fbdev->panel->get_bklight_max(fbdev->panel));
} else
r = -ENODEV;
return r;
}
static struct device_attribute dev_attr_panel_name =
__ATTR(name, 0444, omapfb_show_panel_name, NULL);
static DEVICE_ATTR(backlight_level, 0664,
omapfb_show_bklight_level, omapfb_store_bklight_level);
static DEVICE_ATTR(backlight_max, 0444, omapfb_show_bklight_max, NULL);
static struct attribute *panel_attrs[] = {
&dev_attr_panel_name.attr,
&dev_attr_backlight_level.attr,
&dev_attr_backlight_max.attr,
NULL,
};
static struct attribute_group panel_attr_grp = {
.name = "panel",
.attrs = panel_attrs,
};
/* ctrl sysfs entries */
static ssize_t omapfb_show_ctrl_name(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data;
return snprintf(buf, PAGE_SIZE, "%s\n", fbdev->ctrl->name);
}
static struct device_attribute dev_attr_ctrl_name =
__ATTR(name, 0444, omapfb_show_ctrl_name, NULL);
static struct attribute *ctrl_attrs[] = {
&dev_attr_ctrl_name.attr,
NULL,
};
static struct attribute_group ctrl_attr_grp = {
.name = "ctrl",
.attrs = ctrl_attrs,
};
static int omapfb_register_sysfs(struct omapfb_device *fbdev)
{
int r;
if ((r = device_create_file(fbdev->dev, &dev_attr_caps_num)))
goto fail0;
if ((r = device_create_file(fbdev->dev, &dev_attr_caps_text)))
goto fail1;
if ((r = sysfs_create_group(&fbdev->dev->kobj, &panel_attr_grp)))
goto fail2;
if ((r = sysfs_create_group(&fbdev->dev->kobj, &ctrl_attr_grp)))
goto fail3;
return 0;
fail3:
sysfs_remove_group(&fbdev->dev->kobj, &panel_attr_grp);
fail2:
device_remove_file(fbdev->dev, &dev_attr_caps_text);
fail1:
device_remove_file(fbdev->dev, &dev_attr_caps_num);
fail0:
dev_err(fbdev->dev, "unable to register sysfs interface\n");
return r;
}
static void omapfb_unregister_sysfs(struct omapfb_device *fbdev)
{
sysfs_remove_group(&fbdev->dev->kobj, &ctrl_attr_grp);
sysfs_remove_group(&fbdev->dev->kobj, &panel_attr_grp);
device_remove_file(fbdev->dev, &dev_attr_caps_num);
device_remove_file(fbdev->dev, &dev_attr_caps_text);
}
/*
* ---------------------------------------------------------------------------
* LDM callbacks
* ---------------------------------------------------------------------------
*/
/* Initialize system fb_info object and set the default video mode.
* The frame buffer memory already allocated by lcddma_init
*/
static int fbinfo_init(struct omapfb_device *fbdev, struct fb_info *info)
{
struct fb_var_screeninfo *var = &info->var;
struct fb_fix_screeninfo *fix = &info->fix;
int r = 0;
info->fbops = &omapfb_ops;
info->flags = FBINFO_FLAG_DEFAULT;
strncpy(fix->id, MODULE_NAME, sizeof(fix->id));
info->pseudo_palette = fbdev->pseudo_palette;
var->accel_flags = def_accel ? FB_ACCELF_TEXT : 0;
var->xres = def_vxres;
var->yres = def_vyres;
var->xres_virtual = def_vxres;
var->yres_virtual = def_vyres;
var->rotate = def_rotate;
var->bits_per_pixel = fbdev->panel->bpp;
set_fb_var(info, var);
set_fb_fix(info);
r = fb_alloc_cmap(&info->cmap, 16, 0);
if (r != 0)
dev_err(fbdev->dev, "unable to allocate color map memory\n");
return r;
}
/* Release the fb_info object */
static void fbinfo_cleanup(struct omapfb_device *fbdev, struct fb_info *fbi)
{
fb_dealloc_cmap(&fbi->cmap);
}
static void planes_cleanup(struct omapfb_device *fbdev)
{
int i;
for (i = 0; i < fbdev->mem_desc.region_cnt; i++) {
if (fbdev->fb_info[i] == NULL)
break;
fbinfo_cleanup(fbdev, fbdev->fb_info[i]);
framebuffer_release(fbdev->fb_info[i]);
}
}
static int planes_init(struct omapfb_device *fbdev)
{
struct fb_info *fbi;
int i;
int r;
for (i = 0; i < fbdev->mem_desc.region_cnt; i++) {
struct omapfb_plane_struct *plane;
fbi = framebuffer_alloc(sizeof(struct omapfb_plane_struct),
fbdev->dev);
if (fbi == NULL) {
dev_err(fbdev->dev,
"unable to allocate memory for plane info\n");
planes_cleanup(fbdev);
return -ENOMEM;
}
plane = fbi->par;
plane->idx = i;
plane->fbdev = fbdev;
plane->info.mirror = def_mirror;
fbdev->fb_info[i] = fbi;
if ((r = fbinfo_init(fbdev, fbi)) < 0) {
framebuffer_release(fbi);
planes_cleanup(fbdev);
return r;
}
plane->info.out_width = fbi->var.xres;
plane->info.out_height = fbi->var.yres;
}
return 0;
}
/* Free driver resources. Can be called to rollback an aborted initialization
* sequence.
*/
static void omapfb_free_resources(struct omapfb_device *fbdev, int state)
{
int i;
switch (state) {
case OMAPFB_ACTIVE:
for (i = 0; i < fbdev->mem_desc.region_cnt; i++)
unregister_framebuffer(fbdev->fb_info[i]);
case 7:
omapfb_unregister_sysfs(fbdev);
case 6:
fbdev->panel->disable(fbdev->panel);
case 5:
omapfb_set_update_mode(fbdev, OMAPFB_UPDATE_DISABLED);
case 4:
planes_cleanup(fbdev);
case 3:
ctrl_cleanup(fbdev);
case 2:
fbdev->panel->cleanup(fbdev->panel);
case 1:
dev_set_drvdata(fbdev->dev, NULL);
kfree(fbdev);
case 0:
/* nothing to free */
break;
default:
BUG();
}
}
static int omapfb_find_ctrl(struct omapfb_device *fbdev)
{
struct omapfb_platform_data *conf;
char name[17];
int i;
conf = fbdev->dev->platform_data;
fbdev->ctrl = NULL;
strncpy(name, conf->lcd.ctrl_name, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0';
if (strcmp(name, "internal") == 0) {
fbdev->ctrl = fbdev->int_ctrl;
return 0;
}
for (i = 0; i < ARRAY_SIZE(ctrls); i++) {
dev_dbg(fbdev->dev, "ctrl %s\n", ctrls[i]->name);
if (strcmp(ctrls[i]->name, name) == 0) {
fbdev->ctrl = ctrls[i];
break;
}
}
if (fbdev->ctrl == NULL) {
dev_dbg(fbdev->dev, "ctrl %s not supported\n", name);
return -1;
}
return 0;
}
static void check_required_callbacks(struct omapfb_device *fbdev)
{
#define _C(x) (fbdev->ctrl->x != NULL)
#define _P(x) (fbdev->panel->x != NULL)
BUG_ON(fbdev->ctrl == NULL || fbdev->panel == NULL);
BUG_ON(!(_C(init) && _C(cleanup) && _C(get_caps) &&
_C(set_update_mode) && _C(setup_plane) && _C(enable_plane) &&
_P(init) && _P(cleanup) && _P(enable) && _P(disable) &&
_P(get_caps)));
#undef _P
#undef _C
}
/* Called by LDM binding to probe and attach a new device.
* Initialization sequence:
* 1. allocate system omapfb_device structure
* 2. select controller type according to platform configuration
* init LCD panel
* 3. init LCD controller and LCD DMA
* 4. init system fb_info structure for all planes
* 5. setup video mode for first plane and enable it
* 6. enable LCD panel
* 7. register sysfs attributes
* OMAPFB_ACTIVE: register system fb_info structure for all planes
*/
static int omapfb_do_probe(struct platform_device *pdev, struct lcd_panel *panel)
{
struct omapfb_device *fbdev = NULL;
int init_state;
unsigned long phz, hhz, vhz;
unsigned long vram;
int i;
int r = 0;
init_state = 0;
if (pdev->num_resources != 0) {
dev_err(&pdev->dev, "probed for an unknown device\n");
r = -ENODEV;
goto cleanup;
}
if (pdev->dev.platform_data == NULL) {
dev_err(&pdev->dev, "missing platform data\n");
r = -ENOENT;
goto cleanup;
}
fbdev = kzalloc(sizeof(struct omapfb_device), GFP_KERNEL);
if (fbdev == NULL) {
dev_err(&pdev->dev,
"unable to allocate memory for device info\n");
r = -ENOMEM;
goto cleanup;
}
init_state++;
fbdev->dev = &pdev->dev;
fbdev->panel = panel;
platform_set_drvdata(pdev, fbdev);
mutex_init(&fbdev->rqueue_mutex);
#ifdef CONFIG_ARCH_OMAP1
fbdev->int_ctrl = &omap1_int_ctrl;
#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL
fbdev->ext_if = &omap1_ext_if;
#endif
#else /* OMAP2 */
fbdev->int_ctrl = &omap2_int_ctrl;
#ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL
fbdev->ext_if = &omap2_ext_if;
#endif
#endif
if (omapfb_find_ctrl(fbdev) < 0) {
dev_err(fbdev->dev,
"LCD controller not found, board not supported\n");
r = -ENODEV;
goto cleanup;
}
r = fbdev->panel->init(fbdev->panel, fbdev);
if (r)
goto cleanup;
pr_info("omapfb: configured for panel %s\n", fbdev->panel->name);
def_vxres = def_vxres ? : fbdev->panel->x_res;
def_vyres = def_vyres ? : fbdev->panel->y_res;
init_state++;
r = ctrl_init(fbdev);
if (r)
goto cleanup;
if (fbdev->ctrl->mmap != NULL)
omapfb_ops.fb_mmap = omapfb_mmap;
init_state++;
check_required_callbacks(fbdev);
r = planes_init(fbdev);
if (r)
goto cleanup;
init_state++;
#ifdef CONFIG_FB_OMAP_DMA_TUNE
/* Set DMA priority for EMIFF access to highest */
if (cpu_class_is_omap1())
omap_set_dma_priority(0, OMAP_DMA_PORT_EMIFF, 15);
#endif
r = ctrl_change_mode(fbdev->fb_info[0]);
if (r) {
dev_err(fbdev->dev, "mode setting failed\n");
goto cleanup;
}
/* GFX plane is enabled by default */
r = fbdev->ctrl->enable_plane(OMAPFB_PLANE_GFX, 1);
if (r)
goto cleanup;
omapfb_set_update_mode(fbdev, manual_update ?
OMAPFB_MANUAL_UPDATE : OMAPFB_AUTO_UPDATE);
init_state++;
r = fbdev->panel->enable(fbdev->panel);
if (r)
goto cleanup;
init_state++;
r = omapfb_register_sysfs(fbdev);
if (r)
goto cleanup;
init_state++;
vram = 0;
for (i = 0; i < fbdev->mem_desc.region_cnt; i++) {
r = register_framebuffer(fbdev->fb_info[i]);
if (r != 0) {
dev_err(fbdev->dev,
"registering framebuffer %d failed\n", i);
goto cleanup;
}
vram += fbdev->mem_desc.region[i].size;
}
fbdev->state = OMAPFB_ACTIVE;
panel = fbdev->panel;
phz = panel->pixel_clock * 1000;
hhz = phz * 10 / (panel->hfp + panel->x_res + panel->hbp + panel->hsw);
vhz = hhz / (panel->vfp + panel->y_res + panel->vbp + panel->vsw);
omapfb_dev = fbdev;
pr_info("omapfb: Framebuffer initialized. Total vram %lu planes %d\n",
vram, fbdev->mem_desc.region_cnt);
pr_info("omapfb: Pixclock %lu kHz hfreq %lu.%lu kHz "
"vfreq %lu.%lu Hz\n",
phz / 1000, hhz / 10000, hhz % 10, vhz / 10, vhz % 10);
return 0;
cleanup:
omapfb_free_resources(fbdev, init_state);
return r;
}
static int omapfb_probe(struct platform_device *pdev)
{
BUG_ON(fbdev_pdev != NULL);
/* Delay actual initialization until the LCD is registered */
fbdev_pdev = pdev;
if (fbdev_panel != NULL)
omapfb_do_probe(fbdev_pdev, fbdev_panel);
return 0;
}
void omapfb_register_panel(struct lcd_panel *panel)
{
BUG_ON(fbdev_panel != NULL);
fbdev_panel = panel;
if (fbdev_pdev != NULL)
omapfb_do_probe(fbdev_pdev, fbdev_panel);
}
/* Called when the device is being detached from the driver */
static int omapfb_remove(struct platform_device *pdev)
{
struct omapfb_device *fbdev = platform_get_drvdata(pdev);
enum omapfb_state saved_state = fbdev->state;
/* FIXME: wait till completion of pending events */
fbdev->state = OMAPFB_DISABLED;
omapfb_free_resources(fbdev, saved_state);
return 0;
}
/* PM suspend */
static int omapfb_suspend(struct platform_device *pdev, pm_message_t mesg)
{
struct omapfb_device *fbdev = platform_get_drvdata(pdev);
omapfb_blank(VESA_POWERDOWN, fbdev->fb_info[0]);
return 0;
}
/* PM resume */
static int omapfb_resume(struct platform_device *pdev)
{
struct omapfb_device *fbdev = platform_get_drvdata(pdev);
omapfb_blank(VESA_NO_BLANKING, fbdev->fb_info[0]);
return 0;
}
static struct platform_driver omapfb_driver = {
.probe = omapfb_probe,
.remove = omapfb_remove,
.suspend = omapfb_suspend,
.resume = omapfb_resume,
.driver = {
.name = MODULE_NAME,
.owner = THIS_MODULE,
},
};
#ifndef MODULE
/* Process kernel command line parameters */
static int __init omapfb_setup(char *options)
{
char *this_opt = NULL;
int r = 0;
pr_debug("omapfb: options %s\n", options);
if (!options || !*options)
return 0;
while (!r && (this_opt = strsep(&options, ",")) != NULL) {
if (!strncmp(this_opt, "accel", 5))
def_accel = 1;
else if (!strncmp(this_opt, "vram:", 5)) {
char *suffix;
unsigned long vram;
vram = (simple_strtoul(this_opt + 5, &suffix, 0));
switch (suffix[0]) {
case '\0':
break;
case 'm':
case 'M':
vram *= 1024;
/* Fall through */
case 'k':
case 'K':
vram *= 1024;
break;
default:
pr_debug("omapfb: invalid vram suffix %c\n",
suffix[0]);
r = -1;
}
def_vram[def_vram_cnt++] = vram;
}
else if (!strncmp(this_opt, "vxres:", 6))
def_vxres = simple_strtoul(this_opt + 6, NULL, 0);
else if (!strncmp(this_opt, "vyres:", 6))
def_vyres = simple_strtoul(this_opt + 6, NULL, 0);
else if (!strncmp(this_opt, "rotate:", 7))
def_rotate = (simple_strtoul(this_opt + 7, NULL, 0));
else if (!strncmp(this_opt, "mirror:", 7))
def_mirror = (simple_strtoul(this_opt + 7, NULL, 0));
else if (!strncmp(this_opt, "manual_update", 13))
manual_update = 1;
else {
pr_debug("omapfb: invalid option\n");
r = -1;
}
}
return r;
}
#endif
/* Register both the driver and the device */
static int __init omapfb_init(void)
{
#ifndef MODULE
char *option;
if (fb_get_options("omapfb", &option))
return -ENODEV;
omapfb_setup(option);
#endif
/* Register the driver with LDM */
if (platform_driver_register(&omapfb_driver)) {
pr_debug("failed to register omapfb driver\n");
return -ENODEV;
}
return 0;
}
static void __exit omapfb_cleanup(void)
{
platform_driver_unregister(&omapfb_driver);
}
module_param_named(accel, def_accel, uint, 0664);
module_param_array_named(vram, def_vram, ulong, &def_vram_cnt, 0664);
module_param_named(vxres, def_vxres, long, 0664);
module_param_named(vyres, def_vyres, long, 0664);
module_param_named(rotate, def_rotate, uint, 0664);
module_param_named(mirror, def_mirror, uint, 0664);
module_param_named(manual_update, manual_update, bool, 0664);
module_init(omapfb_init);
module_exit(omapfb_cleanup);
MODULE_DESCRIPTION("TI OMAP framebuffer driver");
MODULE_AUTHOR("Imre Deak <imre.deak@nokia.com>");
MODULE_LICENSE("GPL");
/*
* File: drivers/video/omap/omap2/rfbi.c
*
* OMAP2 Remote Frame Buffer Interface support
*
* Copyright (C) 2005 Nokia Corporation
* Author: Juha Yrjl <juha.yrjola@nokia.com>
* 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/delay.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/arch/omapfb.h>
#include "dispc.h"
/* To work around an RFBI transfer rate limitation */
#define OMAP_RFBI_RATE_LIMIT 1
#define RFBI_BASE 0x48050800
#define RFBI_REVISION 0x0000
#define RFBI_SYSCONFIG 0x0010
#define RFBI_SYSSTATUS 0x0014
#define RFBI_CONTROL 0x0040
#define RFBI_PIXEL_CNT 0x0044
#define RFBI_LINE_NUMBER 0x0048
#define RFBI_CMD 0x004c
#define RFBI_PARAM 0x0050
#define RFBI_DATA 0x0054
#define RFBI_READ 0x0058
#define RFBI_STATUS 0x005c
#define RFBI_CONFIG0 0x0060
#define RFBI_ONOFF_TIME0 0x0064
#define RFBI_CYCLE_TIME0 0x0068
#define RFBI_DATA_CYCLE1_0 0x006c
#define RFBI_DATA_CYCLE2_0 0x0070
#define RFBI_DATA_CYCLE3_0 0x0074
#define RFBI_VSYNC_WIDTH 0x0090
#define RFBI_HSYNC_WIDTH 0x0094
#define DISPC_BASE 0x48050400
#define DISPC_CONTROL 0x0040
static struct {
u32 base;
void (*lcdc_callback)(void *data);
void *lcdc_callback_data;
unsigned long l4_khz;
int bits_per_cycle;
struct omapfb_device *fbdev;
struct clk *dss_ick;
struct clk *dss1_fck;
unsigned tearsync_pin_cnt;
unsigned tearsync_mode;
} rfbi;
static inline void rfbi_write_reg(int idx, u32 val)
{
__raw_writel(val, rfbi.base + idx);
}
static inline u32 rfbi_read_reg(int idx)
{
return __raw_readl(rfbi.base + idx);
}
static int rfbi_get_clocks(void)
{
if (IS_ERR((rfbi.dss_ick = clk_get(rfbi.fbdev->dev, "dss_ick")))) {
dev_err(rfbi.fbdev->dev, "can't get dss_ick");
return PTR_ERR(rfbi.dss_ick);
}
if (IS_ERR((rfbi.dss1_fck = clk_get(rfbi.fbdev->dev, "dss1_fck")))) {
dev_err(rfbi.fbdev->dev, "can't get dss1_fck");
clk_put(rfbi.dss_ick);
return PTR_ERR(rfbi.dss1_fck);
}
return 0;
}
static void rfbi_put_clocks(void)
{
clk_put(rfbi.dss1_fck);
clk_put(rfbi.dss_ick);
}
static void rfbi_enable_clocks(int enable)
{
if (enable) {
clk_enable(rfbi.dss_ick);
clk_enable(rfbi.dss1_fck);
} else {
clk_disable(rfbi.dss1_fck);
clk_disable(rfbi.dss_ick);
}
}
#ifdef VERBOSE
static void rfbi_print_timings(void)
{
u32 l;
u32 time;
l = rfbi_read_reg(RFBI_CONFIG0);
time = 1000000000 / rfbi.l4_khz;
if (l & (1 << 4))
time *= 2;
dev_dbg(rfbi.fbdev->dev, "Tick time %u ps\n", time);
l = rfbi_read_reg(RFBI_ONOFF_TIME0);
dev_dbg(rfbi.fbdev->dev,
"CSONTIME %d, CSOFFTIME %d, WEONTIME %d, WEOFFTIME %d, "
"REONTIME %d, REOFFTIME %d\n",
l & 0x0f, (l >> 4) & 0x3f, (l >> 10) & 0x0f, (l >> 14) & 0x3f,
(l >> 20) & 0x0f, (l >> 24) & 0x3f);
l = rfbi_read_reg(RFBI_CYCLE_TIME0);
dev_dbg(rfbi.fbdev->dev,
"WECYCLETIME %d, RECYCLETIME %d, CSPULSEWIDTH %d, "
"ACCESSTIME %d\n",
(l & 0x3f), (l >> 6) & 0x3f, (l >> 12) & 0x3f,
(l >> 22) & 0x3f);
}
#else
static void rfbi_print_timings(void) {}
#endif
static void rfbi_set_timings(const struct extif_timings *t)
{
u32 l;
BUG_ON(!t->converted);
rfbi_enable_clocks(1);
rfbi_write_reg(RFBI_ONOFF_TIME0, t->tim[0]);
rfbi_write_reg(RFBI_CYCLE_TIME0, t->tim[1]);
l = rfbi_read_reg(RFBI_CONFIG0);
l &= ~(1 << 4);
l |= (t->tim[2] ? 1 : 0) << 4;
rfbi_write_reg(RFBI_CONFIG0, l);
rfbi_print_timings();
rfbi_enable_clocks(0);
}
static void rfbi_get_clk_info(u32 *clk_period, u32 *max_clk_div)
{
*clk_period = 1000000000 / rfbi.l4_khz;
*max_clk_div = 2;
}
static int ps_to_rfbi_ticks(int time, int div)
{
unsigned long tick_ps;
int ret;
/* Calculate in picosecs to yield more exact results */
tick_ps = 1000000000 / (rfbi.l4_khz) * div;
ret = (time + tick_ps - 1) / tick_ps;
return ret;
}
#ifdef OMAP_RFBI_RATE_LIMIT
static unsigned long rfbi_get_max_tx_rate(void)
{
unsigned long l4_rate, dss1_rate;
int min_l4_ticks = 0;
int i;
/* According to TI this can't be calculated so make the
* adjustments for a couple of known frequencies and warn for
* others.
*/
static const struct {
unsigned long l4_clk; /* HZ */
unsigned long dss1_clk; /* HZ */
unsigned long min_l4_ticks;
} ftab[] = {
{ 55, 132, 7, }, /* 7.86 MPix/s */
{ 110, 110, 12, }, /* 9.16 MPix/s */
{ 110, 132, 10, }, /* 11 Mpix/s */
{ 120, 120, 10, }, /* 12 Mpix/s */
{ 133, 133, 10, }, /* 13.3 Mpix/s */
};
l4_rate = rfbi.l4_khz / 1000;
dss1_rate = clk_get_rate(rfbi.dss1_fck) / 1000000;
for (i = 0; i < ARRAY_SIZE(ftab); i++) {
/* Use a window instead of an exact match, to account
* for different DPLL multiplier / divider pairs.
*/
if (abs(ftab[i].l4_clk - l4_rate) < 3 &&
abs(ftab[i].dss1_clk - dss1_rate) < 3) {
min_l4_ticks = ftab[i].min_l4_ticks;
break;
}
}
if (i == ARRAY_SIZE(ftab)) {
/* Can't be sure, return anyway the maximum not
* rate-limited. This might cause a problem only for the
* tearing synchronisation.
*/
dev_err(rfbi.fbdev->dev,
"can't determine maximum RFBI transfer rate\n");
return rfbi.l4_khz * 1000;
}
return rfbi.l4_khz * 1000 / min_l4_ticks;
}
#else
static int rfbi_get_max_tx_rate(void)
{
return rfbi.l4_khz * 1000;
}
#endif
static int rfbi_convert_timings(struct extif_timings *t)
{
u32 l;
int reon, reoff, weon, weoff, cson, csoff, cs_pulse;
int actim, recyc, wecyc;
int div = t->clk_div;
if (div <= 0 || div > 2)
return -1;
/* Make sure that after conversion it still holds that:
* weoff > weon, reoff > reon, recyc >= reoff, wecyc >= weoff,
* csoff > cson, csoff >= max(weoff, reoff), actim > reon
*/
weon = ps_to_rfbi_ticks(t->we_on_time, div);
weoff = ps_to_rfbi_ticks(t->we_off_time, div);
if (weoff <= weon)
weoff = weon + 1;
if (weon > 0x0f)
return -1;
if (weoff > 0x3f)
return -1;
reon = ps_to_rfbi_ticks(t->re_on_time, div);
reoff = ps_to_rfbi_ticks(t->re_off_time, div);
if (reoff <= reon)
reoff = reon + 1;
if (reon > 0x0f)
return -1;
if (reoff > 0x3f)
return -1;
cson = ps_to_rfbi_ticks(t->cs_on_time, div);
csoff = ps_to_rfbi_ticks(t->cs_off_time, div);
if (csoff <= cson)
csoff = cson + 1;
if (csoff < max(weoff, reoff))
csoff = max(weoff, reoff);
if (cson > 0x0f)
return -1;
if (csoff > 0x3f)
return -1;
l = cson;
l |= csoff << 4;
l |= weon << 10;
l |= weoff << 14;
l |= reon << 20;
l |= reoff << 24;
t->tim[0] = l;
actim = ps_to_rfbi_ticks(t->access_time, div);
if (actim <= reon)
actim = reon + 1;
if (actim > 0x3f)
return -1;
wecyc = ps_to_rfbi_ticks(t->we_cycle_time, div);
if (wecyc < weoff)
wecyc = weoff;
if (wecyc > 0x3f)
return -1;
recyc = ps_to_rfbi_ticks(t->re_cycle_time, div);
if (recyc < reoff)
recyc = reoff;
if (recyc > 0x3f)
return -1;
cs_pulse = ps_to_rfbi_ticks(t->cs_pulse_width, div);
if (cs_pulse > 0x3f)
return -1;
l = wecyc;
l |= recyc << 6;
l |= cs_pulse << 12;
l |= actim << 22;
t->tim[1] = l;
t->tim[2] = div - 1;
t->converted = 1;
return 0;
}
static int rfbi_setup_tearsync(unsigned pin_cnt,
unsigned hs_pulse_time, unsigned vs_pulse_time,
int hs_pol_inv, int vs_pol_inv, int extif_div)
{
int hs, vs;
int min;
u32 l;
if (pin_cnt != 1 && pin_cnt != 2)
return -EINVAL;
hs = ps_to_rfbi_ticks(hs_pulse_time, 1);
vs = ps_to_rfbi_ticks(vs_pulse_time, 1);
if (hs < 2)
return -EDOM;
if (pin_cnt == 2)
min = 2;
else
min = 4;
if (vs < min)
return -EDOM;
if (vs == hs)
return -EINVAL;
rfbi.tearsync_pin_cnt = pin_cnt;
dev_dbg(rfbi.fbdev->dev,
"setup_tearsync: pins %d hs %d vs %d hs_inv %d vs_inv %d\n",
pin_cnt, hs, vs, hs_pol_inv, vs_pol_inv);
rfbi_enable_clocks(1);
rfbi_write_reg(RFBI_HSYNC_WIDTH, hs);
rfbi_write_reg(RFBI_VSYNC_WIDTH, vs);
l = rfbi_read_reg(RFBI_CONFIG0);
if (hs_pol_inv)
l &= ~(1 << 21);
else
l |= 1 << 21;
if (vs_pol_inv)
l &= ~(1 << 20);
else
l |= 1 << 20;
rfbi_enable_clocks(0);
return 0;
}
static int rfbi_enable_tearsync(int enable, unsigned line)
{
u32 l;
dev_dbg(rfbi.fbdev->dev, "tearsync %d line %d mode %d\n",
enable, line, rfbi.tearsync_mode);
if (line > (1 << 11) - 1)
return -EINVAL;
rfbi_enable_clocks(1);
l = rfbi_read_reg(RFBI_CONFIG0);
l &= ~(0x3 << 2);
if (enable) {
rfbi.tearsync_mode = rfbi.tearsync_pin_cnt;
l |= rfbi.tearsync_mode << 2;
} else
rfbi.tearsync_mode = 0;
rfbi_write_reg(RFBI_CONFIG0, l);
rfbi_write_reg(RFBI_LINE_NUMBER, line);
rfbi_enable_clocks(0);
return 0;
}
static void rfbi_write_command(const void *buf, unsigned int len)
{
rfbi_enable_clocks(1);
if (rfbi.bits_per_cycle == 16) {
const u16 *w = buf;
BUG_ON(len & 1);
for (; len; len -= 2)
rfbi_write_reg(RFBI_CMD, *w++);
} else {
const u8 *b = buf;
BUG_ON(rfbi.bits_per_cycle != 8);
for (; len; len--)
rfbi_write_reg(RFBI_CMD, *b++);
}
rfbi_enable_clocks(0);
}
static void rfbi_read_data(void *buf, unsigned int len)
{
rfbi_enable_clocks(1);
if (rfbi.bits_per_cycle == 16) {
u16 *w = buf;
BUG_ON(len & ~1);
for (; len; len -= 2) {
rfbi_write_reg(RFBI_READ, 0);
*w++ = rfbi_read_reg(RFBI_READ);
}
} else {
u8 *b = buf;
BUG_ON(rfbi.bits_per_cycle != 8);
for (; len; len--) {
rfbi_write_reg(RFBI_READ, 0);
*b++ = rfbi_read_reg(RFBI_READ);
}
}
rfbi_enable_clocks(0);
}
static void rfbi_write_data(const void *buf, unsigned int len)
{
rfbi_enable_clocks(1);
if (rfbi.bits_per_cycle == 16) {
const u16 *w = buf;
BUG_ON(len & 1);
for (; len; len -= 2)
rfbi_write_reg(RFBI_PARAM, *w++);
} else {
const u8 *b = buf;
BUG_ON(rfbi.bits_per_cycle != 8);
for (; len; len--)
rfbi_write_reg(RFBI_PARAM, *b++);
}
rfbi_enable_clocks(0);
}
static void rfbi_transfer_area(int width, int height,
void (callback)(void * data), void *data)
{
u32 w;
BUG_ON(callback == NULL);
rfbi_enable_clocks(1);
omap_dispc_set_lcd_size(width, height);
rfbi.lcdc_callback = callback;
rfbi.lcdc_callback_data = data;
rfbi_write_reg(RFBI_PIXEL_CNT, width * height);
w = rfbi_read_reg(RFBI_CONTROL);
w |= 1; /* enable */
if (!rfbi.tearsync_mode)
w |= 1 << 4; /* internal trigger, reset by HW */
rfbi_write_reg(RFBI_CONTROL, w);
omap_dispc_enable_lcd_out(1);
}
static inline void _stop_transfer(void)
{
u32 w;
w = rfbi_read_reg(RFBI_CONTROL);
rfbi_write_reg(RFBI_CONTROL, w & ~(1 << 0));
rfbi_enable_clocks(0);
}
static void rfbi_dma_callback(void *data)
{
_stop_transfer();
rfbi.lcdc_callback(rfbi.lcdc_callback_data);
}
static void rfbi_set_bits_per_cycle(int bpc)
{
u32 l;
rfbi_enable_clocks(1);
l = rfbi_read_reg(RFBI_CONFIG0);
l &= ~(0x03 << 0);
switch (bpc)
{
case 8:
break;
case 16:
l |= 3;
break;
default:
BUG();
}
rfbi_write_reg(RFBI_CONFIG0, l);
rfbi.bits_per_cycle = bpc;
rfbi_enable_clocks(0);
}
static int rfbi_init(struct omapfb_device *fbdev)
{
u32 l;
int r;
rfbi.fbdev = fbdev;
rfbi.base = io_p2v(RFBI_BASE);
if ((r = rfbi_get_clocks()) < 0)
return r;
rfbi_enable_clocks(1);
rfbi.l4_khz = clk_get_rate(rfbi.dss_ick) / 1000;
/* Reset */
rfbi_write_reg(RFBI_SYSCONFIG, 1 << 1);
while (!(rfbi_read_reg(RFBI_SYSSTATUS) & (1 << 0)));
l = rfbi_read_reg(RFBI_SYSCONFIG);
/* Enable autoidle and smart-idle */
l |= (1 << 0) | (2 << 3);
rfbi_write_reg(RFBI_SYSCONFIG, l);
/* 16-bit interface, ITE trigger mode, 16-bit data */
l = (0x03 << 0) | (0x00 << 2) | (0x01 << 5) | (0x02 << 7);
l |= (0 << 9) | (1 << 20) | (1 << 21);
rfbi_write_reg(RFBI_CONFIG0, l);
rfbi_write_reg(RFBI_DATA_CYCLE1_0, 0x00000010);
l = rfbi_read_reg(RFBI_CONTROL);
/* Select CS0, clear bypass mode */
l = (0x01 << 2);
rfbi_write_reg(RFBI_CONTROL, l);
if ((r = omap_dispc_request_irq(rfbi_dma_callback, NULL)) < 0) {
dev_err(fbdev->dev, "can't get DISPC irq\n");
rfbi_enable_clocks(0);
return r;
}
l = rfbi_read_reg(RFBI_REVISION);
pr_info("omapfb: RFBI version %d.%d initialized\n",
(l >> 4) & 0x0f, l & 0x0f);
rfbi_enable_clocks(0);
return 0;
}
static void rfbi_cleanup(void)
{
omap_dispc_free_irq();
rfbi_put_clocks();
}
const struct lcd_ctrl_extif omap2_ext_if = {
.init = rfbi_init,
.cleanup = rfbi_cleanup,
.get_clk_info = rfbi_get_clk_info,
.get_max_tx_rate = rfbi_get_max_tx_rate,
.set_bits_per_cycle = rfbi_set_bits_per_cycle,
.convert_timings = rfbi_convert_timings,
.set_timings = rfbi_set_timings,
.write_command = rfbi_write_command,
.read_data = rfbi_read_data,
.write_data = rfbi_write_data,
.transfer_area = rfbi_transfer_area,
.setup_tearsync = rfbi_setup_tearsync,
.enable_tearsync = rfbi_enable_tearsync,
.max_transmit_size = (u32)~0,
};
/*
* File: drivers/video/omap/omap1/sossi.c
*
* OMAP1 Special OptimiSed Screen Interface support
*
* Copyright (C) 2004-2005 Nokia Corporation
* Author: Juha Yrjl <juha.yrjola@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/mm.h>
#include <linux/clk.h>
#include <linux/irq.h>
#include <asm/io.h>
#include <asm/arch/dma.h>
#include <asm/arch/omapfb.h>
#include "lcdc.h"
#define MODULE_NAME "omapfb-sossi"
#define OMAP_SOSSI_BASE 0xfffbac00
#define SOSSI_ID_REG 0x00
#define SOSSI_INIT1_REG 0x04
#define SOSSI_INIT2_REG 0x08
#define SOSSI_INIT3_REG 0x0c
#define SOSSI_FIFO_REG 0x10
#define SOSSI_REOTABLE_REG 0x14
#define SOSSI_TEARING_REG 0x18
#define SOSSI_INIT1B_REG 0x1c
#define SOSSI_FIFOB_REG 0x20
#define DMA_GSCR 0xfffedc04
#define DMA_LCD_CCR 0xfffee3c2
#define DMA_LCD_CTRL 0xfffee3c4
#define DMA_LCD_LCH_CTRL 0xfffee3ea
#define CONF_SOSSI_RESET_R (1 << 23)
#define RD_ACCESS 0
#define WR_ACCESS 1
#define SOSSI_MAX_XMIT_BYTES (512 * 1024)
static struct {
void __iomem *base;
struct clk *fck;
unsigned long fck_hz;
spinlock_t lock;
int bus_pick_count;
int bus_pick_width;
int tearsync_mode;
int tearsync_line;
void (*lcdc_callback)(void *data);
void *lcdc_callback_data;
int vsync_dma_pending;
/* timing for read and write access */
int clk_div;
u8 clk_tw0[2];
u8 clk_tw1[2];
/* if last_access is the same as current we don't have to change
* the timings
*/
int last_access;
struct omapfb_device *fbdev;
} sossi;
static inline u32 sossi_read_reg(int reg)
{
return readl(sossi.base + reg);
}
static inline u16 sossi_read_reg16(int reg)
{
return readw(sossi.base + reg);
}
static inline u8 sossi_read_reg8(int reg)
{
return readb(sossi.base + reg);
}
static inline void sossi_write_reg(int reg, u32 value)
{
writel(value, sossi.base + reg);
}
static inline void sossi_write_reg16(int reg, u16 value)
{
writew(value, sossi.base + reg);
}
static inline void sossi_write_reg8(int reg, u8 value)
{
writeb(value, sossi.base + reg);
}
static void sossi_set_bits(int reg, u32 bits)
{
sossi_write_reg(reg, sossi_read_reg(reg) | bits);
}
static void sossi_clear_bits(int reg, u32 bits)
{
sossi_write_reg(reg, sossi_read_reg(reg) & ~bits);
}
#define HZ_TO_PS(x) (1000000000 / (x / 1000))
static u32 ps_to_sossi_ticks(u32 ps, int div)
{
u32 clk_period = HZ_TO_PS(sossi.fck_hz) * div;
return (clk_period + ps - 1) / clk_period;
}
static int calc_rd_timings(struct extif_timings *t)
{
u32 tw0, tw1;
int reon, reoff, recyc, actim;
int div = t->clk_div;
/* Make sure that after conversion it still holds that:
* reoff > reon, recyc >= reoff, actim > reon
*/
reon = ps_to_sossi_ticks(t->re_on_time, div);
/* reon will be exactly one sossi tick */
if (reon > 1)
return -1;
reoff = ps_to_sossi_ticks(t->re_off_time, div);
if (reoff <= reon)
reoff = reon + 1;
tw0 = reoff - reon;
if (tw0 > 0x10)
return -1;
recyc = ps_to_sossi_ticks(t->re_cycle_time, div);
if (recyc <= reoff)
recyc = reoff + 1;
tw1 = recyc - tw0;
/* values less then 3 result in the SOSSI block resetting itself */
if (tw1 < 3)
tw1 = 3;
if (tw1 > 0x40)
return -1;
actim = ps_to_sossi_ticks(t->access_time, div);
if (actim < reoff)
actim++;
/* access time (data hold time) will be exactly one sossi
* tick
*/
if (actim - reoff > 1)
return -1;
t->tim[0] = tw0 - 1;
t->tim[1] = tw1 - 1;
return 0;
}
static int calc_wr_timings(struct extif_timings *t)
{
u32 tw0, tw1;
int weon, weoff, wecyc;
int div = t->clk_div;
/* Make sure that after conversion it still holds that:
* weoff > weon, wecyc >= weoff
*/
weon = ps_to_sossi_ticks(t->we_on_time, div);
/* weon will be exactly one sossi tick */
if (weon > 1)
return -1;
weoff = ps_to_sossi_ticks(t->we_off_time, div);
if (weoff <= weon)
weoff = weon + 1;
tw0 = weoff - weon;
if (tw0 > 0x10)
return -1;
wecyc = ps_to_sossi_ticks(t->we_cycle_time, div);
if (wecyc <= weoff)
wecyc = weoff + 1;
tw1 = wecyc - tw0;
/* values less then 3 result in the SOSSI block resetting itself */
if (tw1 < 3)
tw1 = 3;
if (tw1 > 0x40)
return -1;
t->tim[2] = tw0 - 1;
t->tim[3] = tw1 - 1;
return 0;
}
static void _set_timing(int div, int tw0, int tw1)
{
u32 l;
#ifdef VERBOSE
dev_dbg(sossi.fbdev->dev, "Using TW0 = %d, TW1 = %d, div = %d\n",
tw0 + 1, tw1 + 1, div);
#endif
clk_set_rate(sossi.fck, sossi.fck_hz / div);
clk_enable(sossi.fck);
l = sossi_read_reg(SOSSI_INIT1_REG);
l &= ~((0x0f << 20) | (0x3f << 24));
l |= (tw0 << 20) | (tw1 << 24);
sossi_write_reg(SOSSI_INIT1_REG, l);
clk_disable(sossi.fck);
}
static void _set_bits_per_cycle(int bus_pick_count, int bus_pick_width)
{
u32 l;
l = sossi_read_reg(SOSSI_INIT3_REG);
l &= ~0x3ff;
l |= ((bus_pick_count - 1) << 5) | ((bus_pick_width - 1) & 0x1f);
sossi_write_reg(SOSSI_INIT3_REG, l);
}
static void _set_tearsync_mode(int mode, unsigned line)
{
u32 l;
l = sossi_read_reg(SOSSI_TEARING_REG);
l &= ~(((1 << 11) - 1) << 15);
l |= line << 15;
l &= ~(0x3 << 26);
l |= mode << 26;
sossi_write_reg(SOSSI_TEARING_REG, l);
if (mode)
sossi_set_bits(SOSSI_INIT2_REG, 1 << 6); /* TE logic */
else
sossi_clear_bits(SOSSI_INIT2_REG, 1 << 6);
}
static inline void set_timing(int access)
{
if (access != sossi.last_access) {
sossi.last_access = access;
_set_timing(sossi.clk_div,
sossi.clk_tw0[access], sossi.clk_tw1[access]);
}
}
static void sossi_start_transfer(void)
{
/* WE */
sossi_clear_bits(SOSSI_INIT2_REG, 1 << 4);
/* CS active low */
sossi_clear_bits(SOSSI_INIT1_REG, 1 << 30);
}
static void sossi_stop_transfer(void)
{
/* WE */
sossi_set_bits(SOSSI_INIT2_REG, 1 << 4);
/* CS active low */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 30);
}
static void wait_end_of_write(void)
{
/* Before reading we must check if some writings are going on */
while (!(sossi_read_reg(SOSSI_INIT2_REG) & (1 << 3)));
}
static void send_data(const void *data, unsigned int len)
{
while (len >= 4) {
sossi_write_reg(SOSSI_FIFO_REG, *(const u32 *) data);
len -= 4;
data += 4;
}
while (len >= 2) {
sossi_write_reg16(SOSSI_FIFO_REG, *(const u16 *) data);
len -= 2;
data += 2;
}
while (len) {
sossi_write_reg8(SOSSI_FIFO_REG, *(const u8 *) data);
len--;
data++;
}
}
static void set_cycles(unsigned int len)
{
unsigned long nr_cycles = len / (sossi.bus_pick_width / 8);
BUG_ON((nr_cycles - 1) & ~0x3ffff);
sossi_clear_bits(SOSSI_INIT1_REG, 0x3ffff);
sossi_set_bits(SOSSI_INIT1_REG, (nr_cycles - 1) & 0x3ffff);
}
static int sossi_convert_timings(struct extif_timings *t)
{
int r = 0;
int div = t->clk_div;
t->converted = 0;
if (div <= 0 || div > 8)
return -1;
/* no CS on SOSSI, so ignore cson, csoff, cs_pulsewidth */
if ((r = calc_rd_timings(t)) < 0)
return r;
if ((r = calc_wr_timings(t)) < 0)
return r;
t->tim[4] = div;
t->converted = 1;
return 0;
}
static void sossi_set_timings(const struct extif_timings *t)
{
BUG_ON(!t->converted);
sossi.clk_tw0[RD_ACCESS] = t->tim[0];
sossi.clk_tw1[RD_ACCESS] = t->tim[1];
sossi.clk_tw0[WR_ACCESS] = t->tim[2];
sossi.clk_tw1[WR_ACCESS] = t->tim[3];
sossi.clk_div = t->tim[4];
}
static void sossi_get_clk_info(u32 *clk_period, u32 *max_clk_div)
{
*clk_period = HZ_TO_PS(sossi.fck_hz);
*max_clk_div = 8;
}
static void sossi_set_bits_per_cycle(int bpc)
{
int bus_pick_count, bus_pick_width;
/* We set explicitly the the bus_pick_count as well, although
* with remapping/reordering disabled it will be calculated by HW
* as (32 / bus_pick_width).
*/
switch (bpc) {
case 8:
bus_pick_count = 4;
bus_pick_width = 8;
break;
case 16:
bus_pick_count = 2;
bus_pick_width = 16;
break;
default:
BUG();
return;
}
sossi.bus_pick_width = bus_pick_width;
sossi.bus_pick_count = bus_pick_count;
}
static int sossi_setup_tearsync(unsigned pin_cnt,
unsigned hs_pulse_time, unsigned vs_pulse_time,
int hs_pol_inv, int vs_pol_inv, int div)
{
int hs, vs;
u32 l;
if (pin_cnt != 1 || div < 1 || div > 8)
return -EINVAL;
hs = ps_to_sossi_ticks(hs_pulse_time, div);
vs = ps_to_sossi_ticks(vs_pulse_time, div);
if (vs < 8 || vs <= hs || vs >= (1 << 12))
return -EDOM;
vs /= 8;
vs--;
if (hs > 8)
hs = 8;
if (hs)
hs--;
dev_dbg(sossi.fbdev->dev,
"setup_tearsync: hs %d vs %d hs_inv %d vs_inv %d\n",
hs, vs, hs_pol_inv, vs_pol_inv);
clk_enable(sossi.fck);
l = sossi_read_reg(SOSSI_TEARING_REG);
l &= ~((1 << 15) - 1);
l |= vs << 3;
l |= hs;
if (hs_pol_inv)
l |= 1 << 29;
else
l &= ~(1 << 29);
if (vs_pol_inv)
l |= 1 << 28;
else
l &= ~(1 << 28);
sossi_write_reg(SOSSI_TEARING_REG, l);
clk_disable(sossi.fck);
return 0;
}
static int sossi_enable_tearsync(int enable, unsigned line)
{
int mode;
dev_dbg(sossi.fbdev->dev, "tearsync %d line %d\n", enable, line);
if (line >= 1 << 11)
return -EINVAL;
if (enable) {
if (line)
mode = 2; /* HS or VS */
else
mode = 3; /* VS only */
} else
mode = 0;
sossi.tearsync_line = line;
sossi.tearsync_mode = mode;
return 0;
}
static void sossi_write_command(const void *data, unsigned int len)
{
clk_enable(sossi.fck);
set_timing(WR_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
/* CMD#/DATA */
sossi_clear_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(len);
sossi_start_transfer();
send_data(data, len);
sossi_stop_transfer();
wait_end_of_write();
clk_disable(sossi.fck);
}
static void sossi_write_data(const void *data, unsigned int len)
{
clk_enable(sossi.fck);
set_timing(WR_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
/* CMD#/DATA */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(len);
sossi_start_transfer();
send_data(data, len);
sossi_stop_transfer();
wait_end_of_write();
clk_disable(sossi.fck);
}
static void sossi_transfer_area(int width, int height,
void (callback)(void *data), void *data)
{
BUG_ON(callback == NULL);
sossi.lcdc_callback = callback;
sossi.lcdc_callback_data = data;
clk_enable(sossi.fck);
set_timing(WR_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
_set_tearsync_mode(sossi.tearsync_mode, sossi.tearsync_line);
/* CMD#/DATA */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(width * height * sossi.bus_pick_width / 8);
sossi_start_transfer();
if (sossi.tearsync_mode) {
/* Wait for the sync signal and start the transfer only
* then. We can't seem to be able to use HW sync DMA for
* this since LCD DMA shows huge latencies, as if it
* would ignore some of the DMA requests from SoSSI.
*/
unsigned long flags;
spin_lock_irqsave(&sossi.lock, flags);
sossi.vsync_dma_pending++;
spin_unlock_irqrestore(&sossi.lock, flags);
} else
/* Just start the transfer right away. */
omap_enable_lcd_dma();
}
static void sossi_dma_callback(void *data)
{
omap_stop_lcd_dma();
sossi_stop_transfer();
clk_disable(sossi.fck);
sossi.lcdc_callback(sossi.lcdc_callback_data);
}
static void sossi_read_data(void *data, unsigned int len)
{
clk_enable(sossi.fck);
set_timing(RD_ACCESS);
_set_bits_per_cycle(sossi.bus_pick_count, sossi.bus_pick_width);
/* CMD#/DATA */
sossi_set_bits(SOSSI_INIT1_REG, 1 << 18);
set_cycles(len);
sossi_start_transfer();
while (len >= 4) {
*(u32 *) data = sossi_read_reg(SOSSI_FIFO_REG);
len -= 4;
data += 4;
}
while (len >= 2) {
*(u16 *) data = sossi_read_reg16(SOSSI_FIFO_REG);
len -= 2;
data += 2;
}
while (len) {
*(u8 *) data = sossi_read_reg8(SOSSI_FIFO_REG);
len--;
data++;
}
sossi_stop_transfer();
clk_disable(sossi.fck);
}
static irqreturn_t sossi_match_irq(int irq, void *data)
{
unsigned long flags;
spin_lock_irqsave(&sossi.lock, flags);
if (sossi.vsync_dma_pending) {
sossi.vsync_dma_pending--;
omap_enable_lcd_dma();
}
spin_unlock_irqrestore(&sossi.lock, flags);
return IRQ_HANDLED;
}
static int sossi_init(struct omapfb_device *fbdev)
{
u32 l, k;
struct clk *fck;
struct clk *dpll1out_ck;
int r;
sossi.base = (void __iomem *)IO_ADDRESS(OMAP_SOSSI_BASE);
sossi.fbdev = fbdev;
spin_lock_init(&sossi.lock);
dpll1out_ck = clk_get(fbdev->dev, "ck_dpll1out");
if (IS_ERR(dpll1out_ck)) {
dev_err(fbdev->dev, "can't get DPLL1OUT clock\n");
return PTR_ERR(dpll1out_ck);
}
/* We need the parent clock rate, which we might divide further
* depending on the timing requirements of the controller. See
* _set_timings.
*/
sossi.fck_hz = clk_get_rate(dpll1out_ck);
clk_put(dpll1out_ck);
fck = clk_get(fbdev->dev, "ck_sossi");
if (IS_ERR(fck)) {
dev_err(fbdev->dev, "can't get SoSSI functional clock\n");
return PTR_ERR(fck);
}
sossi.fck = fck;
/* Reset and enable the SoSSI module */
l = omap_readl(MOD_CONF_CTRL_1);
l |= CONF_SOSSI_RESET_R;
omap_writel(l, MOD_CONF_CTRL_1);
l &= ~CONF_SOSSI_RESET_R;
omap_writel(l, MOD_CONF_CTRL_1);
clk_enable(sossi.fck);
l = omap_readl(ARM_IDLECT2);
l &= ~(1 << 8); /* DMACK_REQ */
omap_writel(l, ARM_IDLECT2);
l = sossi_read_reg(SOSSI_INIT2_REG);
/* Enable and reset the SoSSI block */
l |= (1 << 0) | (1 << 1);
sossi_write_reg(SOSSI_INIT2_REG, l);
/* Take SoSSI out of reset */
l &= ~(1 << 1);
sossi_write_reg(SOSSI_INIT2_REG, l);
sossi_write_reg(SOSSI_ID_REG, 0);
l = sossi_read_reg(SOSSI_ID_REG);
k = sossi_read_reg(SOSSI_ID_REG);
if (l != 0x55555555 || k != 0xaaaaaaaa) {
dev_err(fbdev->dev,
"invalid SoSSI sync pattern: %08x, %08x\n", l, k);
r = -ENODEV;
goto err;
}
if ((r = omap_lcdc_set_dma_callback(sossi_dma_callback, NULL)) < 0) {
dev_err(fbdev->dev, "can't get LCDC IRQ\n");
r = -ENODEV;
goto err;
}
l = sossi_read_reg(SOSSI_ID_REG); /* Component code */
l = sossi_read_reg(SOSSI_ID_REG);
dev_info(fbdev->dev, "SoSSI version %d.%d initialized\n",
l >> 16, l & 0xffff);
l = sossi_read_reg(SOSSI_INIT1_REG);
l |= (1 << 19); /* DMA_MODE */
l &= ~(1 << 31); /* REORDERING */
sossi_write_reg(SOSSI_INIT1_REG, l);
if ((r = request_irq(INT_1610_SoSSI_MATCH, sossi_match_irq,
IRQT_FALLING,
"sossi_match", sossi.fbdev->dev)) < 0) {
dev_err(sossi.fbdev->dev, "can't get SoSSI match IRQ\n");
goto err;
}
clk_disable(sossi.fck);
return 0;
err:
clk_disable(sossi.fck);
clk_put(sossi.fck);
return r;
}
static void sossi_cleanup(void)
{
omap_lcdc_free_dma_callback();
clk_put(sossi.fck);
}
struct lcd_ctrl_extif omap1_ext_if = {
.init = sossi_init,
.cleanup = sossi_cleanup,
.get_clk_info = sossi_get_clk_info,
.convert_timings = sossi_convert_timings,
.set_timings = sossi_set_timings,
.set_bits_per_cycle = sossi_set_bits_per_cycle,
.setup_tearsync = sossi_setup_tearsync,
.enable_tearsync = sossi_enable_tearsync,
.write_command = sossi_write_command,
.read_data = sossi_read_data,
.write_data = sossi_write_data,
.transfer_area = sossi_transfer_area,
.max_transmit_size = SOSSI_MAX_XMIT_BYTES,
};
......@@ -119,6 +119,7 @@ struct dentry;
#define FB_ACCEL_NV_40 46 /* nVidia Arch 40 */
#define FB_ACCEL_XGI_VOLARI_V 47 /* XGI Volari V3XT, V5, V8 */
#define FB_ACCEL_XGI_VOLARI_Z 48 /* XGI Volari Z7 */
#define FB_ACCEL_OMAP1610 49 /* TI OMAP16xx */
#define FB_ACCEL_NEOMAGIC_NM2070 90 /* NeoMagic NM2070 */
#define FB_ACCEL_NEOMAGIC_NM2090 91 /* NeoMagic NM2090 */
#define FB_ACCEL_NEOMAGIC_NM2093 92 /* NeoMagic NM2093 */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment