From 56023db1cac7ad89fd5fbeb4221bd037a3622368 Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Mon, 9 May 2005 14:21:24 -0700 Subject: [PATCH] FB: OMAP: Add support for framebuffer Adds support for OMAP framebuffer. Signed-off-by: Imre Deak Signed-off-by: Tony Lindgren --- drivers/video/Kconfig | 42 + drivers/video/Makefile | 1 + drivers/video/omap/Makefile | 18 + drivers/video/omap/debug.h | 106 ++ drivers/video/omap/lcd_h2.c | 133 ++ drivers/video/omap/lcd_h3.c | 111 ++ drivers/video/omap/lcd_inn1510.c | 98 ++ drivers/video/omap/lcd_inn1610.c | 122 ++ drivers/video/omap/lcd_osk.c | 117 ++ drivers/video/omap/lcd_p2.c | 311 ++++ drivers/video/omap/omap_lcdc.c | 599 ++++++++ drivers/video/omap/omapfb.h | 324 +++++ drivers/video/omap/omapfb_main.c | 2269 ++++++++++++++++++++++++++++++ drivers/video/omap/sossi.c | 302 ++++ drivers/video/omap/sossi.h | 19 + include/linux/fb.h | 1 + 16 files changed, 4573 insertions(+) create mode 100644 drivers/video/omap/Makefile create mode 100644 drivers/video/omap/debug.h 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_inn1510.c create mode 100644 drivers/video/omap/lcd_inn1610.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/omap_lcdc.c create mode 100644 drivers/video/omap/omapfb.h create mode 100644 drivers/video/omap/omapfb_main.c create mode 100644 drivers/video/omap/sossi.c create mode 100644 drivers/video/omap/sossi.h diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 6be8fbec0a0..61e23979770 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -1468,6 +1468,48 @@ config FB_S1D13XXX working with S1D13806). Product specs at +config FB_OMAP + tristate "OMAP frame buffer support (EXPERIMENTAL)" + depends on FB && ARCH_OMAP1 + select FB_SOFT_CURSOR + help + This is the new frame buffer device driver with 2D acceleration + for OMAP boards. + +config FB_OMAP_INTERNAL_LCDC + bool "OMAP internal LCD controller support" + depends on FB_OMAP + default y + help + Say Y here, if you want to have support for the internal OMAP + LCD controller. If unsure, say Y. + +config FB_OMAP_EXTERNAL_LCDC + bool "OMAP 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 interface. + +config FB_OMAP_MANUAL_UPDATE + bool "Default to manual update mode" + depends on FB_OMAP_EXTERNAL_LCDC + 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 is + required. If unsure, say N. + +config FB_OMAP_DMA_TUNE + bool "Set DMA SDRAM access priority high" + depends on FB_OMAP + 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. + 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 bd8dc0ffe72..20b83d8483e 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -96,6 +96,7 @@ obj-$(CONFIG_FB_IMX) += imxfb.o obj-$(CONFIG_FB_VESA) += vesafb.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/Makefile b/drivers/video/omap/Makefile new file mode 100644 index 00000000000..e5bfd0326b2 --- /dev/null +++ b/drivers/video/omap/Makefile @@ -0,0 +1,18 @@ +# +# Makefile for the new OMAP framebuffer device driver +# + +obj-$(CONFIG_FB_OMAP) += omapfb.o + +objs-yy := omapfb_main.o +objs-y$(CONFIG_FB_OMAP_INTERNAL_LCDC) += omap_lcdc.o +objs-y$(CONFIG_FB_OMAP_EXTERNAL_LCDC) += sossi.o +objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_H3) += lcd_h3.o +objs-y$(CONFIG_MACH_OMAP_H2) += lcd_h2.o +objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1610.o +objs-$(CONFIG_ARCH_OMAP1510)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1510.o +objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_OSK) += lcd_osk.o +objs-$(CONFIG_ARCH_OMAP730)$(CONFIG_MACH_OMAP_PERSEUS2) += lcd_p2.o + +omapfb-objs := $(objs-yy) + diff --git a/drivers/video/omap/debug.h b/drivers/video/omap/debug.h new file mode 100644 index 00000000000..cbc5db8d529 --- /dev/null +++ b/drivers/video/omap/debug.h @@ -0,0 +1,106 @@ +/* + * File: drivers/video/omap_new/debug.c + * + * Debug support for the omapfb driver + * + * 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. + */ + +#ifndef __OMAPFB_DEBUG_H +#define __OMAPFB_DEBUG_H + +#ifdef OMAPFB_DBG + +#define DBG_BUF_SIZE 2048 +#define MAX_DBG_INDENT_LEVEL 5 +#define DBG_INDENT_SIZE 3 +#define MAX_DBG_MESSAGES 0 + +static int dbg_indent; +static int dbg_cnt; +static char dbg_buf[DBG_BUF_SIZE]; +static spinlock_t dbg_spinlock = SPIN_LOCK_UNLOCKED; + +static inline void dbg_print(int level, const char *fmt, ...) +{ + if (level <= OMAPFB_DBG) { + if (!MAX_DBG_MESSAGES || dbg_cnt < MAX_DBG_MESSAGES) { + va_list args; + int ind = dbg_indent; + unsigned long flags; + + spin_lock_irqsave(&dbg_spinlock, flags); + dbg_cnt++; + if (ind > MAX_DBG_INDENT_LEVEL) + ind = MAX_DBG_INDENT_LEVEL; + + printk("%*s", ind * DBG_INDENT_SIZE, ""); + va_start(args, fmt); + vsnprintf(dbg_buf, sizeof(dbg_buf), fmt, args); + printk(dbg_buf); + va_end(args); + spin_unlock_irqrestore(&dbg_spinlock, flags); + } + } +} + +#define DBGPRINT dbg_print + +#define DBGENTER(level) do { \ + dbg_print(level, "%s: Enter\n", __FUNCTION__); \ + dbg_indent++; \ + } while (0) + +#define DBGLEAVE(level) do { \ + dbg_indent--; \ + dbg_print(level, "%s: Leave\n", __FUNCTION__); \ + } while (0) + +static inline void dump_dma_regs(int lch) +{ +#define _R(x) __REG16(OMAP_DMA_##x(lch)) + + dbg_print(4, "\nCSDP :%#06x CCR :%#06x CSSA_U :%#06x " + "\nCDSA_L:%#06x CDSA_U :%#06x CEN :%#06x " + "\nCFN :%#06x CSFI :%#06x CSEI :%#06x " + "\nCSAC :%#06x CICR :%#06x CSR :%04x " + "\nCSSA_L:%#06x CDAC :%#06x CDEI :%#06x " + "\nCDFI :%#06x COLOR_L :%#06x COLOR_U :%#06x " + "\nCCR2 :%#06x CLNK_CTRL:%#06x LCH_CTRL:%#06x\n", + _R(CSDP), _R(CCR), _R(CSSA_U), + _R(CDSA_L), _R(CDSA_U), _R(CEN), + _R(CFN), _R(CSFI), _R(CSEI), + _R(CSAC), _R(CICR), 0, /* _R(CSR), */ + _R(CSSA_L), _R(CDAC), _R(CDEI), + _R(CDFI), _R(COLOR_L), _R(COLOR_U), + _R(CCR2), _R(CLNK_CTRL), _R(LCH_CTRL)); +#undef _R +} + +#define DUMP_DMA_REGS(lch) dump_dma_regs(lch) + +#else /* OMAPFB_DBG */ + +#define DBGPRINT(level, format, ...) +#define DBGENTER(level) +#define DBGLEAVE(level) +#define DUMP_DMA_REGS(lch) + +#endif /* OMAPFB_DBG */ + +#endif /* __OMAPFB_DEBUG_H */ diff --git a/drivers/video/omap/lcd_h2.c b/drivers/video/omap/lcd_h2.c new file mode 100644 index 00000000000..981cf1e2341 --- /dev/null +++ b/drivers/video/omap/lcd_h2.c @@ -0,0 +1,133 @@ +/* + * File: drivers/video/omap_new/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 "omapfb.h" + +// #define OMAPFB_DBG 1 + +#include "debug.h" +#include "../drivers/ssi/omap-uwire.h" + +#define TSC2101_UWIRE_CS 1 + +static int tsc2101_write_reg(int page, int reg, u16 data) +{ + u16 cmd; + int r; + + DBGENTER(1); + + cmd = ((page & 3) << 11) | ((reg & 0x3f) << 5); + if (omap_uwire_data_transfer(TSC2101_UWIRE_CS, cmd, 16, 0, NULL, 1)) + r = -1; + else + r = omap_uwire_data_transfer(TSC2101_UWIRE_CS, data, 16, 0, NULL, 0); + + DBGLEAVE(1); + return r; +} + +static int h2_panel_init(struct lcd_panel *panel) +{ + unsigned long uwire_flags; + DBGENTER(1); + + /* Configure N15 pin to be uWire CS1 */ + omap_cfg_reg(N15_1610_UWIRE_CS1); + uwire_flags = UWIRE_READ_RISING_EDGE | UWIRE_WRITE_RISING_EDGE; + uwire_flags |= UWIRE_FREQ_DIV_8; + omap_uwire_configure_mode(TSC2101_UWIRE_CS, uwire_flags); + + DBGLEAVE(1); + return 0; +} + +static void h2_panel_cleanup(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static int h2_panel_enable(struct lcd_panel *panel) +{ + int r; + + DBGENTER(1); + + /* Assert LCD_EN, BKLIGHT_EN pins on LCD panel + * page2, GPIO config reg, GPIO(0,1) to out and asserted + */ + r = tsc2101_write_reg(2, 0x23, 0xCC00) ? -1 : 0; + + DBGLEAVE(1); + return r; +} + +static void h2_panel_disable(struct lcd_panel *panel) +{ + DBGENTER(1); + + /* Deassert LCD_EN and BKLIGHT_EN pins on LCD panel + * page2, GPIO config reg, GPIO(0,1) to out and deasserted + */ + if (tsc2101_write_reg(2, 0x23, 0x8800)) + PRNERR("failed to disable LCD panel\n"); + + DBGLEAVE(1); +} + +static unsigned long h2_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcdc_video_mode mode240x320 = { + .x_res = 240, + .y_res = 320, + .pixel_clock = 12500, + .bpp = 16, + .hsw = 12, + .hfp = 14, + .hbp = 72 - 12, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, +}; + +struct lcd_panel h2_panel = { + .name = "h2", + .config = LCD_PANEL_TFT, + .video_mode = &mode240x320, + + .init = h2_panel_init, + .cleanup = h2_panel_cleanup, + .enable = h2_panel_enable, + .disable = h2_panel_disable, + .get_caps= h2_panel_get_caps, +}; + diff --git a/drivers/video/omap/lcd_h3.c b/drivers/video/omap/lcd_h3.c new file mode 100644 index 00000000000..27deb6a0553 --- /dev/null +++ b/drivers/video/omap/lcd_h3.c @@ -0,0 +1,111 @@ +/* + * File: drivers/video/omap_new/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 "omapfb.h" + +// #define OMAPFB_DBG 1 + +#include "debug.h" + +static int h3_panel_init(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); + return 0; +} + +static void h3_panel_cleanup(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static int h3_panel_enable(struct lcd_panel *panel) +{ + int r = 0; + + DBGENTER(1); + + /* 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) + PRNERR("Unable to turn on LCD panel\n"); + + DBGLEAVE(1); + return r; +} + +static void h3_panel_disable(struct lcd_panel *panel) +{ + int r = 0; + + DBGENTER(1); + + /* 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) + PRNERR("Unable to turn off LCD panel\n"); + + DBGLEAVE(1); +} + +static unsigned long h3_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcdc_video_mode mode240x320 = { + .x_res = 240, + .y_res = 320, + .pixel_clock = 12500, + .bpp = 16, + .hsw = 12, + .hfp = 14, + .hbp = 72 - 12, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 4, +}; + +struct lcd_panel h3_panel = { + .name = "h3", + .config = LCD_PANEL_TFT, + .video_mode = &mode240x320, + + .init = h3_panel_init, + .cleanup = h3_panel_cleanup, + .enable = h3_panel_enable, + .disable = h3_panel_disable, + .get_caps= h3_panel_get_caps, +}; + diff --git a/drivers/video/omap/lcd_inn1510.c b/drivers/video/omap/lcd_inn1510.c new file mode 100644 index 00000000000..45f1bdfc733 --- /dev/null +++ b/drivers/video/omap/lcd_inn1510.c @@ -0,0 +1,98 @@ +/* + * File: drivers/video/omap_new/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 "omapfb.h" + +// #define OMAPFB_DBG 1 + +#include "debug.h" + +static int innovator1510_panel_init(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); + return 0; +} + +static void innovator1510_panel_cleanup(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static int innovator1510_panel_enable(struct lcd_panel *panel) +{ + DBGENTER(1); + + fpga_write(0x7, OMAP1510_FPGA_LCD_PANEL_CONTROL); + + DBGLEAVE(1); + return 0; +} + +static void innovator1510_panel_disable(struct lcd_panel *panel) +{ + DBGENTER(1); + + fpga_write(0x0, OMAP1510_FPGA_LCD_PANEL_CONTROL); + + DBGLEAVE(1); +} + +static unsigned long innovator1510_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcdc_video_mode mode240x320 = { + .x_res = 240, + .y_res = 320, + .pixel_clock = 12500, + .bpp = 16, + .hsw = 40, + .hfp = 40, + .hbp = 72, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, +}; + +struct lcd_panel innovator1510_panel = { + .name = "inn1510", + .config = LCD_PANEL_TFT, + .video_mode = &mode240x320, + + .init = innovator1510_panel_init, + .cleanup = innovator1510_panel_cleanup, + .enable = innovator1510_panel_enable, + .disable = innovator1510_panel_disable, + .get_caps= innovator1510_panel_get_caps, +}; + diff --git a/drivers/video/omap/lcd_inn1610.c b/drivers/video/omap/lcd_inn1610.c new file mode 100644 index 00000000000..2d31695088b --- /dev/null +++ b/drivers/video/omap/lcd_inn1610.c @@ -0,0 +1,122 @@ +/* + * File: drivers/video/omap_new/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 "omapfb.h" + +// #define OMAPFB_DBG 1 + +#include "debug.h" + +static int innovator1610_panel_init(struct lcd_panel *panel) +{ + int r = 0; + + DBGENTER(1); + + if (omap_request_gpio(14)) { + PRNERR("can't request GPIO 14\n"); + r = -1; + goto exit; + } + if (omap_request_gpio(15)) { + PRNERR("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: + DBGLEAVE(1); + return r; +} + +static void innovator1610_panel_cleanup(struct lcd_panel *panel) +{ + DBGENTER(1); + + omap_free_gpio(15); + omap_free_gpio(14); + + DBGLEAVE(1); +} + +static int innovator1610_panel_enable(struct lcd_panel *panel) +{ + DBGENTER(1); + + /* set GPIO14 and GPIO15 high */ + omap_set_gpio_dataout(14, 1); + omap_set_gpio_dataout(15, 1); + + DBGLEAVE(1); + return 0; +} + +static void innovator1610_panel_disable(struct lcd_panel *panel) +{ + DBGENTER(1); + + /* set GPIO13, GPIO14 and GPIO15 low */ + omap_set_gpio_dataout(14, 0); + omap_set_gpio_dataout(15, 0); + + DBGLEAVE(1); +} + +static unsigned long innovator1610_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcdc_video_mode mode320x240 = { + .x_res = 320, + .y_res = 240, + .pixel_clock = 12500, + .bpp = 16, + .hsw = 40, + .hfp = 40, + .hbp = 72, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, +}; + +struct lcd_panel innovator1610_panel = { + .name = "inn1610", + .config = LCD_PANEL_TFT, + .video_mode = &mode320x240, + + .init = innovator1610_panel_init, + .cleanup = innovator1610_panel_cleanup, + .enable = innovator1610_panel_enable, + .disable = innovator1610_panel_disable, + .get_caps= innovator1610_panel_get_caps, +}; + diff --git a/drivers/video/omap/lcd_osk.c b/drivers/video/omap/lcd_osk.c new file mode 100644 index 00000000000..4a48832541e --- /dev/null +++ b/drivers/video/omap/lcd_osk.c @@ -0,0 +1,117 @@ +/* + * File: drivers/video/omap_new/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 "omapfb.h" + +// #define OMAPFB_DBG 1 + +#include "debug.h" + +static int osk_panel_init(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); + return 0; +} + +static void osk_panel_cleanup(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static int osk_panel_enable(struct lcd_panel *panel) +{ + DBGENTER(1); + + /* configure PWL pin */ + omap_cfg_reg(PWL); + + /* Enable PWL unit */ + omap_writeb(0x01, OMAP16XX_PWL_CLK_ENABLE); + + /* Set PWL level */ + omap_writeb(0xFF, OMAP16XX_PWL_ENABLE); + + /* configure GPIO2 as output */ + omap_set_gpio_direction(2, 0); + + /* set GPIO2 high */ + omap_set_gpio_dataout(2, 1); + + DBGLEAVE(1); + return 0; +} + +static void osk_panel_disable(struct lcd_panel *panel) +{ + DBGENTER(1); + + /* Set PWL level to zero */ + omap_writeb(0x00, OMAP16XX_PWL_ENABLE); + + /* Disable PWL unit */ + omap_writeb(0x00, OMAP16XX_PWL_CLK_ENABLE); + + /* set GPIO2 low */ + omap_set_gpio_dataout(2, 0); + + DBGLEAVE(1); +} + +static unsigned long osk_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcdc_video_mode mode240x320 = { + .x_res = 240, + .y_res = 320, + .pixel_clock = 12500, + .bpp = 16, + .hsw = 40, + .hfp = 40, + .hbp = 72, + .vsw = 1, + .vfp = 1, + .vbp = 0, + .pcd = 12, +}; + +struct lcd_panel osk_panel = { + .name = "osk", + .config = LCD_PANEL_TFT, + .video_mode = &mode240x320, + + .init = osk_panel_init, + .cleanup = osk_panel_cleanup, + .enable = osk_panel_enable, + .disable = osk_panel_disable, + .get_caps= osk_panel_get_caps, +}; + diff --git a/drivers/video/omap/lcd_p2.c b/drivers/video/omap/lcd_p2.c new file mode 100644 index 00000000000..64be10f1902 --- /dev/null +++ b/drivers/video/omap/lcd_p2.c @@ -0,0 +1,311 @@ +/* + * File: drivers/video/omap_new/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 "omapfb.h" + +/* + * File: epson-md-tft.h + * + * This file contains definitions for Epsons MD-TF LCD Module + * + * Copyright (C) 2004 MPC-Data Limited (http://www.mpc-data.co.uk) + * Author: Dave Peverley + * + * 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 "debug.h" +#include "../drivers/ssi/omap-uwire.h" + +#define LCD_UWIRE_CS 0 + +static int p2_panel_init(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); + return 0; +} + +static void p2_panel_cleanup(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static int p2_panel_enable(struct lcd_panel *panel) +{ + int i; + unsigned long value; + DBGENTER(1); + + /* 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); + + DBGLEAVE(1); + return 0; +} + +static void p2_panel_disable(struct lcd_panel *panel) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static unsigned long p2_panel_get_caps(struct lcd_panel *panel) +{ + return 0; +} + +static struct lcdc_video_mode mode176x220 = { + .x_res = 176, + .y_res = 220, + .pixel_clock = 12500, + .bpp = 16, + .hsw = 5, + .hfp = 1, + .hbp = 1, + .vsw = 2, + .vfp = 12, + .vbp = 1, + .pcd = 4, + .flags = OMAP_LCDC_INV_PIX_CLOCK, +}; + +struct lcd_panel p2_panel = { + .name = "p2", + .config = LCD_PANEL_TFT, + .video_mode = &mode176x220, + + .init = p2_panel_init, + .cleanup = p2_panel_cleanup, + .enable = p2_panel_enable, + .disable = p2_panel_disable, + .get_caps= p2_panel_get_caps, +}; + diff --git a/drivers/video/omap/omap_lcdc.c b/drivers/video/omap/omap_lcdc.c new file mode 100644 index 00000000000..fdc3af2cdad --- /dev/null +++ b/drivers/video/omap/omap_lcdc.c @@ -0,0 +1,599 @@ +/* + * linux/arch/arm/mach-omap/omap_lcdc.c + * + * OMAP 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 "omapfb.h" +#include "debug.h" + +#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 fb_update_mode update_mode; + unsigned int irq_mask; + struct completion last_frame_complete; + struct completion palette_load_complete; + struct clk *lcd_ck; +} omap_lcdc; + +static void inline enable_irqs(int mask) +{ + omap_lcdc.irq_mask |= mask; +} + +static void inline disable_irqs(int mask) +{ + omap_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 |= omap_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; + + init_completion(&omap_lcdc.last_frame_complete); + 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 last_frame_timeout(unsigned long data) +{ + printk(KERN_ERR "omap_lcdc: timeout waiting for DONE flag\n"); + complete(&omap_lcdc.last_frame_complete); +} + +static void inline wait_for_frame_done(void) +{ + struct timer_list timeout; + + init_timer(&timeout); + timeout.function = last_frame_timeout; + timeout.expires = jiffies + msecs_to_jiffies(500); + add_timer(&timeout); + wait_for_completion(&omap_lcdc.last_frame_complete); + del_timer_sync(&timeout); +} + +static void disable_controller(void) +{ + disable_controller_async(); + wait_for_frame_done(); +} + +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)) { + printk(KERN_ERR "omap_lcdc: resetting " + "(status %#010x,reset count %lu)\n", + status, reset_count); + last_jiffies = jiffies; + } + if (reset_count < 100) { + enable_controller(); + } else { + reset_count = 0; + printk(KERN_ERR "omap_lcdc: too many reset attempts, " + "giving up.\n"); + } +} + +/* Configure the LCD DMA according to the current mode specified by parameters + * in fbdev and fbdev->var. + */ +static void setup_lcd_dma(struct omapfb_device *fbdev) +{ + static const int dma_elem_type[] = { + 0, + OMAP_DMA_DATA_TYPE_S8, + OMAP_DMA_DATA_TYPE_S16, + 0, + OMAP_DMA_DATA_TYPE_S32, + }; + struct fb_var_screeninfo *var = &fbdev->fb_info->var; + unsigned long src; + int esize, xelem, yelem; + + src = fbdev->lcddma_handle + fbdev->vis_frame_org + fbdev->view_org; + switch (var->rotate) { + case 0: + esize = fbdev->mirror || (src & 3) ? 2 : 4; + xelem = var->xres * var->bits_per_pixel / 8 / esize; + yelem = var->yres; + break; + case 90: + case 180: + case 270: + esize = 2; + xelem = var->xres * var->bits_per_pixel / 16; + yelem = var->yres; + break; + default: + BUG(); + return; + } + DBGPRINT(1, "setup_dma: src=%#010x esize=%d xelem=%d yelem=%d\n", + src, esize, xelem, yelem); + omap_set_lcd_dma_b1(src, xelem, yelem, dma_elem_type[esize]); + omap_set_lcd_dma_single_transfer(0); + if (!cpu_is_omap1510()) { + /* Set virtual xres elem size */ + omap_set_lcd_dma_b1_vxres( + fbdev->fb_info->fix.line_length / esize); + /* Setup transformations */ + omap_set_lcd_dma_b1_rotation(var->rotate); + omap_set_lcd_dma_b1_mirror(fbdev->mirror); + omap_set_lcd_dma_b1_scale(fbdev->xscale, fbdev->yscale); + } + omap_setup_lcd_dma(); +} + +static irqreturn_t lcdc_irq_handler(int irq, void *dev_id, + struct pt_regs *fp) +{ + 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(&omap_lcdc.last_frame_complete); + } + if (status & OMAP_LCDC_STAT_LOADED_PALETTE) { + disable_controller_async(); + complete(&omap_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 void omap_lcdc_change_mode(struct omapfb_device *fbdev) +{ + DBGENTER(1); + + omap_stop_lcd_dma(); + disable_controller(); + setup_lcd_dma(fbdev); + enable_controller(); + + DBGLEAVE(1); +} + +/* 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(struct omapfb_device *fbdev) +{ + u8 *palette; + u32 code; + unsigned long size; + unsigned long palette_org; + + DBGENTER(1); + + switch (fbdev->panel->video_mode->bpp) { + case 1: + /* 0 is already set */ + code = 0; + size = 256; + break; + case 2: + code = 0x1000; + size = 256; + break; + case 4: + code = 0x2000; + size = 256; + break; + case 8: + code = 0x3000; + size = 256; + break; + case 12: + case 16: + code = 0x4000; + size = 32; + break; + default: + BUG(); + return; + } + palette_org = MAX_PALETTE_SIZE - size; + palette = fbdev->lcddma_base + palette_org; + memset(palette, 0, size); + *(u32 *)palette = code; + + omap_set_lcd_dma_b1(fbdev->lcddma_handle + palette_org, + size / 4 + 1, 1, OMAP_DMA_DATA_TYPE_S32); + omap_set_lcd_dma_single_transfer(1); + omap_setup_lcd_dma(); + + init_completion(&omap_lcdc.palette_load_complete); + enable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); + set_load_mode(OMAP_LCDC_LOAD_PALETTE); + enable_controller(); + wait_for_completion(&omap_lcdc.palette_load_complete); + /* The controller gets disabled in the irq handler */ + disable_irqs(OMAP_LCDC_IRQ_LOADED_PALETTE); + omap_stop_lcd_dma(); + + DBGLEAVE(1); +} + +static void inline setup_regs(struct omapfb_device *fbdev) +{ + u32 l; + struct lcdc_video_mode *mode = fbdev->panel->video_mode; + int tft = fbdev->panel->config & LCD_PANEL_TFT; + int signal_levels = fbdev->panel->signals; + + l = omap_readl(OMAP_LCDC_CONTROL); + l &= ~OMAP_LCDC_CTRL_LCD_TFT; + l |= tft ? OMAP_LCDC_CTRL_LCD_TFT : 0; + omap_writel(l, OMAP_LCDC_CONTROL); + + l = omap_readl(OMAP_LCDC_TIMING2); + l &= ~(((1 << 6) - 1) << 20); + l |= signal_levels << 20; + omap_writel(l, OMAP_LCDC_TIMING2); + + l = mode->x_res - 1; + l |= (mode->hsw - 1) << 10; + l |= (mode->hfp - 1) << 16; + l |= (mode->hbp - 1) << 24; + omap_writel(l, OMAP_LCDC_TIMING0); + + l = mode->y_res - 1; + l |= (mode->vsw - 1) << 10; + l |= mode->vfp << 16; + l |= mode->vbp << 24; + omap_writel(l, OMAP_LCDC_TIMING1); + + l = omap_readl(OMAP_LCDC_TIMING2); + l &= ~0xff; + + if (!cpu_is_omap730()) + l |= mode->pcd; + + if (machine_is_omap_perseus2()) { + u32 clock1, clock2; + int pcd; + + clock1 = mode->pixel_clock * 1000; + clock2 = clk_get_rate(omap_lcdc.lcd_ck); + + if (clock1 != 0) { + pcd = clock2 / clock1; + if (pcd > 255) + pcd = 0; + } else { + pcd = 0; + } + + if (pcd == 0) + l |= mode->pcd; + else + l |= pcd; + +// printk("%% ck1: %d ck2: %d pcd: %d %%\n",clock1, clock2, pcd); + } + + l |= mode->acb << 8; + if (mode->flags & OMAP_LCDC_INV_PIX_CLOCK) + l |= 1 << 22; + omap_writel(l, OMAP_LCDC_TIMING2); +} + +/* Configure the LCD controller, download the color palette and start a looped + * DMA transfer of the frame image data. */ +static int omap_lcdc_set_update_mode(struct omapfb_device *fbdev, + enum fb_update_mode mode) +{ + int r = 0; + + DBGENTER(1); + + if (mode != omap_lcdc.update_mode) { + switch (mode) { + case FB_AUTO_UPDATE: + setup_regs(fbdev); + load_palette(fbdev); + + /* Setup and start LCD DMA */ + setup_lcd_dma(fbdev); + + set_load_mode(OMAP_LCDC_LOAD_FRAME); + enable_irqs(OMAP_LCDC_IRQ_DONE); + /* This will start the actual DMA transfer */ + enable_controller(); + omap_lcdc.update_mode = mode; + break; + case FB_UPDATE_DISABLED: + disable_controller(); + omap_stop_lcd_dma(); + omap_lcdc.update_mode = mode; + break; + default: + r = -EINVAL; + } + } + + DBGLEAVE(1); + return r; +} + +static enum fb_update_mode omap_lcdc_get_update_mode(struct omapfb_device *fbdev) +{ + return omap_lcdc.update_mode; +} + +static void omap_lcdc_suspend(struct omapfb_device *fbdev) +{ + if (omap_lcdc.update_mode == FB_AUTO_UPDATE) { + disable_controller(); + omap_stop_lcd_dma(); + } +} + +static void omap_lcdc_resume(struct omapfb_device *fbdev) +{ + if (omap_lcdc.update_mode == FB_AUTO_UPDATE) { + setup_regs(fbdev); + load_palette(fbdev); + setup_lcd_dma(fbdev); + set_load_mode(OMAP_LCDC_LOAD_FRAME); + enable_irqs(OMAP_LCDC_IRQ_DONE); + enable_controller(); + } +} + +static int omap_lcdc_init(struct omapfb_device *fbdev) +{ + int r; + u32 l; + int rate; + struct clk *tc_ck; + + DBGENTER(1); + + omap_lcdc.irq_mask = 0; + + l = 0; + omap_writel(l, OMAP_LCDC_CONTROL); + + /* FIXME: + * According to errata some platforms have a clock rate limitiation + */ + omap_lcdc.lcd_ck = clk_get(NULL, "lcd_ck"); + if (IS_ERR(omap_lcdc.lcd_ck)) { + printk(KERN_ERR "omap_lcdc: unable to access LCD clock\n"); + r = PTR_ERR(omap_lcdc.lcd_ck); + goto fail0; + } + + tc_ck = clk_get(NULL, "tc_ck"); + if (IS_ERR(tc_ck)) { + printk(KERN_ERR "omap_lcdc: 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_omap_h3()) + rate /= 3; + r = clk_set_rate(omap_lcdc.lcd_ck, rate); + if (r) { + printk(KERN_ERR "omap_lcdc: failed to adjust LCD rate\n"); + goto fail1; + } + clk_use(omap_lcdc.lcd_ck); + + r = request_irq(OMAP_LCDC_IRQ, lcdc_irq_handler, 0, "omap-lcdc", fbdev); + if (r) { + printk(KERN_ERR "omap_lcdc: unable to get IRQ\n"); + goto fail2; + } + + r = omap_request_lcd_dma(NULL, NULL); + if (r) { + printk(KERN_ERR "omap_lcdc: unable to get LCD DMA\n"); + goto fail3; + } + + printk(KERN_INFO "OMAP LCD controller initialized.\n"); + DBGLEAVE(1); + return 0; +fail3: + free_irq(OMAP_LCDC_IRQ, fbdev); +fail2: + clk_unuse(omap_lcdc.lcd_ck); +fail1: + clk_put(omap_lcdc.lcd_ck); +fail0: + DBGLEAVE(1); + return r; +} + +static void omap_lcdc_cleanup(struct omapfb_device *fbdev) +{ + omap_free_lcd_dma(); + free_irq(OMAP_LCDC_IRQ, fbdev); + clk_unuse(omap_lcdc.lcd_ck); + clk_put(omap_lcdc.lcd_ck); +} + +static void omap_lcdc_get_mem_layout(struct omapfb_device *fbdev, + unsigned long *size, unsigned long *fb_org) +{ + struct lcdc_video_mode *mode = fbdev->panel->video_mode; + + *size = MAX_PALETTE_SIZE; + *fb_org = *size; + *size += mode->x_res * mode->bpp / 8 * mode->y_res; +} + +static unsigned long omap_lcdc_get_caps(struct omapfb_device *fbdev) +{ + return 0; +} + +struct lcd_ctrl omapfb_lcdc_ctrl = { + .name = "internal", + .init = omap_lcdc_init, + .cleanup = omap_lcdc_cleanup, + .get_mem_layout = omap_lcdc_get_mem_layout, + .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, + .change_mode = omap_lcdc_change_mode, +}; + +MODULE_DESCRIPTION("TI OMAP LCDC controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/omap/omapfb.h b/drivers/video/omap/omapfb.h new file mode 100644 index 00000000000..33f20864c6e --- /dev/null +++ b/drivers/video/omap/omapfb.h @@ -0,0 +1,324 @@ +/* + * File: drivers/video/omap_new/omapfb.c + * + * Framebuffer driver for TI OMAP boards + * + * 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. + */ + +#ifndef __OMAPFB_H +#define __OMAPFB_H + +/* IOCTL commands. */ + +#define OMAP_IOW(num, dtype) _IOW('O', num, dtype) +#define OMAP_IOR(num, dtype) _IOR('O', num, dtype) +#define OMAP_IOWR(num, dtype) _IOWR('O', num, dtype) +#define OMAP_IO(num) _IO('O', num) + +#define OMAPFB_FILLRECT OMAP_IOW(0, struct fb_fillrect) +#define OMAPFB_COPYAREA OMAP_IOW(1, struct fb_copyarea) +#define OMAPFB_IMAGEBLIT OMAP_IOW(2, struct fb_image) + +#define OMAPFB_TRANSPARENT_BLIT OMAP_IOW(30, struct fb_image) +#define OMAPFB_MIRROR OMAP_IOW(31, int) +#define OMAPFB_SCALE OMAP_IOW(32, struct fb_scale) +#define OMAPFB_SELECT_VIS_FRAME OMAP_IOW(33, int) +#define OMAPFB_SELECT_SRC_FRAME OMAP_IOW(34, int) +#define OMAPFB_SELECT_DST_FRAME OMAP_IOW(35, int) +#define OMAPFB_GET_FRAME_OFFSET OMAP_IOWR(36, struct fb_frame_offset) +#define OMAPFB_SYNC_GFX OMAP_IO(37) +#define OMAPFB_VSYNC OMAP_IO(38) +#define OMAPFB_LATE_ACTIVATE OMAP_IO(39) +#define OMAPFB_SET_UPDATE_MODE OMAP_IOW(40, enum fb_update_mode) +#define OMAPFB_UPDATE_WINDOW OMAP_IOW(41, struct fb_update_window) +#define OMAPFB_GET_CAPS OMAP_IOR(42, unsigned long) +#define OMAPFB_GET_UPDATE_MODE OMAP_IOW(43, enum fb_update_mode) +#define OMAPFB_GET_GFX_STATUS OMAP_IOR(44, unsigned long) + +#define FBCAPS_GENERIC_MASK 0x00000fff +#define FBCAPS_LCDC_MASK 0x00fff000 +#define FBCAPS_PANEL_MASK 0xff000000 + +#define FBCAPS_MANUAL_UPDATE 0x00001000 +#define FBCAPS_SET_BACKLIGHT 0x01000000 + +enum omapfb_gfx_status { + OMAPFB_GFX_STATUS_OK = 0, + OMAPFB_GFX_STATUS_CHANGED +}; + +#define OMAPFB_UPDATE_FAILED 0x01 +#define OMAPFB_FILLRECT_FAILED 0x02 +#define OMAPFB_COPYAREA_FAILED 0x04 +#define OMAPFB_IMGBLIT_FAILED 0x08 + +struct fb_copyarea_ext { + __u32 dx; + __u32 dy; + __u32 width; + __u32 height; + __u32 sx; + __u32 sy; + __u32 trans_color; + __u32 rev_dir; +}; + +struct fb_scale { + unsigned int xscale, yscale; +}; + +struct fb_frame_offset { + unsigned int idx; + unsigned long offset; +}; + +struct fb_update_window { + unsigned int x, y; + unsigned int width, height; +}; + +enum fb_update_mode { + FB_UPDATE_DISABLED = 0, + FB_AUTO_UPDATE, + FB_MANUAL_UPDATE +}; + +#ifdef __KERNEL__ + +#include +#include +#include + +#define OMAPFB_DEVICE "omapfb" +#define OMAPFB_DRIVER "omapfb" + +#define PRNERR(fmt, args...) printk(KERN_ERR OMAPFB_DRIVER ": " fmt, ## args) + +#define GFX_FIFO_SIZE 2 + +#define LCD_PANEL_TFT 0x01 + +#define OMAP_LCDC_INV_VSYNC 0x01 +#define OMAP_LCDC_INV_HSYNC 0x02 +#define OMAP_LCDC_INV_PIX_CLOCK 0x04 +#define OMAP_LCDC_INV_OUTPUT_EN 0x08 +#define OMAP_LCDC_HSVS_RISING_EDGE 0x10 +#define OMAP_LCDC_HSVS_OPPOSITE 0x20 + +struct lcdc_video_mode { + u16 x_res, y_res; + u32 pixel_clock; /* In kHz */ + int bpp; + u8 hsw; /* Horizontal synchronization pulse width */ + u8 hfp; /* Horizontal front porch */ + u8 hbp; /* Horizontal back porch */ + u8 vsw; /* Vertical synchronization pulse width */ + u8 vfp; /* Vertical front porch */ + u8 vbp; /* Vertical back porch */ + u8 acb; /* ac-bias pin frequency */ + u8 pcd; /* Pixel clock divider (this will change) */ + u8 flags; +}; + +struct lcd_panel { + const char *name; + int config; + int signals; + struct lcdc_video_mode *video_mode; + + int (*init) (struct lcd_panel *panel); + void (*cleanup) (struct lcd_panel *panel); + int (*enable) (struct lcd_panel *panel); + void (*disable) (struct lcd_panel *panel); + unsigned long (*get_caps)(struct lcd_panel *panel); + int (*set_bklight_level)(struct lcd_panel *panel, + unsigned int level); + unsigned int (*get_bklight_level)(struct lcd_panel *panel); + unsigned int (*get_bklight_max) (struct lcd_panel *panel); +}; + +struct omapfb_device; + +struct lcd_ctrl { + const char *name; + void *data; + int (*init) (struct omapfb_device *fbdev); + void (*cleanup) (struct omapfb_device *fbdev); + void (*get_mem_layout) (struct omapfb_device *fbdev, + unsigned long *size, + unsigned long *fb_org); + unsigned long (*get_caps) (struct omapfb_device *fbdev); + int (*set_update_mode)(struct omapfb_device *fbdev, + enum fb_update_mode mode); + enum fb_update_mode (*get_update_mode)(struct omapfb_device *fbdev); + int (*update_window) (struct omapfb_device *fbdev, + struct fb_update_window *win); + void (*suspend) (struct omapfb_device *fbdev); + void (*resume) (struct omapfb_device *fbdev); + void (*change_mode) (struct omapfb_device *fbdev); +}; + +enum omapfb_state { + OMAPFB_DISABLED = 0, + OMAPFB_SUSPENDED= 99, + OMAPFB_ACTIVE = 100 +}; + +struct gfx_lchannel { + int lch_num; + struct gfx_lchannel *next, *prev; +}; + +struct gfx_dma { + spinlock_t spinlock; + + struct completion sync_complete; /* Signalled when the + fifo gets empty */ + volatile int done; /* Indicates the + end of a DMA chain + transfer */ + struct gfx_lchannel fifo[GFX_FIFO_SIZE]; + struct gfx_lchannel *f_head, *f_tail; /* Process and insert + points on the + fifo */ + struct gfx_lchannel *f_chain_end; /* Points to the new + chain end */ + struct semaphore f_free; /* # of free lch-s */ + int f_run; /* # of active lch-s */ + int f_wait; /* # of lch-s + waiting */ + struct tasklet_struct dequeue_tasklet; /* Processes new DMA + chain transfers on + the fifo */ +}; + +#define OMAPFB_RQUEUE_SIZE 20 + +struct omapfb_fillrect_params +{ + struct fb_info *fbi; + struct fb_fillrect rect; +}; + +struct omapfb_copyarea_params +{ + struct fb_info *fbi; + struct fb_copyarea_ext area; +}; + +struct omapfb_update_window_params +{ + struct fb_info *fbi; + struct fb_update_window win; +}; + +struct omapfb_imageblit_params +{ + struct fb_info *fbi; + struct fb_image image; + int flags; +}; + +union req_params +{ + /* All possible requests are to be listed here */ + struct omapfb_fillrect_params fillrect; + struct omapfb_copyarea_params copyarea; + struct omapfb_update_window_params update_window; + struct omapfb_imageblit_params imageblit; +}; + +struct omapfb_request +{ + struct list_head entry; + int (*function)(void *par); + union req_params par; +}; + +struct omapfb_rqueue +{ + spinlock_t lock; + struct list_head free_list; + struct list_head pending_list; + struct completion rqueue_empty; + struct semaphore free_sema; + struct omapfb_request req_pool[OMAPFB_RQUEUE_SIZE]; + struct work_struct work; + unsigned long status; +}; + +struct omapfb_device { + int state; + int ext_lcdc; /* Using external + LCD controller */ + void *lcddma_base; /* MPU virtual + address */ + dma_addr_t lcddma_handle; /* Bus physical + address */ + unsigned long lcddma_mem_size; + unsigned long palette_org; /* Palette offset into + lcddma_base/handle */ + unsigned long frame0_org, frame1_org; /* Frame offsets for + back and front + frame buffers into + lcddma_base/handle */ + unsigned long vis_frame_org; /* Offset of visible + frame buffer. + = frame0/1_org */ + unsigned long src_frame_org; /* Offset of source + frame for drawing + operations. + = frame0/1_org */ + unsigned long dst_frame_org; /* Offset of dest + frame for drawing + operations. + = frame0/1_org */ + unsigned long view_org; /* View offset into + lcddma_base/handle+ + vis_frame_org. + Used for panning */ + unsigned long palette_size; + int xscale, yscale, mirror; /* transformations. + rotate is stored in + fb_info->var */ + + u32 pseudo_palette[17]; + + struct gfx_dma gfx; /* Accelerator */ + struct omapfb_rqueue rqueue; + struct lcd_panel *panel; /* LCD panel */ + struct lcd_ctrl *ctrl; /* LCD controller */ + + struct fb_info *fb_info; /* Linux fbdev + framework data */ + struct device *dev; +}; + +extern struct lcd_panel h3_panel; +extern struct lcd_panel h2_panel; +extern struct lcd_panel p2_panel; +extern struct lcd_panel osk_panel; +extern struct lcd_panel innovator1610_panel; +extern struct lcd_panel innovator1510_panel; + +extern struct lcd_ctrl omapfb_lcdc_ctrl; + +#endif /* __KERNEL__ */ + +#endif /* __OMAPFB_H */ diff --git a/drivers/video/omap/omapfb_main.c b/drivers/video/omap/omapfb_main.c new file mode 100644 index 00000000000..aeb66c8fbb0 --- /dev/null +++ b/drivers/video/omap/omapfb_main.c @@ -0,0 +1,2269 @@ +/* + * 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 +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "omapfb.h" + +// #define OMAPFB_DBG_FIFO 1 +// #define OMAPFB_DBG 1 + +#include "debug.h" + +#define COPY_MODE_REV_DIR 0x01 +#define COPY_MODE_TRANSPARENT 0x02 +#define COPY_MODE_IMAGE 0x04 + +#ifdef OMAPFB_DBG_FIFO +struct gfx_stat { + unsigned long f_run[GFX_FIFO_SIZE]; +} stat; +#endif + +static unsigned int def_accel; +static unsigned long def_vram; +static long def_vxres; +static 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 caps_table_struct { + unsigned long flag; + const char *name; +} omapfb_caps_table[] = { + { FBCAPS_MANUAL_UPDATE, "manual update" }, + { FBCAPS_SET_BACKLIGHT, "backlight setting" }, +}; + +/* + * --------------------------------------------------------------------------- + * LCD panel + * --------------------------------------------------------------------------- + */ +static struct lcd_panel *panels[] = { +#ifdef CONFIG_MACH_OMAP_H2 + &h2_panel, +#endif +#ifdef CONFIG_MACH_OMAP_H3 + &h3_panel, +#endif +#ifdef CONFIG_MACH_OMAP_PERSEUS2 + &p2_panel, +#endif +#ifdef CONFIG_MACH_OMAP_OSK + &osk_panel, +#endif +#ifdef CONFIG_MACH_OMAP_INNOVATOR +#ifdef CONFIG_ARCH_OMAP1510 + &innovator1510_panel, +#endif +#ifdef CONFIG_ARCH_OMAP16XX + &innovator1610_panel, +#endif +#endif +}; + +static struct lcd_ctrl *ctrls[] = { +#ifdef CONFIG_FB_OMAP_INTERNAL_LCDC + &omapfb_lcdc_ctrl, +#endif +}; + +static struct omapfb_request *omapfb_rqueue_alloc_req(struct omapfb_rqueue *rq) +{ + struct omapfb_request *req; + + down(&rq->free_sema); + spin_lock(&rq->lock); + req = list_entry(rq->free_list.next, struct omapfb_request, entry); + list_del(&req->entry); + spin_unlock(&rq->lock); + return req; +} + +static void __omapfb_rqueue_free_req(struct omapfb_rqueue *rq, + struct omapfb_request *req) +{ + list_add(&req->entry, &rq->free_list); + up(&rq->free_sema); +} + +static void omapfb_rqueue_process(void *data) +{ + struct omapfb_rqueue *rq = data; + + spin_lock(&rq->lock); + while (!list_empty(&rq->pending_list)) { + struct omapfb_request *req; + + req = list_entry(rq->pending_list.next, + struct omapfb_request, entry); + list_del(&req->entry); + spin_unlock(&rq->lock); + rq->status |= req->function(&req->par); + spin_lock(&rq->lock); + __omapfb_rqueue_free_req(rq, req); + } + complete(&rq->rqueue_empty); + spin_unlock(&rq->lock); +} + +static void omapfb_rqueue_schedule_req(struct omapfb_rqueue *rq, + struct omapfb_request *req) +{ + spin_lock(&rq->lock); + list_add_tail(&req->entry, &rq->pending_list); + spin_unlock(&rq->lock); + schedule_work(&rq->work); +} + +static void omapfb_rqueue_sync(struct omapfb_rqueue *rq) +{ + int wait = 0; + + spin_lock(&rq->lock); + if (!list_empty(&rq->pending_list)) { + wait = 1; + init_completion(&rq->rqueue_empty); + } + spin_unlock(&rq->lock); + if (wait) + wait_for_completion(&rq->rqueue_empty); +} + +static void omapfb_rqueue_reset(struct omapfb_rqueue *rq, unsigned long *status) +{ + omapfb_rqueue_sync(rq); + spin_lock(&rq->lock); + *status = rq->status; + rq->status = 0; + spin_unlock(&rq->lock); +} + +static void omapfb_rqueue_init(struct omapfb_rqueue *rq) +{ + int i; + + spin_lock_init(&rq->lock); + sema_init(&rq->free_sema, OMAPFB_RQUEUE_SIZE); + init_completion(&rq->rqueue_empty); + INIT_WORK(&rq->work, omapfb_rqueue_process, rq); + INIT_LIST_HEAD(&rq->free_list); + INIT_LIST_HEAD(&rq->pending_list); + for (i = 0; i < OMAPFB_RQUEUE_SIZE; i++) + list_add(&rq->req_pool[i].entry, &rq->free_list); +} + +static void omapfb_rqueue_cleanup(struct omapfb_rqueue *rq) +{ +} + +/* + * --------------------------------------------------------------------------- + * gfx DMA + * --------------------------------------------------------------------------- + */ +/* Get a new logical channel from the gfx fifo. */ +static void inline gfxdma_get_lch(struct gfx_dma *gfx, int *lch) +{ + DBGENTER(3); + + down(&gfx->f_free); + + spin_lock_bh(&gfx->spinlock); + + *lch = gfx->f_tail->lch_num; + gfx->f_tail = gfx->f_tail->next; + + spin_unlock_bh(&gfx->spinlock); + + DBGLEAVE(3); +} + +/* Set basic transfer params for the logical channel */ +static inline void gfxdma_set_lch_params(int lch, int data_type, + int enumber, int fnumber, + unsigned long src_start, int src_amode, + unsigned long dst_start, int dst_amode) +{ + omap_set_dma_transfer_params(lch, data_type, enumber, fnumber, 0); + omap_set_dma_src_params(lch, OMAP_DMA_PORT_EMIFF, + src_amode, src_start); + omap_set_dma_dest_params(lch, OMAP_DMA_PORT_EMIFF, + dst_amode, dst_start); +} + +/* Set element and frame indexes for the logical channel, to support + * image transformations + */ +static inline void gfxdma_set_lch_index(int lch, int src_eidx, int src_fidx, + int dst_eidx, int dst_fidx) +{ + omap_set_dma_src_index(lch, src_eidx, src_fidx); + omap_set_dma_dest_index(lch, dst_eidx, dst_fidx); +} + +/* Set color parameter for the logical channel, to support constant fill and + * transparent copy operations + */ +static inline void gfxdma_set_lch_color(int lch, u32 color, + enum omap_dma_color_mode mode) +{ + omap_set_dma_color_mode(lch, mode, color); +} + + +/* Start a new transfer consisting of a single DMA logical channel, + * or a chain (f_run > 1). Can be called in interrupt context. + * gfx->spinlock must be held. + */ +static void inline gfxdma_start_chain(struct gfx_dma *gfx) +{ + DBGENTER(3); + + gfx->f_run = gfx->f_wait; +#ifdef OMAPFB_DBG_FIFO + stat.f_run[gfx->f_run - 1]++; +#endif + gfx->f_wait = 0; + omap_enable_dma_irq(gfx->f_chain_end->lch_num, OMAP_DMA_BLOCK_IRQ); + /* Let it go */ + DBGPRINT(1, "start %d\n", gfx->f_head->lch_num); + omap_start_dma(gfx->f_head->lch_num); + gfx->f_chain_end = gfx->f_chain_end->next; + + DBGLEAVE(3); +} + +/* Enqueue a logical channel, that has been set up. If no other transfers + * are pending start this new one right away. */ +static void inline gfxdma_enqueue(struct gfx_dma *gfx, int lch) +{ + DBGENTER(3); + + spin_lock_bh(&gfx->spinlock); + DBGPRINT(3, "run:%d wait:%d\n", gfx->f_run, gfx->f_wait); + if (gfx->f_wait) { + DBGPRINT(1, "link %d, %d\n", gfx->f_chain_end->lch_num, lch); + omap_dma_link_lch(gfx->f_chain_end->lch_num, lch); + gfx->f_chain_end = gfx->f_chain_end->next; + } + omap_disable_dma_irq(lch, OMAP_DMA_BLOCK_IRQ); + + gfx->f_wait++; + + if (!gfx->f_run) + gfxdma_start_chain(gfx); + spin_unlock_bh(&gfx->spinlock); + + DBGLEAVE(3); +} + +/* Called by DMA core when the last transfer ended, or there is an error + * condition. We dispatch handling of the end of transfer case to a tasklet. + * Called in interrupt context. + */ +static void gfxdma_handler(int lch, u16 ch_status, void *data) +{ + struct gfx_dma *gfx = (struct gfx_dma *)data; + int done = 0; + + DBGENTER(3); + + DBGPRINT(4, "lch=%d status=%#010x\n", lch, ch_status); + if (unlikely(ch_status & (OMAP_DMA_TOUT_IRQ | OMAP_DMA_DROP_IRQ))) { + PRNERR("gfx DMA error. status=%#010x\n", ch_status); + done = 1; + } else if (likely(ch_status & OMAP_DMA_BLOCK_IRQ)) + done = 1; + if (likely(done)) { + gfx->done = 1; + tasklet_schedule(&gfx->dequeue_tasklet); + } + + DBGLEAVE(3); +} + +/* Let the DMA core know that the last transfer has ended. If there are + * pending transfers in the fifo start them now. + * Called in interrupt context. + */ +static void gfxdma_dequeue_tasklet(unsigned long data) +{ + struct gfx_dma *gfx = (struct gfx_dma *)data; + struct gfx_lchannel *f_chain; + + DBGENTER(3); + + /* start an already programmed transfer + */ + while (likely(gfx->done)) { + gfx->done = 0; + spin_lock(&gfx->spinlock); + f_chain = gfx->f_head; + omap_stop_dma(f_chain->lch_num); + /* Would be better w/o a loop.. */ + while (gfx->f_run--) { + if (gfx->f_run) + omap_dma_unlink_lch(f_chain->lch_num, + f_chain->next->lch_num); + f_chain = f_chain->next; + up(&gfx->f_free); + } + gfx->f_run = 0; + gfx->f_head = f_chain; + if (likely(gfx->f_wait)) + gfxdma_start_chain(gfx); + else + complete(&gfx->sync_complete); + spin_unlock(&gfx->spinlock); + } + + DBGLEAVE(3); +} + +/* Wait till any pending transfers end. */ +static void gfxdma_sync(struct gfx_dma *gfx) +{ + int wait = 0; + + DBGENTER(1); + + for (;;) { + spin_lock_bh(&gfx->spinlock); + if (gfx->f_run + gfx->f_wait) { + wait = 1; + init_completion(&gfx->sync_complete); + } + spin_unlock_bh(&gfx->spinlock); + if (wait) { + wait_for_completion(&gfx->sync_complete); + wait = 0; + } else + break; + } + + DBGLEAVE(1); +} + +/* Initialize the gfx DMA object. + * Allocate DMA logical channels according to the fifo size. + * Set the channel parameters that will be the same for all transfers. + */ +static int gfxdma_init(struct gfx_dma *gfx) +{ + int r = 0; + int i; + + DBGENTER(1); + + for (i = 0; i < GFX_FIFO_SIZE; i++) { + int next_idx; + int lch_num; + + r = omap_request_dma(0, OMAPFB_DRIVER, + gfxdma_handler, gfx, &lch_num); + if (r) { + int j; + + PRNERR("unable to get GFX DMA %d\n", i); + for (j = 0; j < i; j++) + omap_free_dma(lch_num); + r = -1; + goto exit; + } + omap_set_dma_src_data_pack(lch_num, 1); + omap_set_dma_src_burst_mode(lch_num, OMAP_DMA_DATA_BURST_4); + omap_set_dma_dest_data_pack(lch_num, 1); + omap_set_dma_dest_burst_mode(lch_num, OMAP_DMA_DATA_BURST_4); + + gfx->fifo[i].lch_num = lch_num; + + next_idx = i < GFX_FIFO_SIZE - 1 ? i + 1 : 0; + gfx->fifo[next_idx].prev = &gfx->fifo[i]; + gfx->fifo[i].next = &gfx->fifo[next_idx]; + } + gfx->f_head = gfx->f_tail = gfx->f_chain_end = &gfx->fifo[0]; + sema_init(&gfx->f_free, GFX_FIFO_SIZE); + + spin_lock_init(&gfx->spinlock); + + tasklet_init(&gfx->dequeue_tasklet, gfxdma_dequeue_tasklet, + (unsigned long)gfx); + + init_completion(&gfx->sync_complete); +exit: + DBGLEAVE(1); + return r; +} + +/* Clean up the gfx DMA object */ +static void gfxdma_cleanup(struct gfx_dma *gfx) +{ + int i; + + DBGENTER(1); + + for (i = 0; i < GFX_FIFO_SIZE; i++) + omap_free_dma(gfx->fifo[i].lch_num); + + DBGLEAVE(1); +} + +/* + * --------------------------------------------------------------------------- + * 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) +{ + unsigned long mem_size; + int r; + + DBGENTER(1); + + r = fbdev->ctrl->init(fbdev); + if (r < 0) + goto exit; + fbdev->ctrl->get_mem_layout(fbdev, &mem_size, &fbdev->frame0_org); + + if (def_vram) { + if (mem_size > def_vram) { + PRNERR("specified frame buffer memory too small\n"); + r = -ENOMEM; + goto cleanup_ctrl; + } + mem_size = def_vram; + } + fbdev->lcddma_mem_size = PAGE_SIZE << get_order(mem_size); + fbdev->lcddma_base = dma_alloc_writecombine(fbdev->dev, + fbdev->lcddma_mem_size, + &fbdev->lcddma_handle, + GFP_KERNEL); + if (fbdev->lcddma_base == NULL) { + PRNERR("unable to allocate fb DMA memory\n"); + r = -ENOMEM; + goto cleanup_ctrl; + } + + memset(fbdev->lcddma_base, 0, fbdev->lcddma_mem_size); + + DBGPRINT(1, "lcddma_base=%#10x lcddma_handle=%#10x lcddma_mem_size=%d" + "palette_size=%d frame0_org=%d palette_org=%d\n", + fbdev->lcddma_base, fbdev->lcddma_handle, + fbdev->lcddma_mem_size, + fbdev->palette_size, + fbdev->frame0_org, fbdev->palette_org); + + DBGLEAVE(1); + return 0; +cleanup_ctrl: + fbdev->ctrl->cleanup(fbdev); +exit: + DBGLEAVE(1); + return r; +} + +static void ctrl_cleanup(struct omapfb_device *fbdev) +{ + fbdev->ctrl->cleanup(fbdev); + dma_free_writecombine(fbdev->dev, fbdev->lcddma_mem_size, + fbdev->lcddma_base, fbdev->lcddma_handle); +} + +static void ctrl_change_mode(struct omapfb_device *fbdev) +{ + fbdev->ctrl->change_mode(fbdev); +} + +/* + * --------------------------------------------------------------------------- + * 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) +{ + DBGENTER(1); + +#ifdef OMAPFB_DBG_FIFO + memset(&stat, 0, sizeof(stat)); +#endif + + DBGLEAVE(1); + return 0; +} + +/* 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) +{ + struct omapfb_device *dev = (struct omapfb_device *)info->par; + int sync = 0; + + DBGENTER(1); + + spin_lock_bh(&dev->gfx.spinlock); + if (dev->gfx.f_run) + sync = 1; + spin_unlock_bh(&dev->gfx.spinlock); + if (sync) { + gfxdma_sync(&dev->gfx); + } +#ifdef OMAPFB_DBG_FIFO + { + int i; + for (i = 0; i < GFX_FIFO_SIZE; i++) + printk(KERN_INFO "f_run[%d]=%lu\n", i + 1, + stat.f_run[i]); + } +#endif + + DBGLEAVE(1); + 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 omapfb_setcolreg(u_int regno, u_int red, u_int green, + u_int blue, u_int transp, + struct fb_info *info) +{ + u16 pal; + int r = 0; + + DBGENTER(2); + + if (regno >= 16) { + r = -1; + goto exit; + } + pal = ((red >> 11) << 11) | ((green >> 10) << 5) | (blue >> 11); + ((u32 *)(info->pseudo_palette))[regno] = pal; + +exit: + DBGLEAVE(2); + return r; +} + +static int omapfb_suspend(struct device *dev, pm_message_t mesg, u32 level); +static int omapfb_resume(struct device *dev, u32 level); + +static int omapfb_blank(int blank, struct fb_info *info) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)info->par; + int r = 0; + + DBGENTER(1); + switch (blank) { + case VESA_NO_BLANKING: + omapfb_resume(fbdev->dev, 0); + break; + case VESA_POWERDOWN: + omapfb_suspend(fbdev->dev, 0, 0); + break; + default: + r = -EINVAL; + } + + DBGLEAVE(1); + return r; +} + +/* Setup a constant fill DMA transfer. Destination must be elem size aligned. */ +static inline void fill_block(struct omapfb_device *fbdev, + unsigned long dst, unsigned long enumber, + unsigned long height, int esize, u32 color) +{ + unsigned long fidx; + int lch; + + DBGPRINT(2, "dst:%#010x enumber:%d height:%d esize:%d color:%#010x\n", + dst, enumber, height, esize, color); + fidx = fbdev->fb_info->fix.line_length - enumber * esize + 1; + gfxdma_get_lch(&fbdev->gfx, &lch); + gfxdma_set_lch_params(lch, dma_elem_type[esize], enumber, height, + 0, OMAP_DMA_AMODE_CONSTANT, + dst, OMAP_DMA_AMODE_DOUBLE_IDX); + gfxdma_set_lch_index(lch, 0, 0, 1, fidx); + gfxdma_set_lch_color(lch, color, OMAP_DMA_CONSTANT_FILL); + gfxdma_enqueue(&fbdev->gfx, lch); + + DUMP_DMA_REGS(lch); +} + +/* Fill the specified rectangle with a solid color. + * ROP_XOR and bpp<8 can't be handled by the DMA hardware. + * When frame flipping is in effect use the destination frame. + * We'll make our best to use the largest possible elem size, doing the fill + * in more parts if alignment requires us to do so. + */ +static int omapfb_fillrect(void *data) +{ + struct omapfb_fillrect_params *par = data; + struct omapfb_device *fbdev = (struct omapfb_device *)par->fbi->par; + + int dx = par->rect.dx, dy = par->rect.dy; + int vxres = par->fbi->var.xres_virtual; + int vyres = par->fbi->var.yres_virtual; + int width = par->rect.width, height = par->rect.height; + unsigned long dst; + u32 color; + int bpp; + int enumber, esize; + int r = 0; + + DBGENTER(2); + bpp = par->fbi->var.bits_per_pixel; + /* bpp < 8 is tbd. + * We can't do ROP_XOR with DMA + * If IRQs are disabled we can't use DMA + */ + if (bpp < 8 || par->rect.rop == ROP_XOR || irqs_disabled()) { + r = OMAPFB_FILLRECT_FAILED; + goto exit; + } + /* Clipping */ + if (!width || !height || dx > vxres || dy > vyres) + goto exit; + if (dx + width > vxres) + width = vxres - dx; + if (dy + height > vyres) + height = vyres - dy; + + if (bpp == 12) + bpp = 16; + width = width * bpp / 8; + + dst = fbdev->lcddma_handle + fbdev->dst_frame_org; + dst += dx * bpp / 8 + dy * par->fbi->fix.line_length; + + color = par->rect.color; + switch (bpp) { + case 8: + color |= color << 8; + /* Fall through */ + case 16: + color |= color << 16; + } + + if ((dst & 3) || width < 4) { + if (!(dst & 1) && width > 1) { + esize = 2; + enumber = 1; + width -= 2; + } else { + esize = 1; + enumber = 4 - (esize & 3); + if (enumber > width) + enumber = width; + width -= enumber; + } + fill_block(fbdev, dst, enumber, height, esize, color); + dst = (dst + 3) & ~3; + } + if (width) { + enumber = width / 4; + fill_block(fbdev, dst, enumber, height, 4, color); + dst += enumber * 4; + width -= enumber * 4; + } + if (width) { + if (width == 2) { + esize = 2; + enumber = 1; + } else { + esize = 1; + enumber = width; + } + fill_block(fbdev, dst, enumber, height, esize, color); + } + +exit: + DBGLEAVE(2); + return r; +} + +static int omapfb_schedule_fillrect(struct fb_info *fbi, + const struct fb_fillrect *rect) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + struct omapfb_request *req; + + if ((req = omapfb_rqueue_alloc_req(&fbdev->rqueue)) == NULL) + return -ENOMEM; + req->function = omapfb_fillrect; + req->par.fillrect.fbi = fbi; + req->par.fillrect.rect = *rect; + omapfb_rqueue_schedule_req(&fbdev->rqueue, req); + return fbdev->rqueue.status ? OMAPFB_GFX_STATUS_CHANGED : 0; +} + +/* Setup a gfx DMA transfer to a rectangular area. + * A color parameter can be specified for transparent copy. + * Transfer direction can be setup to use either incremental or decremental + * addresses. + * Source and destination must be elem size aligned. + */ +static inline void transfer_block(struct omapfb_device *fbdev, + unsigned long src, unsigned long dst, + unsigned long img_width, + unsigned long enumber, unsigned long height, + int esize, u32 trans_color, int flags) +{ + s16 eidx; + s16 s_fidx, d_fidx; + int lch; + + eidx = 1; + s_fidx = img_width - enumber * esize + 1; + d_fidx = fbdev->fb_info->fix.line_length - enumber * esize + 1; + if (flags & COPY_MODE_REV_DIR) { + eidx = -2 * esize + 1; + s_fidx = -s_fidx + eidx + 1; + d_fidx = -d_fidx + eidx + 1; + } + + DBGPRINT(2, "src:%#010x dst:%#010x enumber:%d height:%d " + "esize:%d eidx:%d s_fidx:%d d_fidx bg_color:%#010x flags:%d\n", + src, dst, enumber, height, esize, eidx, s_fidx, d_fidx, + bg_color, flags); + + gfxdma_get_lch(&fbdev->gfx, &lch); + gfxdma_set_lch_params(lch, dma_elem_type[esize], enumber, height, + src, OMAP_DMA_AMODE_DOUBLE_IDX, + dst, OMAP_DMA_AMODE_DOUBLE_IDX); + gfxdma_set_lch_index(lch, eidx, s_fidx, eidx, d_fidx); + if (flags & COPY_MODE_TRANSPARENT) + gfxdma_set_lch_color(lch, trans_color, + OMAP_DMA_TRANSPARENT_COPY); + else + gfxdma_set_lch_color(lch, 0, OMAP_DMA_COLOR_DIS); + gfxdma_enqueue(&fbdev->gfx, lch); + + DUMP_DMA_REGS(lch); +} + +/* Copy a rectangular area or an image to another rectangular area. + * A color parameter can be specified for transparent copy. + * Transfer direction can be setup to use either incremental or decremental + * addresses. + * Currently both source and destination area must be entirely contained in + * frame buffer memory. + * The largest possible transfer elem size will be determined according to + * source and destination address alignment, dividing the transfer into more + * parts if necessary. + */ +static inline void copy_data(struct omapfb_device *fbdev, + unsigned long src, unsigned long dst, + unsigned long width, unsigned long height, + u32 trans_color, int flags) +{ + struct fb_info *fbi = fbdev->fb_info; + int esize, stripe_esize; + int step, rest, enumber; + unsigned long img_width; + static const int esize_arr[] = {4, 1, 2, 1}; + int rev; + + /* Check alignment constraints */ + esize = esize_arr[(src ^ dst) & 3]; + + rev = flags & COPY_MODE_REV_DIR; + if (rev) { + rest = src & (esize - 1); + if (rest > width) + rest = width; + src -= rest ? rest : esize; + dst -= rest ? rest : esize; + } else { + rest = esize - (src & (esize - 1)); + if (rest > width) + rest = width; + } + if (width < esize) + rest = width; + + img_width = flags & COPY_MODE_IMAGE ? width : fbi->fix.line_length; + + DBGPRINT(2, "\nrev=%d src=%#010lx dst=%#010lx \n" + "esize=%d width=%d rest=%d\n", + rev, src, dst, esize, width, rest); + if (rest) { + /* Transfer this unaligned stripe, so that afterwards + * we have both src and dst 16bit or 32bit aligned. + */ + if (rest == 2) { + /* Area body is 32bit aligned */ + stripe_esize = 2; + enumber = 1; + step = rev ? -esize : 2; + width -= 2; + } else { + stripe_esize = 1; + enumber = rest; + step = rev ? -esize : rest; + } + transfer_block(fbdev, src, dst, img_width, enumber, height, + stripe_esize, trans_color, flags); + src += step; + dst += step; + } + if (width) { + /* Transfer area body */ + enumber = (width & ~(esize - 1)) / esize; + transfer_block(fbdev, src, dst, img_width, enumber, height, + esize, trans_color, flags); + step = enumber * esize; + width -= step; + if (rev) + step = -step + esize - width; + src += step; + dst += step; + } + if (width) { + /* Transfer the remaining unaligned stripe */ + if (width == 2) { + stripe_esize = 2; + enumber = 1; + } else { + stripe_esize = 1; + enumber = width; + } + transfer_block(fbdev, src, dst, img_width, enumber, height, + stripe_esize, trans_color, flags); + } + + DBGLEAVE(2); +} + +/* Copy a rectangular area in the frame buffer to another rectangular area. + * Calculate the source and destination addresses. + * Transfer direction will be determined taking care of possible area + * overlapping. + * Currently both source and destination area must be entirely contained in + * frame buffer memory, in case of frame flipping source and destination frame + * respectively. + */ +static int omapfb_copyarea(void *data) +{ + struct omapfb_copyarea_params *par = data; + struct omapfb_device *fbdev = (struct omapfb_device *)par->fbi->par; + + int width = par->area.width, height = par->area.height; + int sx = par->area.sx, sy = par->area.sy; + int dx = par->area.dx, dy = par->area.dy; + unsigned long dst, dst_ofs, src, src_ofs; + unsigned long end_ofs; + int bpp = par->fbi->var.bits_per_pixel; + int flags; + int r = 0; + + DBGENTER(2); + + if (!width || !height) + goto exit; + + /* Bpp < 8 is tbd. If IRQs are disabled we can't use DMA */ + if (bpp < 8 || irqs_disabled()) { + r = OMAPFB_COPYAREA_FAILED; + goto exit; + } + + src = fbdev->lcddma_handle; + dst = src; + src_ofs = fbdev->src_frame_org + sx * bpp / 8 + + sy * par->fbi->fix.line_length; + dst_ofs = fbdev->dst_frame_org + dx * bpp / 8 + + dy * par->fbi->fix.line_length; + end_ofs = (height - 1) * par->fbi->fix.line_length + width * bpp / 8; + src += src_ofs; + dst += dst_ofs; + + DBGPRINT(2, "src:%#010lx dst:%#010lx end_ofs:%#010lx\n", + src, dst, end_ofs); + + /* Currently we support only transfers where both source and destination + * area is contained entirely in fbmem. This is because of DMA memory + * constraints. + */ + if (src_ofs + end_ofs > fbdev->lcddma_mem_size || + dst_ofs + end_ofs > fbdev->lcddma_mem_size) { + r = OMAPFB_COPYAREA_FAILED; + goto exit; + } + + flags = 0; + if (par->area.rev_dir) { + flags = COPY_MODE_REV_DIR; + src += end_ofs; + dst += end_ofs; + } + if (par->area.trans_color != -1) + flags |= COPY_MODE_TRANSPARENT; + + width = width * bpp / 8; + copy_data(fbdev, src, dst, width, height, par->area.trans_color, flags); +exit: + DBGLEAVE(2); + return r; +} + +static int omapfb_schedule_copyarea(struct fb_info *fbi, + const struct fb_copyarea_ext *area) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + struct omapfb_request *req; + + if ((req = omapfb_rqueue_alloc_req(&fbdev->rqueue)) == NULL) + return -ENOMEM; + req->function = omapfb_copyarea; + req->par.copyarea.fbi = fbi; + req->par.copyarea.area = *area; + omapfb_rqueue_schedule_req(&fbdev->rqueue, req); + return fbdev->rqueue.status ? OMAPFB_GFX_STATUS_CHANGED : 0; +} + +/* Copy an image to a rectangular area in the frame buffer. + * A color parameter can be specified for transparent copy. + * Calculate the source and destination addresses. + * Transfer direction will be determined taking care of possible area + * overlapping. + * Currently both source and destination area must be entirely contained in + * frame buffer memory, in case of frame flipping source and destination frame + * respectively. + */ +static int do_imageblit(void *data) +{ + struct omapfb_imageblit_params *par = data; + struct omapfb_device *fbdev = (struct omapfb_device *)par->fbi->par; + + int width = par->image.width, height = par->image.height; + int dx = par->image.dx, dy = par->image.dy; + const char *img_data = par->image.data; + unsigned long dst, dst_ofs; + unsigned long dst_end_ofs; + int bpp = par->fbi->var.bits_per_pixel; + u32 bg_color; + int r = 0; + + DBGENTER(2); + + if (!width || !height) + goto exit; + + /* bpp conversion is not supported, let the default function handle it. + * Note that image->depth is either 1 for monochrome image, or equals + * bpp of the current video mode, so we can't rely on it. + * If IRQs are disabled we can't use DMA. + */ + if (bpp < 8 || par->image.depth != bpp || irqs_disabled()) { + r = OMAPFB_IMGBLIT_FAILED; + goto exit; + } + + dst = fbdev->lcddma_handle; + dst_ofs = fbdev->dst_frame_org + + dx * bpp / 8 + dy * par->fbi->fix.line_length; + dst_end_ofs = (height - 1) * par->fbi->fix.line_length + + width * bpp / 8; + dst += dst_ofs; + + DBGPRINT(2, "data:%#010lx dst:%#010lx dst_end_ofs:%#010lx\n", + img_data, dst, dst_end_ofs); + + /* Check that both source and destination is DMA -able */ + if (dst_ofs + dst_end_ofs > fbdev->lcddma_mem_size) { + r = OMAPFB_IMGBLIT_FAILED; + goto exit; + } + + if (((unsigned long)img_data < (unsigned long)fbdev->lcddma_base) || + ((unsigned long)img_data + width * bpp / 8 * height > + (unsigned long)fbdev->lcddma_base + fbdev->lcddma_mem_size)) { + r = OMAPFB_IMGBLIT_FAILED; + goto exit; + } + + bg_color = par->image.bg_color; + if (par->flags & COPY_MODE_TRANSPARENT) { + switch (bpp) { + case 8: + bg_color |= bg_color << 8; + /* Fall through */ + case 16: + bg_color |= bg_color << 16; + } + } + + width = width * bpp / 8; + copy_data(fbdev, (unsigned long)img_data, dst, width, height, + bg_color, par->flags | COPY_MODE_IMAGE); +exit: + DBGLEAVE(2); + return r; +} + +static int omapfb_schedule_imageblit(struct fb_info *fbi, + const struct fb_image *image, int flags) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + struct omapfb_request *req; + + if ((req = omapfb_rqueue_alloc_req(&fbdev->rqueue)) == NULL) + return -ENOMEM; + req->function = do_imageblit; + req->par.imageblit.fbi = fbi; + req->par.imageblit.image = *image; + req->par.imageblit.flags = flags; + omapfb_rqueue_schedule_req(&fbdev->rqueue, req); + return fbdev->rqueue.status ? OMAPFB_GFX_STATUS_CHANGED : 0; +} + +/* 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 omapfb_device *fbdev) +{ + struct fb_info *fbi = fbdev->fb_info; + struct fb_fix_screeninfo *fix = &fbi->fix; + struct fb_var_screeninfo *var = &fbi->var; + int frame_size; + + strncpy(fix->id, OMAPFB_DRIVER, sizeof(fix->id)); + fix->type = FB_TYPE_PACKED_PIXELS; + switch (var->bits_per_pixel) { + case 16: + fix->visual = FB_VISUAL_TRUECOLOR; + 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 * var->bits_per_pixel / 8; + fix->smem_len = fbdev->lcddma_mem_size - fbdev->frame0_org; + fix->smem_start = fbdev->lcddma_handle + fbdev->frame0_org; + + /* Set the second frame buffer offset for flipping if there is + * room for it. */ + frame_size = fix->line_length * var->yres; + fbdev->frame1_org = fbdev->frame0_org + frame_size; + if (fbdev->frame1_org + frame_size > fbdev->lcddma_mem_size) + fbdev->frame1_org = 0; + fbdev->vis_frame_org = fbdev->src_frame_org = fbdev->dst_frame_org = + fbdev->frame0_org; + + fbdev->view_org = var->yoffset * fix->line_length + + var->xoffset * var->bits_per_pixel / 8; +} + +/* 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 omapfb_device *fbdev, + struct fb_var_screeninfo *var) +{ + int bpp; + unsigned long max_frame_size; + unsigned long line_size; + + bpp = var->bits_per_pixel = fbdev->panel->video_mode->bpp; + if (bpp != 16) + /* Not yet supported */ + return -1; + switch (var->rotate) { + case 0: + case 180: + var->xres = fbdev->panel->video_mode->x_res; + var->yres = fbdev->panel->video_mode->y_res; + break; + case 90: + case 270: + var->xres = fbdev->panel->video_mode->y_res; + var->yres = fbdev->panel->video_mode->x_res; + break; + default: + return -1; + } + 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->lcddma_mem_size - fbdev->frame0_org; + 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; + } + } + 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; + + 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; + var->nonstd = 0; + + /* TODO: video timing params, sync */ + var->pixclock = -1; + var->left_margin = -1; + var->right_margin = -1; + var->upper_margin = -1; + var->lower_margin = -1; + var->hsync_len = -1; + var->vsync_len = -1; + + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +static struct fb_var_screeninfo new_var; + +/* 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_device *fbdev = (struct omapfb_device *)fbi->par; + + DBGENTER(1); + + if (cpu_is_omap1510() && rotate != fbdev->fb_info->var.rotate) { + memcpy(&new_var, &fbi->var, sizeof(new_var)); + new_var.rotate = rotate; + if (set_fb_var(fbdev, &new_var) == 0 && + memcmp(&new_var, &fbi->var, sizeof(new_var))) { + memcpy(&fbi->var, &new_var, sizeof(new_var)); + set_fb_fix(fbdev); + ctrl_change_mode(fbdev); + } + } + + DBGLEAVE(1); +} + +/* 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_device *fbdev = (struct omapfb_device *)fbi->par; + int r = 0; + + DBGENTER(1); + + if (var->xoffset != fbi->var.xoffset || + var->yoffset != fbi->var.yoffset) { + memcpy(&new_var, &fbi->var, sizeof(new_var)); + new_var.xoffset = var->xoffset; + new_var.yoffset = var->yoffset; + if (set_fb_var(fbdev, &new_var)) + r = -EINVAL; + else { + memcpy(&fbi->var, &new_var, sizeof(new_var)); + set_fb_fix(fbdev); + ctrl_change_mode(fbdev); + } + } + + DBGLEAVE(1); + 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_device *fbdev = (struct omapfb_device *)fbi->par; + int r = 0; + + DBGENTER(1); + + mirror = mirror ? 1 : 0; + if (cpu_is_omap1510()) + r = -EINVAL; + else if (mirror != fbdev->mirror) { + fbdev->mirror = mirror; + ctrl_change_mode(fbdev); + } + + DBGLEAVE(1); + return r; +} + +/* Set x,y scale and switch to the new mode */ +static int omapfb_scale(struct fb_info *fbi, + unsigned int xscale, unsigned int yscale) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + int r = 0; + + DBGENTER(1); + + if (cpu_is_omap1510()) + r = -EINVAL; + else if (xscale != fbdev->xscale || yscale != fbdev->yscale) { + if (fbi->var.xres * xscale > fbi->var.xres_virtual || + fbi->var.yres * yscale > fbi->var.yres_virtual) + r = -EINVAL; + else { + fbdev->xscale = xscale; + fbdev->yscale = yscale; + ctrl_change_mode(fbdev); + } + } + + DBGLEAVE(1); + 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_device *fbdev = (struct omapfb_device *)fbi->par; + int r; + + DBGENTER(1); + + r = set_fb_var(fbdev, var); + + DBGLEAVE(1); + 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_device *fbdev = (struct omapfb_device *)fbi->par; + + DBGENTER(1); + + set_fb_fix(fbdev); + ctrl_change_mode(fbdev); + + DBGLEAVE(1); + return 0; +} + +/* Frame flipping support. Assign the primary or the secondary frame to the + * visible frame, as well as the source and destination frames for graphics + * operations like rectangle fill and area copy. Flipping is only possible + * if we have enough video memory for the secondary frame. + */ +static int omapfb_select_vis_frame(struct fb_info *fbi, unsigned int vis_idx) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + + if (vis_idx > 1 || (vis_idx == 1 && !fbdev->frame1_org)) + return -EINVAL; + fbdev->vis_frame_org = vis_idx ? fbdev->frame1_org : fbdev->frame0_org; + ctrl_change_mode(fbdev); + return 0; +} + +static int omapfb_select_src_frame(struct fb_info *fbi, unsigned int src_idx) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + + if (src_idx > 1 || (src_idx == 1 && !fbdev->frame1_org)) + return -EINVAL; + fbdev->src_frame_org = src_idx ? fbdev->frame1_org : fbdev->frame0_org; + return 0; +} + +static int omapfb_select_dst_frame(struct fb_info *fbi, unsigned int dst_idx) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + + if (dst_idx > 1 || (dst_idx == 1 && !fbdev->frame1_org)) + return -EINVAL; + fbdev->dst_frame_org = dst_idx ? fbdev->frame1_org : fbdev->frame0_org; + DBGPRINT(1, "dst_frame_org=%#010x\n", fbdev->dst_frame_org); + return 0; +} + +/* Get the address of the primary and secondary frames */ +static int omapfb_get_frame_offset(struct fb_info *fbi, + struct fb_frame_offset *fb_offset) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + + if (fb_offset->idx > 1) + return -EINVAL; + if (fb_offset->idx == 1 && !fbdev->frame1_org) + return -EINVAL; + fb_offset->offset = fb_offset->idx ? fbdev->frame1_org : + fbdev->frame0_org; + return 0; +} + +static int omapfb_update_window(void *data) +{ + struct omapfb_update_window_params *par = data; + struct omapfb_device *fbdev = (struct omapfb_device *)par->fbi->par; + + gfxdma_sync(&fbdev->gfx); + if (fbdev->ctrl->update_window(fbdev, &par->win)) + return OMAPFB_UPDATE_FAILED; + else + return 0; +} + +static int omapfb_schedule_update_window(struct fb_info *fbi, + struct fb_update_window *win) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + struct omapfb_request *req; + + if (!fbdev->ctrl->update_window || + win->x >= fbi->var.xres || win->y >= fbi->var.yres) + return -EINVAL; + if (win->x + win->width >= fbi->var.xres) + win->width = fbi->var.xres - win->x; + if (win->y + win->height >= fbi->var.yres) + win->height = fbi->var.yres - win->y; + if (!win->width || !win->height) + return 0; + if ((req = omapfb_rqueue_alloc_req(&fbdev->rqueue)) == NULL) + return -ENOMEM; + req->function = omapfb_update_window; + req->par.update_window.fbi = fbi; + req->par.update_window.win = *win; + omapfb_rqueue_schedule_req(&fbdev->rqueue, req); + return fbdev->rqueue.status ? OMAPFB_GFX_STATUS_CHANGED : 0; +} + +static int omapfb_schedule_full_update(struct fb_info *fbi) +{ + struct fb_update_window win; + + win.x = 0; + win.y = 0; + win.width = fbi->var.xres; + win.height = fbi->var.yres; + return omapfb_schedule_update_window(fbi, &win); +} + +static unsigned long omapfb_get_caps(struct fb_info *fbi) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + unsigned long caps; + + caps = 0; + caps |= fbdev->panel->get_caps(fbdev->panel); + caps |= fbdev->ctrl->get_caps(fbdev); + return caps; +} + +static int omapfb_set_update_mode(struct omapfb_device *fbdev, + enum fb_update_mode new_mode); + +static enum fb_update_mode omapfb_get_update_mode(struct omapfb_device *fbdev); + +/* Ioctl interface. Part of the kernel mode frame buffer API is duplicated + * here to be accessible by user mode code. In addition transparent copy + * graphics transformations, frame flipping support is provided through this + * interface. + */ +static int omapfb_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg, + struct fb_info *fbi) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)fbi->par; + struct fb_ops *ops = fbi->fbops; + union { + struct fb_fillrect rect; + struct fb_copyarea_ext area; + struct fb_image image; + struct fb_scale scale; + struct fb_frame_offset frame_offset; + struct fb_update_window update_window; + unsigned int frame_idx; + unsigned int mirror; + enum fb_update_mode update_mode; + unsigned long caps; + unsigned long rqueue_status; + } p; + int r = 0; + + DBGENTER(2); + + BUG_ON(!ops); + DBGPRINT(2, "cmd=%010x\n", cmd); + switch (cmd) + { + case OMAPFB_FILLRECT: + if (copy_from_user(&p.rect, (void __user *)arg, sizeof(p.rect))) + r = -EFAULT; + else + r = omapfb_schedule_fillrect(fbi, &p.rect); + break; + case OMAPFB_COPYAREA: + if (copy_from_user(&p.area, (void __user *)arg, sizeof(p.area))) + r = -EFAULT; + else + r = omapfb_schedule_copyarea(fbi, &p.area); + break; + case OMAPFB_IMAGEBLIT: + if (copy_from_user(&p.image, (void __user *)arg, + sizeof(p.image))) + r = -EFAULT; + else + r = omapfb_schedule_imageblit(fbi, &p.image, 0); + break; + case OMAPFB_TRANSPARENT_BLIT: + if (copy_from_user(&p.image, (void __user *)arg, + sizeof(p.image))) + r = -EFAULT; + else + r = omapfb_schedule_imageblit(fbi, &p.image, + COPY_MODE_TRANSPARENT); + break; + case OMAPFB_MIRROR: + if (get_user(p.mirror, (int __user *)arg)) + r = -EFAULT; + else + omapfb_mirror(fbi, p.mirror); + break; + case OMAPFB_SCALE: + if (copy_from_user(&p.scale, (void __user *)arg, + sizeof(p.scale))) + r = -EFAULT; + else + r = omapfb_scale(fbi, p.scale.xscale, p.scale.yscale); + break; + case OMAPFB_SELECT_VIS_FRAME: + if (get_user(p.frame_idx, (int __user *)arg)) + r = -EFAULT; + else + r = omapfb_select_vis_frame(fbi, p.frame_idx); + break; + case OMAPFB_SELECT_SRC_FRAME: + if (get_user(p.frame_idx, (int __user *)arg)) + r = - EFAULT; + else + r = omapfb_select_src_frame(fbi, p.frame_idx); + break; + case OMAPFB_SELECT_DST_FRAME: + if (get_user(p.frame_idx, (int __user *)arg)) + r = -EFAULT; + else + r = omapfb_select_dst_frame(fbi, p.frame_idx); + break; + case OMAPFB_GET_FRAME_OFFSET: + if (copy_from_user(&p.frame_offset, (void __user *)arg, + sizeof(p.frame_offset))) + r = -EFAULT; + else { + r = omapfb_get_frame_offset(fbi, &p.frame_offset); + if (copy_to_user((void __user *)arg, &p.frame_offset, + sizeof(p.frame_offset))) + r = -EFAULT; + } + break; + case OMAPFB_SYNC_GFX: + omapfb_rqueue_sync(&fbdev->rqueue); + break; + case OMAPFB_VSYNC: + break; + case OMAPFB_LATE_ACTIVATE: + printk(KERN_WARNING OMAPFB_DRIVER + ": LATE_ACTIVATE obsoleted by SET_UPDATE_MODE.\n"); +// r = -EINVAL; + 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 fb_update_mode __user *)arg)) + r = -EFAULT; + break; + case OMAPFB_UPDATE_WINDOW: + if (copy_from_user(&p.update_window, (void __user *)arg, + sizeof(p.update_window))) + r = -EFAULT; + else + r = omapfb_schedule_update_window(fbi, &p.update_window); + break; + case OMAPFB_GET_CAPS: + p.caps = omapfb_get_caps(fbi); + if (put_user(p.caps, (unsigned long __user *)arg)) + r = -EFAULT; + break; + case OMAPFB_GET_GFX_STATUS: + omapfb_rqueue_reset(&fbdev->rqueue, &p.rqueue_status); + if (put_user(p.rqueue_status, (unsigned long *)arg)) + r = -EFAULT; + break; + default: + r = -EINVAL; + } + + DBGLEAVE(2); + 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_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_cursor = soft_cursor, + .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, char *buf) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data; + + return snprintf(buf, PAGE_SIZE, "%#010lx\n", + omapfb_get_caps(fbdev->fb_info)); +} + +static ssize_t omapfb_show_caps_text(struct device *dev, char *buf) +{ + struct omapfb_device *fbdev = (struct omapfb_device *)dev->driver_data; + int pos = 0; + int i; + unsigned long caps; + + caps = omapfb_get_caps(fbdev->fb_info); + for (i = 0; i < ARRAY_SIZE(omapfb_caps_table) && pos < PAGE_SIZE; i++) { + if (omapfb_caps_table[i].flag & caps) { + pos += snprintf(&buf[pos], PAGE_SIZE - pos, "%s\n", + omapfb_caps_table[i].name); + } + } + return min((int)PAGE_SIZE, pos); +} + +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, 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, 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, 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, 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, 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: + PRNERR("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 = fbdev->fb_info; + struct fb_var_screeninfo *var = &info->var; + int r = 0; + + DBGENTER(1); + + BUG_ON(!fbdev->lcddma_base); + info->fbops = &omapfb_ops; + info->flags = FBINFO_FLAG_DEFAULT; + info->screen_base = (char __iomem *) fbdev->lcddma_base + + fbdev->frame0_org; + info->pseudo_palette = fbdev->pseudo_palette; + + var->accel_flags = def_accel ? FB_ACCELF_TEXT : 0; + var->xres_virtual = def_vxres; + var->yres_virtual = def_vyres; + var->rotate = def_rotate; + + fbdev->mirror = def_mirror; + + set_fb_var(fbdev, var); + set_fb_fix(fbdev); + + r = fb_alloc_cmap(&info->cmap, 16, 0); + if (r != 0) + PRNERR("unable to allocate color map memory\n"); + + DBGLEAVE(1); + return r; +} + +/* Release the fb_info object */ +static void fbinfo_cleanup(struct omapfb_device *fbdev) +{ + DBGENTER(1); + + fb_dealloc_cmap(&fbdev->fb_info->cmap); + + DBGLEAVE(1); +} + +/* Free driver resources. Can be called to rollback an aborted initialization + * sequence. + */ +static void omapfb_free_resources(struct omapfb_device *fbdev, int state) +{ + switch (state) { + case OMAPFB_ACTIVE: + unregister_framebuffer(fbdev->fb_info); + case 8: + omapfb_unregister_sysfs(fbdev); + case 7: + omapfb_set_update_mode(fbdev, FB_UPDATE_DISABLED); + case 6: + fbdev->panel->disable(fbdev->panel); + case 5: + gfxdma_cleanup(&fbdev->gfx); + case 4: + fbinfo_cleanup(fbdev); + case 3: + ctrl_cleanup(fbdev); + case 2: + fbdev->panel->cleanup(fbdev->panel); + case 1: + omapfb_rqueue_cleanup(&fbdev->rqueue); + dev_set_drvdata(fbdev->dev, NULL); + framebuffer_release(fbdev->fb_info); + case 0: + /* nothing to free */ + break; + default: + BUG(); + } +} + +static int omapfb_find_panel(struct omapfb_device *fbdev) +{ + const struct omap_lcd_config *cfg; + char name[17]; + int i; + + fbdev->panel = NULL; + cfg = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (cfg == NULL) { + const char *def_name = NULL; + + if (machine_is_omap_h2()) + def_name = "h2"; + if (machine_is_omap_h3()) + def_name = "h3"; + if (machine_is_omap_perseus2()) + def_name = "p2"; + if (machine_is_omap_osk()) + def_name = "osk"; + if (machine_is_omap_innovator() && cpu_is_omap1610()) + def_name = "inn1610"; + if (machine_is_omap_innovator() && cpu_is_omap1510()) + def_name = "inn1510"; + if (def_name == NULL) + return -1; + strncpy(name, def_name, sizeof(name) - 1); + } else + strncpy(name, cfg->panel_name, sizeof(name) - 1); + name[sizeof(name) - 1] = 0; + for (i = 0; i < ARRAY_SIZE(panels); i++) { + if (strcmp(panels[i]->name, name) == 0) { + fbdev->panel = panels[i]; + break; + } + } + if (fbdev->panel == NULL) + return -1; + return 0; +} + +static int omapfb_find_ctrl(struct omapfb_device *fbdev) +{ + const struct omap_lcd_config *cfg; + char name[17]; + int i; + + fbdev->ctrl = NULL; + cfg = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); + if (cfg == NULL) { + strcpy(name, "internal"); + } else + strncpy(name, cfg->ctrl_name, sizeof(name) - 1); + name[sizeof(name) - 1] = 0; + for (i = 0; i < ARRAY_SIZE(ctrls); i++) { + if (strcmp(ctrls[i]->name, name) == 0) { + fbdev->ctrl = ctrls[i]; + break; + } + } + if (fbdev->ctrl == NULL) + return -1; + return 0; +} + +static void check_required_callbacks(struct omapfb_device *fbdev) +{ +#define _C(x) (fbdev->ctrl->x) +#define _P(x) (fbdev->panel->x) + BUG_ON(!(_C(init) && _C(cleanup) && _C(get_caps) && + _C(get_mem_layout) && _C(set_update_mode) && _C(change_mode) && + _P(init) && _P(cleanup) && _P(enable) && _P(disable) && + _P(get_caps))); +#undef _P +#undef _C +} + +static int omapfb_set_update_mode(struct omapfb_device *fbdev, + enum fb_update_mode mode) +{ + return fbdev->ctrl->set_update_mode(fbdev, mode); +} + +static enum fb_update_mode omapfb_get_update_mode(struct omapfb_device *fbdev) +{ + return fbdev->ctrl->get_update_mode(fbdev); +} + +/* Called by LDM binding to probe and attach a new device. + * Initialization sequence: + * 1. allocate system fb_info structure + * select panel type according to machine type + * 2. init LCD panel + * 3. init LCD controller and LCD DMA + * 4. init system fb_info structure + * 5. init gfx DMA + * 6. enable LCD panel + * start LCD frame transfer + * 7. register system fb_info structure + */ +static int omapfb_probe(struct device *dev) +{ + struct platform_device *pdev; + struct omapfb_device *fbdev = NULL; + struct fb_info *fbi; + int init_state; + int r = 0; + + DBGENTER(1); + + init_state = 0; + + pdev = to_platform_device(dev); + if (pdev->num_resources != 0) { + PRNERR("probed for an unknown device\n"); + r = -ENODEV; + goto cleanup; + } + + fbi = framebuffer_alloc(sizeof(struct omapfb_device), dev); + if (!fbi) { + PRNERR("unable to allocate memory for device info\n"); + r = -ENOMEM; + goto cleanup; + } + + fbdev = (struct omapfb_device *)fbi->par; + fbdev->fb_info = fbi; + fbdev->dev = dev; + dev_set_drvdata(dev, fbdev); + + init_state++; + if (omapfb_find_ctrl(fbdev) < 0) { + PRNERR("LCD controller not found, board not supported\n"); + r = -ENODEV; + goto cleanup; + } + if (omapfb_find_panel(fbdev) < 0) { + PRNERR("LCD panel not found, board not supported\n"); + r = -ENODEV; + goto cleanup; + } + + check_required_callbacks(fbdev); + + printk(KERN_INFO OMAPFB_DRIVER ": configured for panel %s\n", + fbdev->panel->name); + + omapfb_rqueue_init(&fbdev->rqueue); + + r = fbdev->panel->init(fbdev->panel); + if (r) + goto cleanup; + init_state++; + + r = ctrl_init(fbdev); + if (r) + goto cleanup; + init_state++; + + r = fbinfo_init(fbdev); + if (r) + goto cleanup; + init_state++; + + r = gfxdma_init(&fbdev->gfx); + if (r) + goto cleanup; + init_state++; + +#ifdef CONFIG_FB_OMAP_DMA_TUNE + /* Set DMA priority for EMIFF access to highest */ + omap_set_dma_priority(OMAP_DMA_PORT_EMIFF, 15); +#endif + + r = fbdev->panel->enable(fbdev->panel); + if (r) + goto cleanup; + init_state++; + + r = omapfb_set_update_mode(fbdev, manual_update ? + FB_MANUAL_UPDATE : FB_AUTO_UPDATE); + if (r) + goto cleanup; + init_state++; + + r = omapfb_register_sysfs(fbdev); + if (r) + goto cleanup; + init_state++; + + r = register_framebuffer(fbdev->fb_info); + if (r != 0) { + PRNERR("register_framebuffer failed\n"); + goto cleanup; + } + + fbdev->state = OMAPFB_ACTIVE; + + printk(KERN_INFO "OMAP framebuffer initialized vram=%lu\n", + fbdev->lcddma_mem_size); + + DBGLEAVE(1); + return 0; + +cleanup: + omapfb_free_resources(fbdev, init_state); + + DBGLEAVE(1); + return r; +} + +/* Called when the device is being detached from the driver */ +static int omapfb_remove(struct device *dev) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + enum omapfb_state saved_state = fbdev->state; + + DBGENTER(1); + /* FIXME: wait till completion of pending events */ + + fbdev->state = OMAPFB_DISABLED; + omapfb_free_resources(fbdev, saved_state); + + DBGLEAVE(1); + return 0; +} + +/* PM suspend */ +static int omapfb_suspend(struct device *dev, pm_message_t mesg, u32 level) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + + DBGENTER(1); + + if (fbdev->state == OMAPFB_ACTIVE) { + if (fbdev->ctrl->suspend) + fbdev->ctrl->suspend(fbdev); + fbdev->panel->disable(fbdev->panel); + fbdev->state = OMAPFB_SUSPENDED; + } + + DBGLEAVE(1); + return 0; +} + +/* PM resume */ +static int omapfb_resume(struct device *dev, u32 level) +{ + struct omapfb_device *fbdev = dev_get_drvdata(dev); + + DBGENTER(1); + + if (fbdev->state == OMAPFB_SUSPENDED) { + fbdev->panel->enable(fbdev->panel); + if (fbdev->ctrl->resume) + fbdev->ctrl->resume(fbdev); + fbdev->state = OMAPFB_ACTIVE; + if (manual_update) + omapfb_schedule_full_update(fbdev->fb_info); + } + + DBGLEAVE(1); + return 0; +} + +static void omapfb_release_dev(struct device *dev) +{ + DBGENTER(1); + DBGLEAVE(1); +} + +static u64 omapfb_dmamask = ~(u32)0; + +static struct platform_device omapfb_device = { + .name = OMAPFB_DEVICE, + .id = -1, + .dev = { + .release = omapfb_release_dev, + .dma_mask = &omapfb_dmamask, + .coherent_dma_mask = 0xffffffff, + }, + .num_resources = 0, +}; + +static struct device_driver omapfb_driver = { + .name = OMAPFB_DRIVER, + .bus = &platform_bus_type, + .probe = omapfb_probe, + .remove = omapfb_remove, + .suspend = omapfb_suspend, + .resume = omapfb_resume +}; + +#ifndef MODULE + +/* Process kernel command line parameters */ +static int __init omapfb_setup(char *options) +{ + char *this_opt = NULL; + int r = 0; + + DBGENTER(1); + + if (!options || !*options) + goto exit; + + 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; + def_vram = (simple_strtoul(this_opt + 5, &suffix, 0)); + switch (suffix[0]) { + case 'm': + case 'M': + def_vram *= 1024 * 1024; + break; + case 'k': + case 'K': + def_vram *= 1024; + break; + default: + PRNERR("invalid vram suffix\n"); + r = -1; + } + } + 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 { + PRNERR("invalid option\n"); + r = -1; + } + } +exit: + DBGLEAVE(1); + return r; +} + +#endif + +/* Register both the driver and the device */ +static int __init omapfb_init(void) +{ + int r = 0; + + DBGENTER(1); + +#ifndef MODULE + { + char *option; + + if (fb_get_options("omapfb", &option)) { + r = -ENODEV; + goto exit; + } + omapfb_setup(option); + } +#endif + /* Register the device with LDM */ + if (platform_device_register(&omapfb_device)) { + PRNERR("failed to register omapfb device\n"); + r = -ENODEV; + goto exit; + } + /* Register the driver with LDM */ + if (driver_register(&omapfb_driver)) { + PRNERR("failed to register omapfb driver\n"); + platform_device_unregister(&omapfb_device); + r = -ENODEV; + goto exit; + } + +exit: + DBGLEAVE(1); + return r; +} + +static void __exit omapfb_cleanup(void) +{ + DBGENTER(1); + + driver_unregister(&omapfb_driver); + platform_device_unregister(&omapfb_device); + + DBGLEAVE(1); +} + +module_param_named(accel, def_accel, uint, 0664); +module_param_named(vram, def_vram, ulong, 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/sossi.c b/drivers/video/omap/sossi.c new file mode 100644 index 00000000000..b2006d77e25 --- /dev/null +++ b/drivers/video/omap/sossi.c @@ -0,0 +1,302 @@ +/* + * File: drivers/video/omap_new/omapfb_main.c + * + * Special optimiSed Screen Interface driver for TI OMAP boards + * + * Copyright (C) 2004 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 "sossi.h" + +#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 + +static int sossi_base = IO_ADDRESS(OMAP_SOSSI_BASE); + +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); +} + +#if 1 +void sossi_dump(void) +{ + printk(" INIT1: 0x%08x\n", sossi_read_reg(SOSSI_INIT1_REG)); + printk(" INIT2: 0x%08x\n", sossi_read_reg(SOSSI_INIT2_REG)); + printk(" INIT3: 0x%08x\n", sossi_read_reg(SOSSI_INIT3_REG)); + printk(" TEARING: 0x%08x\n", sossi_read_reg(SOSSI_TEARING_REG)); + printk(" INIT1B: 0x%08x\n", sossi_read_reg(SOSSI_INIT1B_REG)); +} +#endif + +static void sossi_dma_init(void) +{ + /* OMAP3.1 mapping disable */ + omap_writel(omap_readl(DMA_GSCR) | (1 << 3), DMA_GSCR); + /* Logical channel type to b0100 */ + omap_writew(omap_readw(DMA_LCD_LCH_CTRL) | (1 << 2), DMA_LCD_LCH_CTRL); + /* LCD_DMA dest port to 1 */ + omap_writew(omap_readw(DMA_LCD_CTRL) | (1 << 8), DMA_LCD_CTRL); + /* LCD_CCR OMAP31 comp mode */ + omap_writew(omap_readw(DMA_LCD_CCR) | (1 << 10), DMA_LCD_CCR); +} + +#define MOD_CONF_CTRL_1 0xfffe1110 +#define CONF_SOSSI_RESET_R (1 << 23) +#define CONF_MOD_SOSSI_CLK_EN_R (1 << 16) + +int sossi_init(void) +{ + u32 l, k; + + /* 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); + + l |= CONF_MOD_SOSSI_CLK_EN_R; + /* FIXME: Hardcode divide ratio 3 */ + l |= 2 << 17; + omap_writel(l, MOD_CONF_CTRL_1); + + omap_writel(omap_readl(ARM_IDLECT2) | (1 << 11), ARM_IDLECT2); + omap_writel(omap_readl(ARM_IDLECT1) | (1 << 6), ARM_IDLECT1); + + sossi_dma_init(); + + 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) { + printk(KERN_ERR "Invalid SoSSI sync pattern: %08x, %08x\n", l, k); + return -ENODEV; + } + l = sossi_read_reg(SOSSI_ID_REG); /* Component code */ + l = sossi_read_reg(SOSSI_ID_REG); + printk(KERN_INFO "SoSSI rev. %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); + + return 0; +} + +static void set_timings(int tw0, int tw1) +{ + u32 l; + + l = sossi_read_reg(SOSSI_INIT1_REG); + l &= ~((0x0f << 20) | (0x3f << 24)); + l |= ((tw0 & 0x0f) << 20) | ((tw1 & 0x3f) << 24); + sossi_write_reg(SOSSI_INIT1_REG, l); +} + +static struct sossi { + int bus_pick_width; +} sossi; + +void sossi_set_xfer_params(int tw0, int tw1, int bus_pick_count, int bus_pick_width) +{ + u32 l; + + set_timings(tw0, tw1); + sossi.bus_pick_width = bus_pick_width; + l = ((bus_pick_count - 1) << 5) | ((bus_pick_width - 1) & 0x1f); + sossi_write_reg(SOSSI_INIT3_REG, l); +} + +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); + /* FIXME: locking? */ +} + +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); + /* FIXME: locking? */ +} + +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) +{ + int nr_cycles = len / (sossi.bus_pick_width / 8); + + sossi_clear_bits(SOSSI_INIT1_REG, 0x3ffff); + sossi_set_bits(SOSSI_INIT1_REG, (nr_cycles - 1) & 0x3ffff); +} + +void sossi_send_cmd(const void *data, unsigned int len) +{ + sossi_clear_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(len); + send_data(data, len); +} + +void sossi_send_data(const void *data, unsigned int len) +{ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(len); + send_data(data, len); +} + +void sossi_prepare_dma_transfer(unsigned int count) +{ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(count); +} + +void sossi_send_data_const32(u32 data, unsigned int count) +{ + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(count * 4); + while (count > 0) { + sossi_write_reg(SOSSI_FIFO_REG, data); + count--; + } +} + +void sossi_set_tearing(int mode, int hs_counter, int detect_limit, + int vs_counter, int vs_detect_limit, int flags) +{ + u32 l = 0; + + l |= vs_counter << 30; + if (flags & SOSSI_FLAG_HS_INVERTED) + l |= 1 << 29; + if (flags & SOSSI_FLAG_VS_INVERTED) + l |= 1 << 28; + l |= mode << 26; + l |= hs_counter << 15; + l |= vs_detect_limit << 3; + l |= detect_limit; + sossi_write_reg(SOSSI_TEARING_REG, l); +} + +void sossi_read_data(void *data, unsigned int len) +{ + /* Before reading we must check if some writings are going on */ + while (!(sossi_read_reg(SOSSI_INIT2_REG) & (1 << 3))); + sossi_set_bits(SOSSI_INIT1_REG, 1 << 18); + set_cycles(len); + 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++; + } +} diff --git a/drivers/video/omap/sossi.h b/drivers/video/omap/sossi.h new file mode 100644 index 00000000000..4cd18d7de1e --- /dev/null +++ b/drivers/video/omap/sossi.h @@ -0,0 +1,19 @@ +#ifndef DRIVERS_VIDEO_OMAP_SOSSI_H +#define DRIVERS_VIDEO_OMAP_SOSSI_H + +#define SOSSI_FLAG_HS_INVERTED 0x01 +#define SOSSI_FLAG_VS_INVERTED 0x02 + +extern int sossi_init(void); +extern void sossi_set_xfer_params(int tw0, int tw1, int bus_pick_count, int bus_pick_width); +extern void sossi_start_transfer(void); +extern void sossi_stop_transfer(void); +extern void sossi_send_cmd(const void *data, unsigned int len); +extern void sossi_send_data(const void *data, unsigned int len); +extern void sossi_send_data_const32(u32 data, unsigned int count); +extern void sossi_prepare_dma_transfer(unsigned int count); +extern void sossi_read_data(void *data, unsigned int len); +extern void sossi_set_tearing(int mode, int hs_counter, int detect_limit, + int vs_counter, int vs_detect_limit, int flags); + +#endif diff --git a/include/linux/fb.h b/include/linux/fb.h index b468bf49654..edff021b27e 100644 --- a/include/linux/fb.h +++ b/include/linux/fb.h @@ -107,6 +107,7 @@ #define FB_ACCEL_NV_20 44 /* nVidia Arch 20 */ #define FB_ACCEL_NV_30 45 /* nVidia Arch 30 */ #define FB_ACCEL_NV_40 46 /* nVidia Arch 40 */ +#define FB_ACCEL_OMAP1610 47 /* 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