From 4361cd71abf47b3ce84c9aa498333be29eb6315c Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Tue, 3 Apr 2007 14:15:01 -0400 Subject: [PATCH] FB: Add support for OMAP framebuffer Add support for OMAP framebuffer Signed-off-by: Imre Deak --- drivers/video/Kconfig | 4 + drivers/video/Makefile | 1 + drivers/video/omap/Kconfig | 75 ++ drivers/video/omap/Makefile | 35 + drivers/video/omap/blizzard.c | 1570 ++++++++++++++++++++++ drivers/video/omap/dispc.c | 1512 ++++++++++++++++++++++ drivers/video/omap/dispc.h | 43 + drivers/video/omap/hwa742.c | 1072 ++++++++++++++++ drivers/video/omap/lcd_ams_delta.c | 140 ++ drivers/video/omap/lcd_apollon.c | 139 ++ drivers/video/omap/lcd_h2.c | 155 +++ drivers/video/omap/lcd_h3.c | 143 +++ drivers/video/omap/lcd_h4.c | 119 ++ drivers/video/omap/lcd_inn1510.c | 126 ++ drivers/video/omap/lcd_inn1610.c | 151 +++ drivers/video/omap/lcd_mipid.c | 616 +++++++++ drivers/video/omap/lcd_osk.c | 146 +++ drivers/video/omap/lcd_p2.c | 344 +++++ drivers/video/omap/lcd_palmte.c | 126 ++ drivers/video/omap/lcd_palmtt.c | 130 ++ drivers/video/omap/lcd_palmz71.c | 126 ++ drivers/video/omap/lcd_sx1.c | 331 +++++ drivers/video/omap/lcdc.c | 887 +++++++++++++ drivers/video/omap/lcdc.h | 7 + drivers/video/omap/omapfb_main.c | 1928 ++++++++++++++++++++++++++++ drivers/video/omap/rfbi.c | 591 +++++++++ drivers/video/omap/sossi.c | 682 ++++++++++ include/linux/fb.h | 1 + 28 files changed, 11200 insertions(+) create mode 100644 drivers/video/omap/Kconfig create mode 100644 drivers/video/omap/Makefile create mode 100644 drivers/video/omap/blizzard.c create mode 100644 drivers/video/omap/dispc.c create mode 100644 drivers/video/omap/dispc.h create mode 100644 drivers/video/omap/hwa742.c create mode 100644 drivers/video/omap/lcd_ams_delta.c create mode 100644 drivers/video/omap/lcd_apollon.c create mode 100644 drivers/video/omap/lcd_h2.c create mode 100644 drivers/video/omap/lcd_h3.c create mode 100644 drivers/video/omap/lcd_h4.c create mode 100644 drivers/video/omap/lcd_inn1510.c create mode 100644 drivers/video/omap/lcd_inn1610.c create mode 100644 drivers/video/omap/lcd_mipid.c create mode 100644 drivers/video/omap/lcd_osk.c create mode 100644 drivers/video/omap/lcd_p2.c create mode 100644 drivers/video/omap/lcd_palmte.c create mode 100644 drivers/video/omap/lcd_palmtt.c create mode 100644 drivers/video/omap/lcd_palmz71.c create mode 100644 drivers/video/omap/lcd_sx1.c create mode 100644 drivers/video/omap/lcdc.c create mode 100644 drivers/video/omap/lcdc.h create mode 100644 drivers/video/omap/omapfb_main.c create mode 100644 drivers/video/omap/rfbi.c create mode 100644 drivers/video/omap/sossi.c diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 344c3759530..1635c3fd879 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1662,6 +1662,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_VIRTUAL tristate "Virtual Frame Buffer support (ONLY FOR TESTING!)" depends on FB diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 558473d040d..d97c2bf1fc3 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -108,6 +108,7 @@ obj-$(CONFIG_FB_VESA) += vesafb.o obj-$(CONFIG_FB_IMAC) += imacfb.o obj-$(CONFIG_FB_VGA16) += vga16fb.o vgastate.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 diff --git a/drivers/video/omap/Kconfig b/drivers/video/omap/Kconfig new file mode 100644 index 00000000000..78123a25a33 --- /dev/null +++ b/drivers/video/omap/Kconfig @@ -0,0 +1,75 @@ +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. + + diff --git a/drivers/video/omap/Makefile b/drivers/video/omap/Makefile new file mode 100644 index 00000000000..be258017799 --- /dev/null +++ b/drivers/video/omap/Makefile @@ -0,0 +1,35 @@ +# +# 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) + diff --git a/drivers/video/omap/blizzard.c b/drivers/video/omap/blizzard.c new file mode 100644 index 00000000000..7905d132b3f --- /dev/null +++ b/drivers/video/omap/blizzard.c @@ -0,0 +1,1570 @@ +/* + * File: drivers/video/omap/blizzard.c + * + * Epson Blizzard LCD controller driver + * + * Copyright (C) 2004-2005 Nokia Corporation + * Authors: Juha Yrjola + * Imre Deak + * YUV support: Jussi Laako + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#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(®, 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(®, 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 = ∁ + + 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, +}; + diff --git a/drivers/video/omap/dispc.c b/drivers/video/omap/dispc.c new file mode 100644 index 00000000000..5eeaddb3075 --- /dev/null +++ b/drivers/video/omap/dispc.c @@ -0,0 +1,1512 @@ +/* + * File: drivers/video/omap/omap2/dispc.c + * + * OMAP2 display controller support + * + * Copyright (C) 2005 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include +#include +#include + +#include + +#include +#include +#include + +#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, ®ion->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, +}; diff --git a/drivers/video/omap/dispc.h b/drivers/video/omap/dispc.h new file mode 100644 index 00000000000..eb1512b56ce --- /dev/null +++ b/drivers/video/omap/dispc.h @@ -0,0 +1,43 @@ +#ifndef _DISPC_H +#define _DISPC_H + +#include + +#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 diff --git a/drivers/video/omap/hwa742.c b/drivers/video/omap/hwa742.c new file mode 100644 index 00000000000..5d92f593d44 --- /dev/null +++ b/drivers/video/omap/hwa742.c @@ -0,0 +1,1072 @@ +/* + * File: drivers/video/omap/hwa742.c + * + * Epson HWA742 LCD controller driver + * + * Copyright (C) 2004-2005 Nokia Corporation + * Authors: Juha Yrjölä + * Imre Deak + * YUV support: Jussi Laako + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#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(®, 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(®, 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 = ∁ + + 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, +}; + diff --git a/drivers/video/omap/lcd_ams_delta.c b/drivers/video/omap/lcd_ams_delta.c new file mode 100644 index 00000000000..3476689467d --- /dev/null +++ b/drivers/video/omap/lcd_ams_delta.c @@ -0,0 +1,140 @@ +/* + * 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 + * + * 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 +#include + +#include +#include + +#include +#include +#include + +#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); diff --git a/drivers/video/omap/lcd_apollon.c b/drivers/video/omap/lcd_apollon.c new file mode 100644 index 00000000000..4c0f370a1c5 --- /dev/null +++ b/drivers/video/omap/lcd_apollon.c @@ -0,0 +1,139 @@ +/* + * 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 + * + * 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 +#include + +#include +#include +#include + +/* #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); diff --git a/drivers/video/omap/lcd_h2.c b/drivers/video/omap/lcd_h2.c new file mode 100644 index 00000000000..288f08243db --- /dev/null +++ b/drivers/video/omap/lcd_h2.c @@ -0,0 +1,155 @@ +/* + * File: drivers/video/omap/lcd-h2.c + * + * LCD panel support for the TI OMAP H2 board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include +#include + +#include +#include + +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); + diff --git a/drivers/video/omap/lcd_h3.c b/drivers/video/omap/lcd_h3.c new file mode 100644 index 00000000000..60ad0e0942c --- /dev/null +++ b/drivers/video/omap/lcd_h3.c @@ -0,0 +1,143 @@ +/* + * File: drivers/video/omap/lcd-h3.c + * + * LCD panel support for the TI OMAP H3 board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include + +#include +#include +#include + +#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); + diff --git a/drivers/video/omap/lcd_h4.c b/drivers/video/omap/lcd_h4.c new file mode 100644 index 00000000000..dde00d422e4 --- /dev/null +++ b/drivers/video/omap/lcd_h4.c @@ -0,0 +1,119 @@ +/* + * File: drivers/video/omap/lcd-h4.c + * + * LCD panel support for the TI OMAP H4 board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include + +#include + +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); + diff --git a/drivers/video/omap/lcd_inn1510.c b/drivers/video/omap/lcd_inn1510.c new file mode 100644 index 00000000000..d514b94684d --- /dev/null +++ b/drivers/video/omap/lcd_inn1510.c @@ -0,0 +1,126 @@ +/* + * File: drivers/video/omap/lcd-inn1510.c + * + * LCD panel support for the TI OMAP1510 Innovator board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include + +#include + +#include +#include + +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); + diff --git a/drivers/video/omap/lcd_inn1610.c b/drivers/video/omap/lcd_inn1610.c new file mode 100644 index 00000000000..cb1fa5a543f --- /dev/null +++ b/drivers/video/omap/lcd_inn1610.c @@ -0,0 +1,151 @@ +/* + * File: drivers/video/omap/lcd-inn1610.c + * + * LCD panel support for the TI OMAP1610 Innovator board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include + +#include +#include + +#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); + diff --git a/drivers/video/omap/lcd_mipid.c b/drivers/video/omap/lcd_mipid.c new file mode 100644 index 00000000000..acc18abc3ea --- /dev/null +++ b/drivers/video/omap/lcd_mipid.c @@ -0,0 +1,616 @@ +/* + * File: drivers/video/omap/lcd_mipid.c + * + * LCD driver for MIPI DBI-C / DCS compatible LCDs + * + * Copyright (C) 2006 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include +#include +#include + +#include +#include + +#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"); diff --git a/drivers/video/omap/lcd_osk.c b/drivers/video/omap/lcd_osk.c new file mode 100644 index 00000000000..cc8bb597ff6 --- /dev/null +++ b/drivers/video/omap/lcd_osk.c @@ -0,0 +1,146 @@ +/* + * File: drivers/video/omap/lcd-osk.c + * + * LCD panel support for the TI OMAP OSK board + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * Adapted for OSK by + * + * 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 +#include + +#include +#include +#include + +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); + diff --git a/drivers/video/omap/lcd_p2.c b/drivers/video/omap/lcd_p2.c new file mode 100644 index 00000000000..864033d4b54 --- /dev/null +++ b/drivers/video/omap/lcd_p2.c @@ -0,0 +1,344 @@ +/* + * File: drivers/video/omap/lcd-p2.c + * + * LCD panel support for the TI OMAP P2 board + * + * Authors: + * jekyll + * B Jp + * Brian Swetland + * + * 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 +#include +#include + +#include +#include +#include + +/* + * 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 + * + * 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); + diff --git a/drivers/video/omap/lcd_palmte.c b/drivers/video/omap/lcd_palmte.c new file mode 100644 index 00000000000..1d37803a9ac --- /dev/null +++ b/drivers/video/omap/lcd_palmte.c @@ -0,0 +1,126 @@ +/* + * 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 +#include + +#include + +#include +#include + +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); + diff --git a/drivers/video/omap/lcd_palmtt.c b/drivers/video/omap/lcd_palmtt.c new file mode 100644 index 00000000000..97d3680ec1b --- /dev/null +++ b/drivers/video/omap/lcd_palmtt.c @@ -0,0 +1,130 @@ +/* + * File: drivers/video/omap/lcd_palmtt.c + * + * LCD panel support for Palm Tungsten|T + * Current version : Marek Vasut + * + * 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 +#include + +#include + +#include +#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); diff --git a/drivers/video/omap/lcd_palmz71.c b/drivers/video/omap/lcd_palmz71.c new file mode 100644 index 00000000000..892993f3fd0 --- /dev/null +++ b/drivers/video/omap/lcd_palmz71.c @@ -0,0 +1,126 @@ +/* + * 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 +#include + +#include + +#include +#include + +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); diff --git a/drivers/video/omap/lcd_sx1.c b/drivers/video/omap/lcd_sx1.c new file mode 100644 index 00000000000..0b927f6ae48 --- /dev/null +++ b/drivers/video/omap/lcd_sx1.c @@ -0,0 +1,331 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include + +/* + * 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); diff --git a/drivers/video/omap/lcdc.c b/drivers/video/omap/lcdc.c new file mode 100644 index 00000000000..0f868436a4f --- /dev/null +++ b/drivers/video/omap/lcdc.c @@ -0,0 +1,887 @@ +/* + * File: drivers/video/omap/omap1/lcdc.c + * + * OMAP1 internal LCD controller + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#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, +}; diff --git a/drivers/video/omap/lcdc.h b/drivers/video/omap/lcdc.h new file mode 100644 index 00000000000..adb731e5314 --- /dev/null +++ b/drivers/video/omap/lcdc.h @@ -0,0 +1,7 @@ +#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 diff --git a/drivers/video/omap/omapfb_main.c b/drivers/video/omap/omapfb_main.c new file mode 100644 index 00000000000..7915b1e8391 --- /dev/null +++ b/drivers/video/omap/omapfb_main.c @@ -0,0 +1,1928 @@ +/* + * File: drivers/video/omap/omapfb_main.c + * + * Framebuffer driver for TI OMAP boards + * + * Copyright (C) 2004 Nokia Corporation + * Author: Imre Deak + * + * Acknowledgements: + * Alex McMains - Original driver + * Juha Yrjola - Original driver and improvements + * Dirk Behme - 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 + +#include +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap/rfbi.c b/drivers/video/omap/rfbi.c new file mode 100644 index 00000000000..05164ca9957 --- /dev/null +++ b/drivers/video/omap/rfbi.c @@ -0,0 +1,591 @@ +/* + * File: drivers/video/omap/omap2/rfbi.c + * + * OMAP2 Remote Frame Buffer Interface support + * + * Copyright (C) 2005 Nokia Corporation + * Author: Juha Yrjölä + * Imre Deak + * + * 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 +#include +#include +#include +#include +#include + +#include + +#include + +#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, +}; + diff --git a/drivers/video/omap/sossi.c b/drivers/video/omap/sossi.c new file mode 100644 index 00000000000..505b93276ff --- /dev/null +++ b/drivers/video/omap/sossi.c @@ -0,0 +1,682 @@ +/* + * File: drivers/video/omap/omap1/sossi.c + * + * OMAP1 Special OptimiSed Screen Interface support + * + * Copyright (C) 2004-2005 Nokia Corporation + * Author: Juha Yrjölä + * + * 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 +#include +#include +#include + +#include + +#include +#include + +#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, +}; + diff --git a/include/linux/fb.h b/include/linux/fb.h index be913ec8716..74b1a68b8a5 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -117,6 +117,7 @@ #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 */ -- 2.41.0