From a2bd3b5934be26656ba88ac1083cb76c9665822e Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 4 Nov 2008 15:12:21 +0200 Subject: [PATCH] DSS: OMAPFB: fb driver for new display subsystem Signed-off-by: Tomi Valkeinen --- arch/arm/plat-omap/Makefile | 2 +- arch/arm/plat-omap/fb-vram.c | 498 +++++++++++ arch/arm/plat-omap/fb.c | 33 +- arch/arm/plat-omap/include/mach/omapfb.h | 14 + drivers/video/Kconfig | 1 + drivers/video/Makefile | 1 + drivers/video/omap/Kconfig | 5 +- drivers/video/omap2/Kconfig | 38 + drivers/video/omap2/Makefile | 2 + drivers/video/omap2/omapfb-ioctl.c | 462 ++++++++++ drivers/video/omap2/omapfb-main.c | 1382 ++++++++++++++++++++++++++++++ drivers/video/omap2/omapfb-sysfs.c | 838 ++++++++++++++++++ drivers/video/omap2/omapfb.h | 109 +++ 13 files changed, 3377 insertions(+), 8 deletions(-) create mode 100644 arch/arm/plat-omap/fb-vram.c create mode 100644 drivers/video/omap2/Kconfig create mode 100644 drivers/video/omap2/Makefile create mode 100644 drivers/video/omap2/omapfb-ioctl.c create mode 100644 drivers/video/omap2/omapfb-main.c create mode 100644 drivers/video/omap2/omapfb-sysfs.c create mode 100644 drivers/video/omap2/omapfb.h diff --git a/arch/arm/plat-omap/Makefile b/arch/arm/plat-omap/Makefile index 2740497..7d602a6 100644 --- a/arch/arm/plat-omap/Makefile +++ b/arch/arm/plat-omap/Makefile @@ -4,7 +4,7 @@ # Common support obj-y := common.o sram.o clock.o devices.o dma.o mux.o gpio.o \ - usb.o fb.o io.o + usb.o fb.o fb-vram.o io.o obj-m := obj-n := obj- := diff --git a/arch/arm/plat-omap/fb-vram.c b/arch/arm/plat-omap/fb-vram.c new file mode 100644 index 0000000..de24503 --- /dev/null +++ b/arch/arm/plat-omap/fb-vram.c @@ -0,0 +1,498 @@ +/* + * linux/arch/arm/plat-omap/fb-vram.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +//#define DEBUG + +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef DEBUG +#define DBG(format, ...) printk(KERN_DEBUG "VRAM: " format, ## __VA_ARGS__) +#else +#define DBG(format, ...) +#endif + +#define OMAP2_SRAM_START 0x40200000 +/* Maximum size, in reality this is smaller if SRAM is partially locked. */ +#define OMAP2_SRAM_SIZE 0xa0000 /* 640k */ + +#define REG_MAP_SIZE(_page_cnt) \ + ((_page_cnt + (sizeof(unsigned long) * 8) - 1) / 8) +#define REG_MAP_PTR(_rg, _page_nr) \ + (((_rg)->map) + (_page_nr) / (sizeof(unsigned long) * 8)) +#define REG_MAP_MASK(_page_nr) \ + (1 << ((_page_nr) & (sizeof(unsigned long) * 8 - 1))) + +#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) \ + || defined(CONFIG_FB_OMAP2) || defined(CONFIG_FB_OMAP2_MODULE) + +/* postponed regions are used to temporarily store region information at boot + * time when we cannot yet allocate the region list */ +#define MAX_POSTPONED_REGIONS 10 + +static int postponed_cnt __initdata; +static struct { + unsigned long paddr; + size_t size; +} postponed_regions[MAX_POSTPONED_REGIONS] __initdata; + +struct vram_alloc { + struct list_head list; + unsigned long paddr; + unsigned pages; +}; + +struct vram_region { + struct list_head list; + struct list_head alloc_list; + unsigned long paddr; + void *vaddr; + unsigned pages; + unsigned dma_alloced:1; +}; + +static DEFINE_MUTEX(region_mutex); +static LIST_HEAD(region_list); + +static inline int region_mem_type(unsigned long paddr) +{ + if (paddr >= OMAP2_SRAM_START && + paddr < OMAP2_SRAM_START + OMAP2_SRAM_SIZE) + return OMAPFB_MEMTYPE_SRAM; + else + return OMAPFB_MEMTYPE_SDRAM; +} + +static struct vram_region *omap_vram_create_region(unsigned long paddr, + void *vaddr, unsigned pages) +{ + struct vram_region *rm; + + rm = kzalloc(sizeof(*rm), GFP_KERNEL); + + if (rm) { + INIT_LIST_HEAD(&rm->alloc_list); + rm->paddr = paddr; + rm->vaddr = vaddr; + rm->pages = pages; + } + + return rm; +} + +static void omap_vram_free_region(struct vram_region *vr) +{ + list_del(&vr->list); + kfree(vr); +} + +static struct vram_alloc *omap_vram_create_allocation(struct vram_region *vr, + unsigned long paddr, unsigned pages) +{ + struct vram_alloc *va; + struct vram_alloc *new; + + new = kzalloc(sizeof(*va), GFP_KERNEL); + + if (!new) + return NULL; + + new->paddr = paddr; + new->pages = pages; + + list_for_each_entry(va, &vr->alloc_list, list) { + if (va->paddr > new->paddr) + break; + } + + list_add_tail(&new->list, &va->list); + + return new; +} + +static void omap_vram_free_allocation(struct vram_alloc *va) +{ + list_del(&va->list); + kfree(va); +} + +__init int omap_vram_add_region_postponed(unsigned long paddr, size_t size) +{ + if (postponed_cnt == MAX_POSTPONED_REGIONS) + return -ENOMEM; + + postponed_regions[postponed_cnt].paddr = paddr; + postponed_regions[postponed_cnt].size = size; + + ++postponed_cnt; + + return 0; +} + +/* add/remove_region can be exported if there's need to add/remove regions + * runtime */ +static int omap_vram_add_region(unsigned long paddr, size_t size) +{ + struct vram_region *rm; + void *vaddr; + unsigned pages; + + DBG("adding region paddr %08lx size %d\n", + paddr, size); + + size &= PAGE_MASK; + pages = size >> PAGE_SHIFT; + + vaddr = ioremap_wc(paddr, size); + if (vaddr == NULL) + return -ENOMEM; + + rm = omap_vram_create_region(paddr, vaddr, pages); + if (rm == NULL) { + iounmap(vaddr); + return -ENOMEM; + } + + list_add(&rm->list, ®ion_list); + + return 0; +} + +#if 0 +int omap_vram_remove_region(unsigned long paddr) +{ + struct region *rm; + unsigned i; + + DBG("remove region paddr %08lx\n", paddr); + list_for_each_entry(rm, ®ion_list, list) + if (rm->paddr != paddr) + continue; + + if (rm->paddr != paddr) + return -EINVAL; + + for (i = 0; i < rm->page_cnt; i++) + if (region_page_reserved(rm, i)) + return -EBUSY; + + iounmap(rm->vaddr); + + list_del(&rm->list); + + kfree(rm); + + return 0; +} +#endif + +int omap_vram_free(unsigned long paddr, void *vaddr, size_t size) +{ + struct vram_region *rm; + struct vram_alloc *alloc; + unsigned start, end; + + DBG("free mem paddr %08lx vaddr %p size %d\n", + paddr, vaddr, size); + + size = PAGE_ALIGN(size); + + mutex_lock(®ion_mutex); + + list_for_each_entry(rm, ®ion_list, list) { + list_for_each_entry(alloc, &rm->alloc_list, list) { + start = alloc->paddr; + end = alloc->paddr + (alloc->pages >> PAGE_SHIFT); + + if (start >= paddr && end < paddr + size) + goto found; + } + } + + mutex_unlock(®ion_mutex); + return -EINVAL; + +found: + if (rm->dma_alloced) { + DBG("freeing dma-alloced\n"); + dma_free_writecombine(NULL, size, vaddr, paddr); + omap_vram_free_allocation(alloc); + omap_vram_free_region(rm); + } else { + omap_vram_free_allocation(alloc); + } + + mutex_unlock(®ion_mutex); + return 0; +} +EXPORT_SYMBOL(omap_vram_free); + +#if 0 +void *omap_vram_reserve(unsigned long paddr, size_t size) +{ + + struct region *rm; + unsigned start_page; + unsigned end_page; + unsigned i; + void *vaddr; + + size = PAGE_ALIGN(size); + + rm = region_find_region(paddr, size); + + DBG("reserve mem paddr %08lx size %d\n", + paddr, size); + + BUG_ON(rm == NULL); + + start_page = (paddr - rm->paddr) >> PAGE_SHIFT; + end_page = start_page + (size >> PAGE_SHIFT); + for (i = start_page; i < end_page; i++) + region_reserve_page(rm, i); + + vaddr = rm->vaddr + (start_page << PAGE_SHIFT); + + return vaddr; +} +EXPORT_SYMBOL(omap_vram_reserve); +#endif +static void *_omap_vram_alloc(int mtype, unsigned pages, unsigned long *paddr) +{ + struct vram_region *rm; + struct vram_alloc *alloc; + void *vaddr; + + list_for_each_entry(rm, ®ion_list, list) { + unsigned long start, end; + + DBG("checking region %lx %d\n", rm->paddr, rm->pages); + + if (region_mem_type(rm->paddr) != mtype) + continue; + + start = rm->paddr; + + list_for_each_entry(alloc, &rm->alloc_list, list) { + end = alloc->paddr; + + if (end - start >= pages << PAGE_SHIFT) + goto found; + + start = alloc->paddr + (alloc->pages << PAGE_SHIFT); + } + + end = rm->paddr + (rm->pages << PAGE_SHIFT); +found: + if (end - start < pages << PAGE_SHIFT) + continue; + + DBG("FOUND %lx, end %lx\n", start, end); + + if (omap_vram_create_allocation(rm, start, pages) == NULL) + return NULL; + + *paddr = start; + vaddr = rm->vaddr + (start - rm->paddr); + + return vaddr; + } + + return NULL; +} + +static void *_omap_vram_alloc_dma(unsigned pages, unsigned long *paddr) +{ + struct vram_region *rm; + void *vaddr; + + vaddr = dma_alloc_writecombine(NULL, pages << PAGE_SHIFT, + (dma_addr_t *)paddr, GFP_KERNEL); + + if (vaddr == NULL) + return NULL; + + rm = omap_vram_create_region(*paddr, vaddr, pages); + if (rm == NULL) { + dma_free_writecombine(NULL, pages << PAGE_SHIFT, vaddr, + (dma_addr_t)*paddr); + return NULL; + } + + rm->dma_alloced = 1; + + if (omap_vram_create_allocation(rm, *paddr, pages) == NULL) { + dma_free_writecombine(NULL, pages << PAGE_SHIFT, vaddr, + (dma_addr_t)*paddr); + kfree(rm); + return NULL; + } + + list_add(&rm->list, ®ion_list); + + return vaddr; +} + +void *omap_vram_alloc(int mtype, size_t size, unsigned long *paddr) +{ + void *vaddr; + unsigned pages; + + BUG_ON(mtype > OMAPFB_MEMTYPE_MAX || !size); + + DBG("alloc mem type %d size %d\n", mtype, size); + + size = PAGE_ALIGN(size); + pages = size >> PAGE_SHIFT; + + mutex_lock(®ion_mutex); + + vaddr = _omap_vram_alloc(mtype, pages, paddr); + + if (vaddr == NULL && mtype == OMAPFB_MEMTYPE_SDRAM) { + DBG("fallback to dma_alloc\n"); + + vaddr = _omap_vram_alloc_dma(pages, paddr); + } + + mutex_unlock(®ion_mutex); + + return vaddr; +} +EXPORT_SYMBOL(omap_vram_alloc); + +#ifdef CONFIG_PROC_FS +static void *r_next(struct seq_file *m, void *v, loff_t *pos) +{ + struct list_head *l = v; + + (*pos)++; + + if (list_is_last(l, ®ion_list)) + return 0; + + return l->next; +} + +static void *r_start(struct seq_file *m, loff_t *pos) +{ + loff_t p = *pos; + struct list_head *l = ®ion_list; + + mutex_lock(®ion_mutex); + + do { + l = l->next; + if (l == ®ion_list) + return NULL; + } while (p--); + + return l; +} + +static void r_stop(struct seq_file *m, void *v) +{ + mutex_unlock(®ion_mutex); +} + +static int r_show(struct seq_file *m, void *v) +{ + struct vram_region *vr; + struct vram_alloc *va; + unsigned size; + + vr = list_entry(v, struct vram_region, list); + + size = vr->pages << PAGE_SHIFT; + seq_printf(m, "%08lx-%08lx v:%p-%p (%d bytes) %s\n", + vr->paddr, vr->paddr + size, + vr->vaddr, vr->vaddr + size, + size, + vr->dma_alloced ? "dma_alloc" : ""); + + list_for_each_entry(va, &vr->alloc_list, list) { + size = va->pages << PAGE_SHIFT; + seq_printf(m, " %08lx-%08lx (%d bytes)\n", + va->paddr, va->paddr + size, + size); + } + + + + return 0; +} + +static const struct seq_operations resource_op = { + .start = r_start, + .next = r_next, + .stop = r_stop, + .show = r_show, +}; + +static int vram_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &resource_op); +} + +static const struct file_operations proc_vram_operations = { + .open = vram_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static int __init omap_vram_create_proc(void) +{ + proc_create("omap-vram", 0, NULL, &proc_vram_operations); + + return 0; +} +#endif + +static __init int omap_vram_init(void) +{ + int i, r; + + for (i = 0; i < postponed_cnt; i++) + omap_vram_add_region(postponed_regions[i].paddr, + postponed_regions[i].size); + +#ifdef CONFIG_PROC_FS + r = omap_vram_create_proc(); + if (r) + return -ENOMEM; +#endif + + return 0; +} + +arch_initcall(omap_vram_init); + +#endif + diff --git a/arch/arm/plat-omap/fb.c b/arch/arm/plat-omap/fb.c index 3746222..da528d0 100644 --- a/arch/arm/plat-omap/fb.c +++ b/arch/arm/plat-omap/fb.c @@ -36,7 +36,11 @@ #include #include -#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) +#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) \ + || defined(CONFIG_FB_OMAP2) || defined(CONFIG_FB_OMAP2_MODULE) + +static int omapfb_vram_count; +static struct omap_fbmem_config *omapfb_vram_config; static struct omapfb_platform_data omapfb_config; static int config_invalid; @@ -95,11 +99,11 @@ static int get_fbmem_region(int region_idx, struct omapfb_mem_region *rg) const struct omap_fbmem_config *conf; u32 paddr; - conf = omap_get_nr_config(OMAP_TAG_FBMEM, - struct omap_fbmem_config, region_idx); - if (conf == NULL) + if (region_idx >= omapfb_vram_count) return -ENOENT; + conf = &omapfb_vram_config[region_idx]; + paddr = conf->start; /* * Low bits encode the page allocation mode, if high bits @@ -209,6 +213,13 @@ void __init omapfb_reserve_sdram(void) if (rg.paddr) { reserve_bootmem(rg.paddr, rg.size, BOOTMEM_DEFAULT); reserved += rg.size; + omap_vram_add_region_postponed(rg.paddr, rg.size); + } else { + void *vaddr; + vaddr = alloc_bootmem(rg.size); + rg.paddr = virt_to_phys(vaddr); + reserved += rg.size; + omap_vram_add_region_postponed(rg.paddr, rg.size); } omapfb_config.mem_desc.region[i] = rg; configured_regions++; @@ -229,7 +240,7 @@ void __init omapfb_reserve_sdram(void) * this point, since the driver built as a module would have problem with * freeing / reallocating the regions. */ -unsigned long omapfb_reserve_sram(unsigned long sram_pstart, +unsigned long __init omapfb_reserve_sram(unsigned long sram_pstart, unsigned long sram_vstart, unsigned long sram_size, unsigned long pstart_avail, @@ -298,14 +309,24 @@ unsigned long omapfb_reserve_sram(unsigned long sram_pstart, return reserved; } +void __init omapfb_set_vram_config(struct omap_fbmem_config *config, int count) +{ + omapfb_vram_count = count; + omapfb_vram_config = config; +} + +#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) void omapfb_set_ctrl_platform_data(void *data) { omapfb_config.ctrl_platform_data = data; } +#endif static inline int omap_init_fb(void) { +#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) const struct omap_lcd_config *conf; +#endif if (config_invalid) return 0; @@ -313,6 +334,7 @@ static inline int omap_init_fb(void) printk(KERN_ERR "Invalid FB mem configuration entries\n"); return 0; } +#if defined(CONFIG_FB_OMAP) || defined(CONFIG_FB_OMAP_MODULE) conf = omap_get_config(OMAP_TAG_LCD, struct omap_lcd_config); if (conf == NULL) { if (configured_regions) @@ -321,6 +343,7 @@ static inline int omap_init_fb(void) return 0; } omapfb_config.lcd = *conf; +#endif return platform_device_register(&omap_fb_device); } diff --git a/arch/arm/plat-omap/include/mach/omapfb.h b/arch/arm/plat-omap/include/mach/omapfb.h index 90d63c5..277e3cf 100644 --- a/arch/arm/plat-omap/include/mach/omapfb.h +++ b/arch/arm/plat-omap/include/mach/omapfb.h @@ -90,6 +90,13 @@ enum omapfb_color_format { OMAPFB_COLOR_CLUT_1BPP, OMAPFB_COLOR_RGB444, OMAPFB_COLOR_YUY422, + + OMAPFB_COLOR_ARGB16, + OMAPFB_COLOR_RGB24U, /* RGB24, 32-bit container */ + OMAPFB_COLOR_RGB24P, /* RGB24, 24-bit container */ + OMAPFB_COLOR_ARGB32, + OMAPFB_COLOR_RGBA32, + OMAPFB_COLOR_RGBX32, }; struct omapfb_update_window { @@ -392,6 +399,13 @@ extern int omapfb_update_window_async(struct fb_info *fbi, /* in arch/arm/plat-omap/fb.c */ extern void omapfb_set_ctrl_platform_data(void *pdata); +extern void omapfb_set_vram_config(struct omap_fbmem_config *config, int count); + +/* in arch/arm/plat-omap/fb-vram */ +__init int omap_vram_add_region_postponed(unsigned long paddr, size_t size); +int omap_vram_free(unsigned long paddr, void *vaddr, size_t size); +void *omap_vram_reserve(unsigned long paddr, size_t size); +void *omap_vram_alloc(int mtype, size_t size, unsigned long *paddr); #endif /* __KERNEL__ */ diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 3f3ce13..689a3b1 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -2116,6 +2116,7 @@ config FB_PRE_INIT_FB the bootloader. source "drivers/video/omap/Kconfig" +source "drivers/video/omap2/Kconfig" source "drivers/video/backlight/Kconfig" source "drivers/video/display/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index e39e33e..3d9d50e 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -120,6 +120,7 @@ obj-$(CONFIG_FB_SM501) += sm501fb.o obj-$(CONFIG_FB_XILINX) += xilinxfb.o obj-$(CONFIG_FB_SH_MOBILE_LCDC) += sh_mobile_lcdcfb.o obj-$(CONFIG_FB_OMAP) += omap/ +obj-$(CONFIG_OMAP2_DSS) += omap2/ obj-$(CONFIG_XEN_FBDEV_FRONTEND) += xen-fbfront.o obj-$(CONFIG_FB_CARMINE) += carminefb.o obj-$(CONFIG_FB_MB862XX) += mb862xx/ diff --git a/drivers/video/omap/Kconfig b/drivers/video/omap/Kconfig index c355b59..541fab3 100644 --- a/drivers/video/omap/Kconfig +++ b/drivers/video/omap/Kconfig @@ -1,6 +1,7 @@ config FB_OMAP tristate "OMAP frame buffer support (EXPERIMENTAL)" - depends on FB && ARCH_OMAP + depends on FB && ARCH_OMAP && (OMAP2_DSS = "n") + select FB_CFB_FILLRECT select FB_CFB_COPYAREA select FB_CFB_IMAGEBLIT @@ -80,7 +81,7 @@ config FB_OMAP_BOOTLOADER_INIT config FB_OMAP_CONSISTENT_DMA_SIZE int "Consistent DMA memory size (MB)" - depends on FB_OMAP + depends on FB && ARCH_OMAP range 1 14 default 2 help diff --git a/drivers/video/omap2/Kconfig b/drivers/video/omap2/Kconfig new file mode 100644 index 0000000..bfa1617 --- /dev/null +++ b/drivers/video/omap2/Kconfig @@ -0,0 +1,38 @@ +config FB_OMAP2 + tristate "OMAP2/3 frame buffer support (EXPERIMENTAL)" + depends on FB && OMAP2_DSS + + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + Frame buffer driver for OMAP2/3 based boards. + +config FB_OMAP2_DEBUG + bool "Debug output for OMAP2/3 FB" + depends on FB_OMAP2 + +config FB_OMAP2_FORCE_AUTO_UPDATE + bool "Force main display to automatic update mode" + depends on FB_OMAP2 + help + Forces main display to automatic update mode (if possible), + and also enables tearsync (if possible). By default + displays that support manual update are started in manual + update mode. + +config FB_OMAP2_NUM_FBS + int "Number of framebuffers" + range 1 10 + default 3 + depends on FB_OMAP2 + help + Select the number of framebuffers created. OMAP2/3 has 3 overlays + so normally this would be 3. + +menu "OMAP2/3 Display Device Drivers" + depends on OMAP2_DSS + + +endmenu + diff --git a/drivers/video/omap2/Makefile b/drivers/video/omap2/Makefile new file mode 100644 index 0000000..51c2e00 --- /dev/null +++ b/drivers/video/omap2/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_FB_OMAP2) += omapfb.o +omapfb-y := omapfb-main.o omapfb-sysfs.o omapfb-ioctl.o diff --git a/drivers/video/omap2/omapfb-ioctl.c b/drivers/video/omap2/omapfb-ioctl.c new file mode 100644 index 0000000..6bf750f --- /dev/null +++ b/drivers/video/omap2/omapfb-ioctl.c @@ -0,0 +1,462 @@ +/* + * linux/drivers/video/omap2/omapfb-ioctl.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "omapfb.h" + +static int omapfb_setup_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + struct omap_overlay *ovl; + int r = 0; + + DBG("omapfb_setup_plane\n"); + + omapfb_lock(fbdev); + + if (ofbi->num_overlays != 1) { + r = -EINVAL; + goto out; + } + + /* XXX uses only the first overlay */ + ovl = ofbi->overlays[0]; + + if (pi->enabled && !ofbi->region.size) { + /* + * This plane's memory was freed, can't enable it + * until it's reallocated. + */ + r = -EINVAL; + goto out; + } + + if (pi->enabled) { + r = omapfb_setup_overlay(fbi, ovl, pi->pos_x, pi->pos_y, + pi->out_width, pi->out_height); + if (r) + goto out; + } + + r = omapfb_setup_overlay(fbi, ovl, pi->pos_x, pi->pos_y, + pi->out_width, pi->out_height); + if (r) + goto out; + + ovl->enable(ovl, pi->enabled); + + if (ovl->manager) + ovl->manager->apply(ovl->manager); + + if (display) { + if (display->sync) + display->sync(display); + + if (display->update) + display->update(display, 0, 0, + display->x_res, display->y_res); + } + +out: + omapfb_unlock(fbdev); + if (r) + dev_err(fbdev->dev, "setup_plane failed\n"); + return r; +} + +static int omapfb_query_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + + omapfb_lock(fbdev); + + if (ofbi->num_overlays != 1) { + memset(pi, 0, sizeof(*pi)); + } else { + struct omap_overlay_info *ovli; + struct omap_overlay *ovl; + + ovl = ofbi->overlays[0]; + ovli = &ovl->info; + + pi->pos_x = ovli->pos_x; + pi->pos_y = ovli->pos_y; + pi->enabled = ovli->enabled; + pi->channel_out = 0; /* xxx */ + pi->mirror = 0; + pi->out_width = ovli->out_width; + pi->out_height = ovli->out_height; + } + + omapfb_unlock(fbdev); + + return 0; +} + +static int omapfb_setup_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb_mem_region *rg; + struct omap_display *display = fb2display(fbi); + int r, i; + size_t size; + + if (mi->type > OMAPFB_MEMTYPE_MAX) + return -EINVAL; + + size = PAGE_ALIGN(mi->size); + + rg = &ofbi->region; + + omapfb_lock(fbdev); + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ofbi->overlays[i]->info.enabled) { + r = -EBUSY; + goto out; + } + } + + if (rg->size != size || rg->type != mi->type) { + struct fb_var_screeninfo new_var; + unsigned long old_size = rg->size; + + if (display->sync) + display->sync(display); + + r = omapfb_realloc_fbmem(fbdev, ofbi->id, size); + if (r) + goto out; + + if (old_size != size) { + if (size) { + memcpy(&new_var, &fbi->var, sizeof(new_var)); + r = check_fb_var(fbi, &new_var); + if (r < 0) + goto out; + memcpy(&fbi->var, &new_var, sizeof(fbi->var)); + set_fb_fix(fbi); + } else { + /* + * Set these explicitly to indicate that the + * plane memory is dealloce'd, the other + * screen parameters in var / fix are invalid. + */ + fbi->fix.smem_start = 0; + fbi->fix.smem_len = 0; + } + } + } + + r = 0; +out: + omapfb_unlock(fbdev); + + return r; +} + +static int omapfb_query_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb_mem_region *rg; + + rg = &ofbi->region; + memset(mi, 0, sizeof(*mi)); + + omapfb_lock(fbdev); + mi->size = rg->size; + mi->type = rg->type; + omapfb_unlock(fbdev); + + return 0; +} + +static int omapfb_update_window(struct fb_info *fbi, + u32 x, u32 y, u32 w, u32 h) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + + if (!display) + return 0; + + if (w == 0 || h == 0) + return 0; + + if (x + w > display->x_res || y + h > display->y_res) + return -EINVAL; + + omapfb_lock(fbdev); + display->update(display, x, y, w, h); + omapfb_unlock(fbdev); + + return 0; +} + +static int omapfb_set_update_mode(struct fb_info *fbi, + enum omapfb_update_mode mode) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + enum omap_dss_update_mode um; + int r; + + if (!display || !display->set_update_mode) + return -EINVAL; + + switch (mode) { + case OMAPFB_UPDATE_DISABLED: + um = OMAP_DSS_UPDATE_DISABLED; + break; + + case OMAPFB_AUTO_UPDATE: + um = OMAP_DSS_UPDATE_AUTO; + break; + + case OMAPFB_MANUAL_UPDATE: + um = OMAP_DSS_UPDATE_MANUAL; + break; + + default: + return -EINVAL; + } + + omapfb_lock(fbdev); + r = display->set_update_mode(display, um); + omapfb_unlock(fbdev); + + return r; +} + +static int omapfb_get_update_mode(struct fb_info *fbi, + enum omapfb_update_mode *mode) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + enum omap_dss_update_mode m; + + if (!display || !display->get_update_mode) + return -EINVAL; + + omapfb_lock(fbdev); + m = display->get_update_mode(display); + omapfb_unlock(fbdev); + + switch (m) { + case OMAP_DSS_UPDATE_DISABLED: + *mode = OMAPFB_UPDATE_DISABLED; + break; + case OMAP_DSS_UPDATE_AUTO: + *mode = OMAPFB_AUTO_UPDATE; + break; + case OMAP_DSS_UPDATE_MANUAL: + *mode = OMAPFB_MANUAL_UPDATE; + break; + default: + BUG(); + } + + return 0; +} + +int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + + union { + struct omapfb_update_window_old uwnd_o; + struct omapfb_update_window uwnd; + struct omapfb_plane_info plane_info; + struct omapfb_caps caps; + struct omapfb_mem_info mem_info; + enum omapfb_update_mode update_mode; + int test_num; + } p; + + int r = 0; + + DBG("ioctl %x (%d)\n", cmd, cmd & 0xff); + + switch (cmd) { + case OMAPFB_SYNC_GFX: + if (!display || !display->sync) { + r = -EINVAL; + break; + } + + omapfb_lock(fbdev); + r = display->sync(display); + omapfb_unlock(fbdev); + break; + + case OMAPFB_UPDATE_WINDOW_OLD: + if (!display || !display->update) { + r = -EINVAL; + break; + } + + if (copy_from_user(&p.uwnd_o, + (void __user *)arg, + sizeof(p.uwnd_o))) { + r = -EFAULT; + break; + } + + r = omapfb_update_window(fbi, p.uwnd_o.x, p.uwnd_o.y, + p.uwnd_o.width, p.uwnd_o.height); + break; + + case OMAPFB_UPDATE_WINDOW: + if (!display || !display->update) { + r = -EINVAL; + break; + } + + if (copy_from_user(&p.uwnd, (void __user *)arg, + sizeof(p.uwnd))) { + r = -EFAULT; + break; + } + + r = omapfb_update_window(fbi, p.uwnd.x, p.uwnd.y, + p.uwnd.width, p.uwnd.height); + break; + + case OMAPFB_SETUP_PLANE: + if (copy_from_user(&p.plane_info, (void __user *)arg, + sizeof(p.plane_info))) + r = -EFAULT; + else + r = omapfb_setup_plane(fbi, &p.plane_info); + break; + + case OMAPFB_QUERY_PLANE: + r = omapfb_query_plane(fbi, &p.plane_info); + if (r < 0) + break; + if (copy_to_user((void __user *)arg, &p.plane_info, + sizeof(p.plane_info))) + r = -EFAULT; + break; + + case OMAPFB_SETUP_MEM: + if (copy_from_user(&p.mem_info, (void __user *)arg, + sizeof(p.mem_info))) + r = -EFAULT; + else + r = omapfb_setup_mem(fbi, &p.mem_info); + break; + + case OMAPFB_QUERY_MEM: + r = omapfb_query_mem(fbi, &p.mem_info); + if (r < 0) + break; + if (copy_to_user((void __user *)arg, &p.mem_info, + sizeof(p.mem_info))) + r = -EFAULT; + break; + + case OMAPFB_GET_CAPS: + if (!display) { + r = -EINVAL; + break; + } + + p.caps.ctrl = display->caps; + + if (copy_to_user((void __user *)arg, &p.caps, sizeof(p.caps))) + r = -EFAULT; + break; + + case OMAPFB_SET_UPDATE_MODE: + if (get_user(p.update_mode, (int __user *)arg)) + r = -EFAULT; + else + r = omapfb_set_update_mode(fbi, p.update_mode); + break; + + case OMAPFB_GET_UPDATE_MODE: + r = omapfb_get_update_mode(fbi, &p.update_mode); + if (r) + break; + if (put_user(p.update_mode, + (enum omapfb_update_mode __user *)arg)) + r = -EFAULT; + break; + + /* LCD and CTRL tests do the same thing for backward + * compatibility */ + case OMAPFB_LCD_TEST: + if (get_user(p.test_num, (int __user *)arg)) { + r = -EFAULT; + break; + } + if (!display || !display->run_test) { + r = -EINVAL; + break; + } + + r = display->run_test(display, p.test_num); + + break; + + case OMAPFB_CTRL_TEST: + if (get_user(p.test_num, (int __user *)arg)) { + r = -EFAULT; + break; + } + if (!display || !display->run_test) { + r = -EINVAL; + break; + } + + r = display->run_test(display, p.test_num); + + break; + + default: + DBG("ioctl unhandled\n"); + r = -EINVAL; + } + + return r; +} + + diff --git a/drivers/video/omap2/omapfb-main.c b/drivers/video/omap2/omapfb-main.c new file mode 100644 index 0000000..89ad631 --- /dev/null +++ b/drivers/video/omap2/omapfb-main.c @@ -0,0 +1,1382 @@ +/* + * linux/drivers/video/omap2/omapfb-main.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "omapfb.h" + +#define MODULE_NAME "omapfb" + +#ifdef DEBUG +static void fill_fb(void *addr, struct fb_info *fbi) +{ + struct fb_var_screeninfo *var = &fbi->var; + + const short w = var->xres_virtual; + const short h = var->yres_virtual; + + int y, x; + u8 *p = addr; + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (var->bits_per_pixel == 16) { + u16 *pw = (u16 *)p; + + if (x < 20 && y < 20) + *pw = 0xffff; + else if (x == 20 || x == w - 20 || + y == 20 || y == h - 20) + *pw = 0xffff; + else if (x == y || w - x == h - y) + *pw = ((1<<5)-1)<<11; + else if (w - x == y || x == h - y) + *pw = ((1<<6)-1)<<5; + else { + int t = x / (w/3); + if (t == 0) + *pw = y % 32; + else if (t == 1) + *pw = (y % 64) << 5; + else if (t == 2) + *pw = (y % 32) << 11; + } + } else if (var->bits_per_pixel == 24) { + u8 *pb = (u8 *)p; + + int r = 0, g = 0, b = 0; + + if (x < 20 && y < 20) + r = g = b = 0xff; + else if (x == 20 || x == w - 20 || + y == 20 || y == h - 20) + r = g = b = 0xff; + else if (x == y || w - x == h - y) + r = 0xff; + else if (w - x == y || x == h - y) + g = 0xff; + else { + int q = x / (w / 3); + u8 base = 255 - (y % 256); + if (q == 0) + r = base; + else if (q == 1) + g = base; + else if (q == 2) + b = base; + } + + pb[0] = b; + pb[1] = g; + pb[2] = r; + + } else if (var->bits_per_pixel == 32) { + u32 *pd = (u32 *)p; + + if (x == 20 || x == w - 20 || + y == 20 || y == h - 20) + *pd = 0xffffff; + else if (x == y || w - x == h - y) + *pd = 0xff0000; + else if (w - x == y || x == h - y) + *pd = 0x00ff00; + else { + u8 base = 255 - (y % 256); + *pd = base << ((x / (w/3)) << 3); + } + } + + p += var->bits_per_pixel >> 3; + } + } +} +#endif + +static enum omap_color_mode fb_mode_to_dss_mode(struct fb_var_screeninfo *var) +{ + switch (var->nonstd) { + case 0: + break; + case OMAPFB_COLOR_YUV422: + return OMAP_DSS_COLOR_UYVY; + + case OMAPFB_COLOR_YUY422: + return OMAP_DSS_COLOR_YUV2; + + case OMAPFB_COLOR_ARGB16: + return OMAP_DSS_COLOR_ARGB16; + + case OMAPFB_COLOR_ARGB32: + return OMAP_DSS_COLOR_ARGB32; + + case OMAPFB_COLOR_RGBA32: + return OMAP_DSS_COLOR_RGBA32; + + case OMAPFB_COLOR_RGBX32: + return OMAP_DSS_COLOR_RGBX32; + + default: + return -EINVAL; + } + + switch (var->bits_per_pixel) { + case 1: + return OMAP_DSS_COLOR_CLUT1; + case 2: + return OMAP_DSS_COLOR_CLUT2; + case 4: + return OMAP_DSS_COLOR_CLUT4; + case 8: + return OMAP_DSS_COLOR_CLUT8; + case 12: + return OMAP_DSS_COLOR_RGB12U; + case 16: + return OMAP_DSS_COLOR_RGB16; + case 24: + return OMAP_DSS_COLOR_RGB24P; + case 32: + return OMAP_DSS_COLOR_RGB24U; + default: + return -EINVAL; + } + + return -EINVAL; +} + +void set_fb_fix(struct fb_info *fbi) +{ + struct fb_fix_screeninfo *fix = &fbi->fix; + struct fb_var_screeninfo *var = &fbi->var; + struct omapfb_mem_region *rg = &FB2OFB(fbi)->region; + + DBG("set_fb_fix\n"); + + /* used by open/write in fbmem.c */ + fbi->screen_base = (char __iomem *)rg->vaddr; + + /* used by mmap in fbmem.c */ + fix->smem_start = rg->paddr; + fix->smem_len = rg->size; + + fix->type = FB_TYPE_PACKED_PIXELS; + + if (var->nonstd) + fix->visual = FB_VISUAL_PSEUDOCOLOR; + else { + switch (var->bits_per_pixel) { + case 32: + case 24: + case 16: + case 12: + fix->visual = FB_VISUAL_TRUECOLOR; + /* 12bpp is stored in 16 bits */ + break; + case 1: + case 2: + case 4: + case 8: + fix->visual = FB_VISUAL_PSEUDOCOLOR; + break; + } + } + + fix->accel = FB_ACCEL_NONE; + fix->line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; + + fix->xpanstep = 1; + fix->ypanstep = 1; +} + +/* check new var and possibly modify it to be ok */ +int check_fb_var(struct fb_info *fbi, struct fb_var_screeninfo *var) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omap_display *display = fb2display(fbi); + unsigned long max_frame_size; + unsigned long line_size; + int xres_min, xres_max; + int yres_min, yres_max; + enum omap_color_mode mode = 0; + struct omap_overlay *ovl; + + DBG("check_fb_var %d\n", ofbi->id); + + if (ofbi->region.size == 0) { + memset(var, 0, sizeof(*var)); + return 0; + } + + if (ofbi->num_overlays == 0) { + dev_err(ofbi->fbdev->dev, "no overlays, aborting\n"); + return -EINVAL; + } + + /* XXX: uses the first overlay */ + ovl = ofbi->overlays[0]; + + /* if we are using non standard mode, fix the bpp first */ + switch (var->nonstd) { + case 0: + break; + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + case OMAPFB_COLOR_ARGB16: + var->bits_per_pixel = 16; + break; + case OMAPFB_COLOR_ARGB32: + case OMAPFB_COLOR_RGBA32: + case OMAPFB_COLOR_RGBX32: + var->bits_per_pixel = 32; + break; + default: + DBG("invalid nonstd mode\n"); + return -EINVAL; + } + + mode = fb_mode_to_dss_mode(var); + if (mode < 0) { + DBG("cannot convert var to omap dss mode\n"); + return -EINVAL; + } + + if ((ovl->supported_modes & mode) == 0) { + DBG("invalid mode\n"); + return -EINVAL; + } + + xres_min = OMAPFB_PLANE_XRES_MIN; + xres_max = (display ? display->x_res : 2048) - ovl->info.pos_x; + yres_min = OMAPFB_PLANE_YRES_MIN; + yres_max = (display ? display->y_res : 2048) - ovl->info.pos_y; + + if (var->xres < xres_min) + var->xres = xres_min; + if (var->yres < yres_min) + var->yres = yres_min; + 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 = ofbi->region.size; + line_size = (var->xres_virtual * var->bits_per_pixel) >> 3; + + 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 / var->bits_per_pixel; + if (var->xres_virtual < var->xres) { + /* Still doesn't fit. Shrink yres_virtual too */ + var->xres_virtual = var->xres; + line_size = var->xres * var->bits_per_pixel / 8; + var->yres_virtual = max_frame_size / line_size; + } + /* Recheck this, as the virtual size changed. */ + if (var->xres_virtual < var->xres) + var->xres = var->xres_virtual; + if (var->yres_virtual < var->yres) + var->yres = var->yres_virtual; + if (var->xres < xres_min || var->yres < yres_min) { + DBG("Cannot fit FB to memory\n"); + return -EINVAL; + } + } + if (var->xres + var->xoffset > var->xres_virtual) + var->xoffset = var->xres_virtual - var->xres; + if (var->yres + var->yoffset > var->yres_virtual) + var->yoffset = var->yres_virtual - var->yres; + + if (var->bits_per_pixel == 16) { + 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; + } else if (var->bits_per_pixel == 24) { + var->red.offset = 16; var->red.length = 8; + var->red.msb_right = 0; + var->green.offset = 8; var->green.length = 8; + var->green.msb_right = 0; + var->blue.offset = 0; var->blue.length = 8; + var->blue.msb_right = 0; + var->transp.offset = 0; var->transp.length = 0; + } else if (var->bits_per_pixel == 32) { + var->red.offset = 16; var->red.length = 8; + var->red.msb_right = 0; + var->green.offset = 8; var->green.length = 8; + var->green.msb_right = 0; + var->blue.offset = 0; var->blue.length = 8; + var->blue.msb_right = 0; + var->transp.offset = 0; var->transp.length = 0; + } else { + DBG("failed to setup fb color mask\n"); + return -EINVAL; + } + + DBG("xres = %d, yres = %d, vxres = %d, vyres = %d\n", + var->xres, var->yres, + var->xres_virtual, var->yres_virtual); + + var->height = -1; + var->width = -1; + var->grayscale = 0; + + if (display && display->check_timings) { + struct omap_video_timings timings; + + if (var->pixclock == 0) { + DBG("Pixclock can't be zero.\n"); + return -EINVAL; + } + + timings.pixel_clock = PICOS2KHZ(var->pixclock); + timings.hfp = var->left_margin; + timings.hbp = var->right_margin; + timings.vfp = var->upper_margin; + timings.vbp = var->lower_margin; + timings.hsw = var->hsync_len; + timings.vsw = var->vsync_len; + + if (display->check_timings(display, &timings)) { + DBG("illegal video timings\n"); + return -EINVAL; + } + + /* pixclock in ps, the rest in pixclock */ + var->pixclock = KHZ2PICOS(timings.pixel_clock); + var->left_margin = timings.hfp; + var->right_margin = timings.hbp; + var->upper_margin = timings.vfp; + var->lower_margin = timings.vbp; + var->hsync_len = timings.hsw; + var->vsync_len = timings.vsw; + } + + /* TODO: get these from panel->config */ + var->vmode = FB_VMODE_NONINTERLACED; + var->sync = 0; + + return 0; +} + +/* + * --------------------------------------------------------------------------- + * fbdev framework callbacks + * --------------------------------------------------------------------------- + */ +static int omapfb_open(struct fb_info *fbi, int user) +{ + return 0; +} + +static int omapfb_release(struct fb_info *fbi, int user) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + + DBG("Closing fb with plane index %d\n", ofbi->id); + + omapfb_lock(fbdev); +#if 1 + if (display) { + /* XXX Is this really needed ? */ + if (display->sync) + display->sync(display); + + if (display->update) + display->update(display, + 0, 0, + display->x_res, display->y_res); + } +#endif + + if (display && display->sync) + display->sync(display); + + omapfb_unlock(fbdev); + + return 0; +} + +/* setup overlay according to the fb */ +int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, + int posx, int posy, int outw, int outh) +{ + int r = 0; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo *var = &fbi->var; + enum omap_color_mode mode = 0; + int offset; + u32 data_start_p; + void *data_start_v; + + DBG("setup_overlay %d\n", ofbi->id); + + if ((ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0 && + (outw != var->xres || outh != var->yres)) { + r = -EINVAL; + goto err; + } + + offset = ((var->yoffset * var->xres_virtual + + var->xoffset) * var->bits_per_pixel) >> 3; + + data_start_p = ofbi->region.paddr + offset; + data_start_v = ofbi->region.vaddr + offset; + + mode = fb_mode_to_dss_mode(var); + + if (mode == -EINVAL) { + r = -EINVAL; + goto err; + } + + r = ovl->setup_input(ovl, + data_start_p, data_start_v, + var->xres_virtual, + var->xres, var->yres, + mode); + + if (r) + goto err; + + r = ovl->setup_output(ovl, + posx, posy, + outw, outh); + + if (r) + goto err; + + return 0; + +err: + DBG("setup_overlay failed\n"); + return r; +} + +/* apply var to the overlay */ +int omapfb_apply_changes(struct fb_info *fbi, int init) +{ + int r = 0; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo *var = &fbi->var; + /*struct omap_display *display = fb2display(fbi);*/ + struct omap_overlay *ovl; + int posx, posy; + int outw, outh; + int i; + + for (i = 0; i < ofbi->num_overlays; i++) { + ovl = ofbi->overlays[i]; + + DBG("apply_changes, fb %d, ovl %d\n", ofbi->id, ovl->id); + + if (ofbi->region.size == 0) { + /* the fb is not available. disable the overlay */ + ovl->enable(ovl, 0); + if (!init && ovl->manager) + ovl->manager->apply(ovl->manager); + continue; + } + + if (init || (ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { + outw = var->xres; + outh = var->yres; + } else { + outw = ovl->info.out_width; + outh = ovl->info.out_height; + } + + if (init) { + posx = 0; + posy = 0; + } else { + posx = ovl->info.pos_x; + posy = ovl->info.pos_y; + } + + r = omapfb_setup_overlay(fbi, ovl, posx, posy, outw, outh); + if (r) + goto err; + + /* disabled for now. if the display has changed, var + * still contains the old timings. */ +#if 0 + if (display && display->set_timings) { + struct omap_video_timings timings; + timings.pixel_clock = PICOS2KHZ(var->pixclock); + timings.hfp = var->left_margin; + timings.hbp = var->right_margin; + timings.vfp = var->upper_margin; + timings.vbp = var->lower_margin; + timings.hsw = var->hsync_len; + timings.vsw = var->vsync_len; + + display->set_timings(display, &timings); + } +#endif + if (!init && ovl->manager) + ovl->manager->apply(ovl->manager); + } + return 0; +err: + DBG("apply_changes failed\n"); + return r; +} + +/* checks var and eventually tweaks it to something supported, + * DO NOT MODIFY PAR */ +static int omapfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) +{ + int r; + + DBG("check_var(%d)\n", FB2OFB(fbi)->id); + + r = check_fb_var(fbi, var); + + return r; +} + +/* set the video mode according to info->var */ +static int omapfb_set_par(struct fb_info *fbi) +{ + int r; + + DBG("set_par(%d)\n", FB2OFB(fbi)->id); + + set_fb_fix(fbi); + r = omapfb_apply_changes(fbi, 0); + + return r; +} + +static void omapfb_rotate(struct fb_info *fbi, int rotate) +{ + DBG("rotate(%d)\n", FB2OFB(fbi)->id); + return; +} + +static int omapfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + int r = 0; + + DBG("pan_display(%d)\n", ofbi->id); + + omapfb_lock(fbdev); + + if (var->xoffset != fbi->var.xoffset || + var->yoffset != fbi->var.yoffset) { + struct fb_var_screeninfo new_var; + + new_var = fbi->var; + new_var.xoffset = var->xoffset; + new_var.yoffset = var->yoffset; + + r = check_fb_var(fbi, &new_var); + + if (r == 0) { + fbi->var = new_var; + set_fb_fix(fbi); + r = omapfb_apply_changes(fbi, 0); + } + } + + omapfb_unlock(fbdev); + + return r; +} + +static void mmap_user_open(struct vm_area_struct *vma) +{ + struct omapfb_info *ofbi = (struct omapfb_info *)vma->vm_private_data; + + atomic_inc(&ofbi->map_count); +} + +static void mmap_user_close(struct vm_area_struct *vma) +{ + struct omapfb_info *ofbi = (struct omapfb_info *)vma->vm_private_data; + + atomic_dec(&ofbi->map_count); +} + +static struct vm_operations_struct mmap_user_ops = { + .open = mmap_user_open, + .close = mmap_user_close, +}; + +static int omapfb_mmap(struct fb_info *fbi, struct vm_area_struct *vma) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb_mem_region *rg = &ofbi->region; + unsigned long off; + unsigned long start; + u32 len; + + if (vma->vm_end - vma->vm_start == 0) + return 0; + if (vma->vm_pgoff > (~0UL >> PAGE_SHIFT)) + return -EINVAL; + off = vma->vm_pgoff << PAGE_SHIFT; + + start = rg->paddr; + len = rg->size; + if (off >= len) + return -EINVAL; + if ((vma->vm_end - vma->vm_start + off) > len) + return -EINVAL; + off += start; + vma->vm_pgoff = off >> PAGE_SHIFT; + vma->vm_flags |= VM_IO | VM_RESERVED; + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &mmap_user_ops; + vma->vm_private_data = ofbi; + if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, vma->vm_page_prot)) + return -EAGAIN; + /* vm_ops.open won't be called for mmap itself. */ + atomic_inc(&ofbi->map_count); + return 0; +} + +/* Store a single color palette entry into a pseudo palette or the hardware + * palette if one is available. For now we support only 16bpp and thus store + * the entry only to the pseudo palette. + */ +static int _setcolreg(struct fb_info *fbi, u_int regno, u_int red, u_int green, + u_int blue, u_int transp, int update_hw_pal) +{ + /*struct omapfb_info *ofbi = FB2OFB(fbi);*/ + /*struct omapfb2_device *fbdev = ofbi->fbdev;*/ + struct fb_var_screeninfo *var = &fbi->var; + int r = 0; + + enum omapfb_color_format mode = OMAPFB_COLOR_RGB24U; /* XXX */ + + /*switch (plane->color_mode) {*/ + switch (mode) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUV420: + case OMAPFB_COLOR_YUY422: + r = -EINVAL; + break; + case OMAPFB_COLOR_CLUT_8BPP: + case OMAPFB_COLOR_CLUT_4BPP: + case OMAPFB_COLOR_CLUT_2BPP: + case OMAPFB_COLOR_CLUT_1BPP: + /* + if (fbdev->ctrl->setcolreg) + r = fbdev->ctrl->setcolreg(regno, red, green, blue, + transp, update_hw_pal); + */ + /* Fallthrough */ + r = -EINVAL; + break; + case OMAPFB_COLOR_RGB565: + case OMAPFB_COLOR_RGB444: + case OMAPFB_COLOR_RGB24P: + case OMAPFB_COLOR_RGB24U: + if (r != 0) + break; + + if (regno < 0) { + r = -EINVAL; + break; + } + + if (regno < 16) { + u16 pal; + pal = ((red >> (16 - var->red.length)) << + var->red.offset) | + ((green >> (16 - var->green.length)) << + var->green.offset) | + (blue >> (16 - var->blue.length)); + ((u32 *)(fbi->pseudo_palette))[regno] = pal; + } + break; + default: + BUG(); + } + return r; +} + +static int omapfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + DBG("setcolreg\n"); + + return _setcolreg(info, regno, red, green, blue, transp, 1); +} + +static int omapfb_setcmap(struct fb_cmap *cmap, struct fb_info *info) +{ + int count, index, r; + u16 *red, *green, *blue, *transp; + u16 trans = 0xffff; + + DBG("setcmap\n"); + + red = cmap->red; + green = cmap->green; + blue = cmap->blue; + transp = cmap->transp; + index = cmap->start; + + for (count = 0; count < cmap->len; count++) { + if (transp) + trans = *transp++; + r = _setcolreg(info, index++, *red++, *green++, *blue++, trans, + count == cmap->len - 1); + if (r != 0) + return r; + } + + return 0; +} + +static int omapfb_blank(int blank, struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_display *display = fb2display(fbi); + int do_update = 0; + int r = 0; + + omapfb_lock(fbdev); + + switch (blank) { + case FB_BLANK_UNBLANK: + if (display->state != OMAP_DSS_DISPLAY_SUSPENDED) { + r = -EINVAL; + goto exit; + } + + if (display->resume) + r = display->resume(display); + + if (r == 0 && display->get_update_mode && + display->get_update_mode(display) == + OMAP_DSS_UPDATE_MANUAL) + do_update = 1; + + break; + + case FB_BLANK_POWERDOWN: + if (display->state != OMAP_DSS_DISPLAY_ACTIVE) { + r = -EINVAL; + goto exit; + } + + if (display->suspend) + r = display->suspend(display); + + break; + + default: + r = -EINVAL; + } + +exit: + omapfb_unlock(fbdev); + + if (r == 0 && do_update && display->update) + r = display->update(display, + 0, 0, + display->x_res, display->y_res); + + return r; +} + +static struct fb_ops omapfb_ops = { + .owner = THIS_MODULE, + .fb_open = omapfb_open, + .fb_release = omapfb_release, + .fb_fillrect = cfb_fillrect, + .fb_copyarea = cfb_copyarea, + .fb_imageblit = cfb_imageblit, + .fb_blank = omapfb_blank, + .fb_ioctl = omapfb_ioctl, + .fb_check_var = omapfb_check_var, + .fb_set_par = omapfb_set_par, + .fb_rotate = omapfb_rotate, + .fb_pan_display = omapfb_pan_display, + .fb_mmap = omapfb_mmap, + .fb_setcolreg = omapfb_setcolreg, + .fb_setcmap = omapfb_setcmap, +}; + +static void omapfb_free_fbmem(struct omapfb2_device *fbdev, int fbnum) +{ + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[fbnum]); + struct omapfb_mem_region *rg; + + rg = &ofbi->region; + + if (rg->paddr) + if (omap_vram_free(rg->paddr, rg->vaddr, rg->size)) + printk("VRAM FREE failed\n"); + + rg->vaddr = NULL; + rg->paddr = 0; + rg->alloc = 0; + rg->size = 0; +} + +static int omapfb_free_all_fbmem(struct omapfb2_device *fbdev) +{ + int i; + + DBG("free all fbmem\n"); + + for (i = 0; i < fbdev->num_fbs; i++) + omapfb_free_fbmem(fbdev, i); + + return 0; +} + +static int omapfb_alloc_fbmem(struct omapfb2_device *fbdev, int fbnum, + unsigned long size) +{ + struct omapfb_info *ofbi; + struct omapfb_mem_region *rg; + unsigned long paddr; + void *vaddr; + + ofbi = FB2OFB(fbdev->fbs[fbnum]); + rg = &ofbi->region; + memset(rg, 0, sizeof(*rg)); + + DBG("allocating %lu bytes for fb %d\n", + size, ofbi->id); + + vaddr = omap_vram_alloc(OMAPFB_MEMTYPE_SDRAM, size, &paddr); + DBG("VRAM ALLOCCI paddr %lx, vaddr %p\n", paddr, vaddr); + + if (vaddr == NULL) { + dev_err(fbdev->dev, + "failed to allocate framebuffer\n"); + return -ENOMEM; + } + + rg->paddr = paddr; + rg->vaddr = vaddr; + rg->size = size; + rg->alloc = 1; + + return 0; +} + +int omapfb_realloc_fbmem(struct omapfb2_device *fbdev, int fbnum, + unsigned long size) +{ + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[fbnum]); + struct omapfb_mem_region *rg = &ofbi->region; + unsigned old_size = rg->size; + int r; + + omapfb_free_fbmem(fbdev, fbnum); + + if (size == 0) + return 0; + + r = omapfb_alloc_fbmem(fbdev, fbnum, size); + + if (r) + omapfb_alloc_fbmem(fbdev, fbnum, old_size); + + return r; +} + +/* allocate fbmem using display resolution as reference */ +static int omapfb_alloc_fbmem_display(struct omapfb2_device *fbdev, int fbnum) +{ + struct omapfb_info *ofbi; + struct omap_display *display; + int bytespp; + unsigned long size; + + ofbi = FB2OFB(fbdev->fbs[fbnum]); + display = fb2display(fbdev->fbs[fbnum]); + + if (!display) + return 0; + + switch (display->bpp) { + case 16: + bytespp = 2; + break; + case 24: + case 32: + bytespp = 4; + break; + default: + bytespp = 4; + break; + } + + size = display->x_res * display->y_res * bytespp; + + return omapfb_alloc_fbmem(fbdev, fbnum, size); +} + +static int omapfb_allocate_all_fbs(struct omapfb2_device *fbdev) +{ + int i, r; + + for (i = 0; i < fbdev->num_fbs; i++) { + r = omapfb_alloc_fbmem_display(fbdev, i); + + if (r) + return r; + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + struct omapfb_mem_region *rg; + rg = &ofbi->region; + + DBG("region%d phys %08x virt %p size=%lu\n", + i, + rg->paddr, + rg->vaddr, + rg->size); + } + + return 0; +} + +/* initialize fb_info, var, fix to something sane based on the display */ +static int fbinfo_init(struct omapfb2_device *fbdev, struct fb_info *fbi) +{ + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + struct omap_display *display = fb2display(fbi); + int r = 0; + + fbi->fbops = &omapfb_ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = fbdev->pseudo_palette; + + strncpy(fix->id, MODULE_NAME, sizeof(fix->id)); + + var->nonstd = 0; + + if (display) { + var->xres = display->x_res; + var->yres = display->y_res; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + /* var->rotate = def_rotate; */ + + switch (display->bpp) { + case 16: + var->bits_per_pixel = 16; + break; + case 18: + var->bits_per_pixel = 16; + break; + case 24: + var->bits_per_pixel = 32; + break; + default: + dev_err(fbdev->dev, "illegal display bpp\n"); + return -EINVAL; + } + + if (display->get_timings) { + struct omap_video_timings timings; + display->get_timings(display, &timings); + + /* pixclock in ps, the rest in pixclock */ + var->pixclock = KHZ2PICOS(timings.pixel_clock); + var->left_margin = timings.hfp; + var->right_margin = timings.hbp; + var->upper_margin = timings.vfp; + var->lower_margin = timings.vbp; + var->hsync_len = timings.hsw; + var->vsync_len = timings.vsw; + } else { + var->pixclock = 0; + var->left_margin = 0; + var->right_margin = 0; + var->upper_margin = 0; + var->lower_margin = 0; + var->hsync_len = 0; + var->vsync_len = 0; + } + } + + r = check_fb_var(fbi, var); + if (r) + goto err; + + set_fb_fix(fbi); + +#ifdef DEBUG + fill_fb(FB2OFB(fbi)->region.vaddr, fbi); +#endif +err: + return r; +} + +static void fbinfo_cleanup(struct omapfb2_device *fbdev, struct fb_info *fbi) +{ + fb_dealloc_cmap(&fbi->cmap); +} + + +static void omapfb_free_resources(struct omapfb2_device *fbdev) +{ + int i; + + DBG("free_resources\n"); + + if (fbdev == NULL) + return; + + for (i = 0; i < fbdev->num_fbs; i++) + unregister_framebuffer(fbdev->fbs[i]); + + /* free the reserved fbmem */ + omapfb_free_all_fbmem(fbdev); + + for (i = 0; i < fbdev->num_fbs; i++) { + fbinfo_cleanup(fbdev, fbdev->fbs[i]); + framebuffer_release(fbdev->fbs[i]); + } + + for (i = 0; i < fbdev->num_displays; i++) { + if (fbdev->displays[i]->state != OMAP_DSS_DISPLAY_DISABLED) + fbdev->displays[i]->disable(fbdev->displays[i]); + + omap_dss_put_display(fbdev->displays[i]); + } + + dev_set_drvdata(fbdev->dev, NULL); + kfree(fbdev); +} + +static int omapfb_create_framebuffers(struct omapfb2_device *fbdev) +{ + int r, i; + + fbdev->num_fbs = 0; + + DBG("create %d framebuffers\n", CONFIG_FB_OMAP2_NUM_FBS); + + /* allocate fb_infos */ + for (i = 0; i < CONFIG_FB_OMAP2_NUM_FBS; i++) { + struct fb_info *fbi; + struct omapfb_info *ofbi; + + fbi = framebuffer_alloc(sizeof(struct omapfb_info), + fbdev->dev); + + if (fbi == NULL) { + dev_err(fbdev->dev, + "unable to allocate memory for plane info\n"); + return -ENOMEM; + } + + fbdev->fbs[i] = fbi; + + ofbi = FB2OFB(fbi); + ofbi->fbdev = fbdev; + ofbi->id = i; + fbdev->num_fbs++; + } + + DBG("fb_infos allocated\n"); + + /* assign overlays for the fbs */ + for (i = 0; i < min(fbdev->num_fbs, fbdev->num_overlays); i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + + ofbi->overlays[0] = fbdev->overlays[i]; + ofbi->num_overlays = 1; + } + + /* allocate fb memories */ + r = omapfb_allocate_all_fbs(fbdev); + if (r) { + dev_err(fbdev->dev, "failed to allocate fbmem\n"); + return r; + } + + DBG("fbmems allocated\n"); + + /* setup fb_infos */ + for (i = 0; i < fbdev->num_fbs; i++) { + r = fbinfo_init(fbdev, fbdev->fbs[i]); + if (r) { + dev_err(fbdev->dev, "failed to setup fb_info\n"); + return r; + } + } + + DBG("fb_infos initialized\n"); + + for (i = 0; i < fbdev->num_fbs; i++) { + r = register_framebuffer(fbdev->fbs[i]); + if (r != 0) { + dev_err(fbdev->dev, + "registering framebuffer %d failed\n", i); + return r; + } + } + + DBG("framebuffers registered\n"); + + for (i = 0; i < fbdev->num_fbs; i++) { + r = omapfb_apply_changes(fbdev->fbs[i], 1); + if (r) + dev_err(fbdev->dev, "failed to change mode\n"); + } + + /* Enable the first framebuffer that has overlay that is connected + * to display. Usually this would be the GFX plane. */ + r = 0; + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + int t; + + for (t = 0; t < ofbi->num_overlays; t++) { + struct omap_overlay *ovl = ofbi->overlays[t]; + if (ovl->manager && ovl->manager->display) { + ovl->enable(ovl, 1); + r = 1; + break; + } + } + + if (r) + break; + } + + DBG("create_framebuffers done\n"); + + return 0; +} + +static int omapfb_probe(struct platform_device *pdev) +{ + struct omapfb2_device *fbdev = NULL; + int r = 0; + int i, t; + struct omap_overlay *ovl; + struct omap_display *def_display; + + DBG("omapfb_probe\n"); + + if (pdev->num_resources != 0) { + dev_err(&pdev->dev, "probed for an unknown device\n"); + r = -ENODEV; + goto err0; + } + + if (pdev->dev.platform_data == NULL) { + dev_err(&pdev->dev, "missing platform data\n"); + r = -ENOENT; + goto err0; + } + + fbdev = kzalloc(sizeof(struct omapfb2_device), GFP_KERNEL); + if (fbdev == NULL) { + r = -ENOMEM; + goto err0; + } + + mutex_init(&fbdev->mtx); + + fbdev->dev = &pdev->dev; + platform_set_drvdata(pdev, fbdev); + + fbdev->num_displays = 0; + t = omap_dss_get_num_displays(); + for (i = 0; i < t; i++) { + struct omap_display *display; + display = omap_dss_get_display(i); + if (!display) { + dev_err(&pdev->dev, "can't get display %d\n", i); + r = -EINVAL; + goto cleanup; + } + + fbdev->displays[fbdev->num_displays++] = display; + } + + if (fbdev->num_displays == 0) { + dev_err(&pdev->dev, "no displays\n"); + r = -EINVAL; + goto cleanup; + } + + fbdev->num_overlays = omap_dss_get_num_overlays(); + for (i = 0; i < fbdev->num_overlays; i++) + fbdev->overlays[i] = omap_dss_get_overlay(i); + + fbdev->num_managers = omap_dss_get_num_overlay_managers(); + for (i = 0; i < fbdev->num_managers; i++) + fbdev->managers[i] = omap_dss_get_overlay_manager(i); + + + /* gfx overlay should be the default one. find a display + * connected to that, and use it as default display */ + ovl = omap_dss_get_overlay(0); + if (ovl->manager && ovl->manager->display) { + def_display = ovl->manager->display; + } else { + dev_err(&pdev->dev, "cannot find default display\n"); + r = -EINVAL; + goto cleanup; + } + + r = omapfb_create_framebuffers(fbdev); + if (r) + goto cleanup; + + for (i = 0; i < fbdev->num_managers; i++) { + struct omap_overlay_manager *mgr; + mgr = fbdev->managers[i]; + r = mgr->apply(mgr); + if (r) { + dev_err(fbdev->dev, "failed to apply dispc config\n"); + goto cleanup; + } + } + + DBG("mgr->apply'ed\n"); + + r = def_display->enable(def_display); + if (r) { + dev_err(fbdev->dev, "Failed to enable display '%s'\n", + def_display->name); + goto cleanup; + } + + /* set the update mode */ + if (def_display->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { +#ifdef CONFIG_FB_OMAP2_FORCE_AUTO_UPDATE + if (def_display->set_update_mode) + def_display->set_update_mode(def_display, + OMAP_DSS_UPDATE_AUTO); + if (def_display->enable_te) + def_display->enable_te(def_display, 1); +#else + if (def_display->set_update_mode) + def_display->set_update_mode(def_display, + OMAP_DSS_UPDATE_MANUAL); + if (def_display->enable_te) + def_display->enable_te(def_display, 0); +#endif + } else { + if (def_display->set_update_mode) + def_display->set_update_mode(def_display, + OMAP_DSS_UPDATE_AUTO); + } + + for (i = 0; i < fbdev->num_displays; i++) { + struct omap_display *display = fbdev->displays[i]; + + if (display->update) + display->update(display, + 0, 0, + display->x_res, display->y_res); + } + + DBG("display->updated\n"); + + omapfb_create_sysfs(fbdev); + DBG("sysfs created\n"); + + return 0; + +cleanup: + omapfb_free_resources(fbdev); +err0: + dev_err(&pdev->dev, "failed to setup omapfb\n"); + return r; +} + +static int omapfb_remove(struct platform_device *pdev) +{ + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + + /* FIXME: wait till completion of pending events */ + + omapfb_remove_sysfs(fbdev); + + omapfb_free_resources(fbdev); + + return 0; +} + +static struct platform_driver omapfb_driver = { + .probe = omapfb_probe, + .remove = omapfb_remove, + .driver = { + .name = "omapfb", + .owner = THIS_MODULE, + }, +}; + +static int __init omapfb_init(void) +{ + DBG("omapfb_init\n"); + + if (platform_driver_register(&omapfb_driver)) { + printk(KERN_ERR "failed to register omapfb driver\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit omapfb_exit(void) +{ + DBG("omapfb_exit\n"); + platform_driver_unregister(&omapfb_driver); +} + +/* late_initcall to let panel/ctrl drivers loaded first. + * I guess better option would be a more dynamic approach, + * so that omapfb reacts to new panels when they are loaded */ +late_initcall(omapfb_init); +/*module_init(omapfb_init);*/ +module_exit(omapfb_exit); + +MODULE_AUTHOR("Tomi Valkeinen "); +MODULE_DESCRIPTION("OMAP2/3 Framebuffer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/omap2/omapfb-sysfs.c b/drivers/video/omap2/omapfb-sysfs.c new file mode 100644 index 0000000..59b48ac --- /dev/null +++ b/drivers/video/omap2/omapfb-sysfs.c @@ -0,0 +1,838 @@ +/* + * linux/drivers/video/omap2/omapfb-sysfs.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "omapfb.h" + +static int omapfb_attach_framebuffer(struct fb_info *fbi, + struct omap_overlay *ovl) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + int i, t; + int r; + + if (ofbi->num_overlays >= OMAPFB_MAX_OVL_PER_FB) { + dev_err(fbdev->dev, "fb has max number of overlays already\n"); + return -EINVAL; + } + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ofbi->overlays[i] == ovl) { + dev_err(fbdev->dev, "fb already attached to overlay\n"); + return -EINVAL; + } + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi2 = FB2OFB(fbdev->fbs[i]); + for (t = 0; t < ofbi2->num_overlays; t++) { + if (ofbi2->overlays[t] == ovl) { + dev_err(fbdev->dev, "overlay already in use\n"); + return -EINVAL; + } + } + } + + ofbi->overlays[ofbi->num_overlays++] = ovl; + +/* + if (ovl->manager && ovl->manager->display) + omapfb_adjust_fb(fbi, ovl, 0, 0); +*/ + r = omapfb_apply_changes(fbi, 1); + if (r) + return r; + + if (ovl->manager) + ovl->manager->apply(ovl->manager); + + return 0; +} + +static int omapfb_detach_framebuffer(struct fb_info *fbi, + struct omap_overlay *ovl) +{ + int i; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ofbi->overlays[i] == ovl) + break; + } + + if (i == ofbi->num_overlays) { + dev_err(fbdev->dev, "cannot detach fb, overlay not attached\n"); + return -EINVAL; + } + + ovl->enable(ovl, 0); + + if (ovl->manager) + ovl->manager->apply(ovl->manager); + + for (i = i + 1; i < ofbi->num_overlays; i++) + ofbi->overlays[i-1] = ofbi->overlays[i]; + + ofbi->num_overlays--; + + return 0; +} + + +static ssize_t show_framebuffers(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + ssize_t l = 0, size = PAGE_SIZE; + int i, t; + + omapfb_lock(fbdev); + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + struct omapfb_mem_region *rg; + + rg = &ofbi->region; + + l += snprintf(buf + l, size - l, "%d p:%08x v:%p size:%lu t:", + ofbi->id, + rg->paddr, rg->vaddr, rg->size); + + if (ofbi->num_overlays == 0) + l += snprintf(buf + l, size - l, "none"); + + for (t = 0; t < ofbi->num_overlays; t++) { + struct omap_overlay *ovl; + ovl = ofbi->overlays[t]; + + l += snprintf(buf + l, size - l, "%s%s", + t == 0 ? "" : ",", + ovl->name); + } + + l += snprintf(buf + l, size - l, "\n"); + } + + omapfb_unlock(fbdev); + + return l; +} + +static struct omap_overlay *find_overlay_by_name(struct omapfb2_device *fbdev, + char *name) +{ + int i; + + for (i = 0; i < fbdev->num_overlays; i++) + if (strcmp(name, fbdev->overlays[i]->name) == 0) + return fbdev->overlays[i]; + + return NULL; +} + +static struct omap_display *find_display_by_name(struct omapfb2_device *fbdev, + char *name) +{ + int i; + + for (i = 0; i < fbdev->num_displays; i++) + if (strcmp(name, fbdev->displays[i]->name) == 0) + return fbdev->displays[i]; + + return NULL; +} + +static struct omap_overlay_manager *find_manager_by_name( + struct omapfb2_device *fbdev, + char *name) +{ + int i; + + for (i = 0; i < fbdev->num_managers; i++) + if (strcmp(name, fbdev->managers[i]->name) == 0) + return fbdev->managers[i]; + + return NULL; +} + +static int parse_overlays(struct omapfb2_device *fbdev, char *str, + struct omap_overlay *ovls[]) +{ + int num_ovls = 0; + int s, e = 0; + char ovlname[10]; + + while (1) { + struct omap_overlay *ovl; + + s = e; + + while (e < strlen(str) && str[e] != ',') + e++; + + strncpy(ovlname, str + s, e - s); + ovlname[e-s] = 0; + + DBG("searching for '%s'\n", ovlname); + ovl = find_overlay_by_name(fbdev, ovlname); + + if (ovl) { + DBG("found an overlay\n"); + ovls[num_ovls] = ovl; + num_ovls++; + } else { + DBG("unknown overlay %s\n", str); + return 0; + } + + if (e == strlen(str)) + break; + + e++; + } + + return num_ovls; +} + +static ssize_t store_framebuffers(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + int idx; + char fbname[3]; + unsigned long fbnum; + char ovlnames[40]; + int num_ovls = 0; + struct omap_overlay *ovls[OMAPFB_MAX_OVL_PER_FB]; + struct fb_info *fbi; + struct omapfb_info *ofbi; + int r, i; + + idx = 0; + while (idx < count && buf[idx] != ' ') + ++idx; + + if (idx == count) + return -EINVAL; + + if (idx >= sizeof(fbname)) + return -EINVAL; + + strncpy(fbname, buf, idx); + fbname[idx] = 0; + idx++; + + if (strict_strtoul(fbname, 10, &fbnum)) + return -EINVAL; + + r = sscanf(buf + idx, "t:%39s", ovlnames); + + if (r != 1) { + r = -EINVAL; + goto err; + } + + omapfb_lock(fbdev); + + if (fbnum >= fbdev->num_fbs) { + dev_err(dev, "fb not found\n"); + r = -EINVAL; + goto err; + } + + fbi = fbdev->fbs[fbnum]; + ofbi = FB2OFB(fbi); + + if (strcmp(ovlnames, "none") == 0) { + num_ovls = 0; + } else { + num_ovls = parse_overlays(fbdev, ovlnames, ovls); + + if (num_ovls == 0) { + dev_err(dev, "overlays not found\n"); + r = -EINVAL; + goto err; + } + } + + for (i = 0; i < ofbi->num_overlays; i++) { + r = omapfb_detach_framebuffer(fbi, ofbi->overlays[i]); + if (r) { + dev_err(dev, "detach failed\n"); + goto err; + } + } + + if (num_ovls > 0) { + for (i = 0; i < num_ovls; i++) { + r = omapfb_attach_framebuffer(fbi, ovls[i]); + if (r) { + dev_err(dev, "attach failed\n"); + goto err; + } + } + } + + omapfb_unlock(fbdev); + return count; + +err: + omapfb_unlock(fbdev); + return r; +} + +static ssize_t show_overlays(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + ssize_t l = 0, size = PAGE_SIZE; + int i, mgr_num; + + omapfb_lock(fbdev); + + for (i = 0; i < fbdev->num_overlays; i++) { + struct omap_overlay *ovl; + struct omap_overlay_manager *mgr; + + ovl = fbdev->overlays[i]; + mgr = ovl->manager; + + for (mgr_num = 0; mgr_num < fbdev->num_managers; mgr_num++) + if (fbdev->managers[mgr_num] == mgr) + break; + + l += snprintf(buf + l, size - l, + "%s t:%s x:%d y:%d iw:%d ih:%d w: %d h: %d e:%d\n", + ovl->name, + mgr ? mgr->name : "none", + ovl->info.pos_x, + ovl->info.pos_y, + ovl->info.width, + ovl->info.height, + ovl->info.out_width, + ovl->info.out_height, + ovl->info.enabled); + } + + omapfb_unlock(fbdev); + + return l; +} + +static ssize_t store_overlays(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + int idx; + struct omap_overlay *ovl = NULL; + struct omap_overlay_manager *mgr; + int r; + char ovlname[10]; + int posx, posy, outw, outh; + int enabled; + + idx = 0; + while (idx < count && buf[idx] != ' ') + ++idx; + + if (idx == count) + return -EINVAL; + + if (idx >= sizeof(ovlname)) + return -EINVAL; + + strncpy(ovlname, buf, idx); + ovlname[idx] = 0; + idx++; + + omapfb_lock(fbdev); + + ovl = find_overlay_by_name(fbdev, ovlname); + + if (!ovl) { + dev_err(dev, "ovl not found\n"); + r = -EINVAL; + goto err; + } + + DBG("ovl %s found\n", ovl->name); + + mgr = ovl->manager; + + posx = ovl->info.pos_x; + posy = ovl->info.pos_y; + outw = ovl->info.out_width; + outh = ovl->info.out_height; + enabled = ovl->info.enabled; + + while (idx < count) { + char c; + int val; + int len; + char sval[10]; + + r = sscanf(buf + idx, "%c:%d%n", &c, &val, &len); + + if (r != 2) { + val = 0; + + r = sscanf(buf + idx, "%c:%9s%n", &c, sval, &len); + + if (r != 2) { + dev_err(dev, "sscanf failed, aborting\n"); + r = -EINVAL; + goto err; + } + } else { + sval[0] = 0; + } + + switch (c) { + case 't': + if (strcmp(sval, "none") == 0) { + mgr = NULL; + } else { + mgr = find_manager_by_name(fbdev, sval); + + if (mgr == NULL) { + dev_err(dev, "no such manager\n"); + r = -EINVAL; + goto err; + } + + DBG("manager %s found\n", mgr->name); + } + + break; + + case 'x': + posx = val; + break; + + case 'y': + posy = val; + break; + + case 'w': + if (ovl->caps & OMAP_DSS_OVL_CAP_SCALE) + outw = val; + break; + + case 'h': + if (ovl->caps & OMAP_DSS_OVL_CAP_SCALE) + outh = val; + break; + + case 'e': + enabled = val; + break; + + default: + dev_err(dev, "unknown option %c\n", c); + r = -EINVAL; + goto err; + } + + idx += len + 1; + } + + r = ovl->setup_output(ovl, posx, posy, outw, outh); + + if (r) { + dev_err(dev, "setup overlay failed\n"); + goto err; + } + + if (mgr != ovl->manager) { + /* detach old manager */ + if (ovl->manager) { + r = ovl->unset_manager(ovl); + if (r) { + dev_err(dev, "detach failed\n"); + goto err; + } + } + + if (mgr) { + r = ovl->set_manager(ovl, mgr); + if (r) { + dev_err(dev, "Failed to attach overlay\n"); + goto err; + } + } + } + + r = ovl->enable(ovl, enabled); + + if (r) { + dev_err(dev, "enable overlay failed\n"); + goto err; + } + + if (mgr) { + r = mgr->apply(mgr); + if (r) { + dev_err(dev, "failed to apply dispc config\n"); + goto err; + } + } else { + ovl->enable(ovl, 0); + } + + if (mgr && mgr->display && mgr->display->update) + mgr->display->update(mgr->display, + 0, 0, + mgr->display->x_res, mgr->display->y_res); + + omapfb_unlock(fbdev); + return count; + +err: + omapfb_unlock(fbdev); + return r; +} + +static ssize_t show_managers(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + ssize_t l = 0, size = PAGE_SIZE; + int i; + + omapfb_lock(fbdev); + + for (i = 0; i < fbdev->num_managers; i++) { + struct omap_display *display; + struct omap_overlay_manager *mgr; + + mgr = fbdev->managers[i]; + display = mgr->display; + + l += snprintf(buf + l, size - l, "%s t:%s\n", + mgr->name, display ? display->name : "none"); + } + + omapfb_unlock(fbdev); + + return l; +} + +static ssize_t store_managers(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + int idx; + struct omap_overlay_manager *mgr; + struct omap_display *display; + char mgrname[10]; + char displayname[10]; + int r; + + idx = 0; + while (idx < count && buf[idx] != ' ') + ++idx; + + if (idx == count) + return -EINVAL; + + if (idx >= sizeof(mgrname)) + return -EINVAL; + + strncpy(mgrname, buf, idx); + mgrname[idx] = 0; + idx++; + + omapfb_lock(fbdev); + + mgr = find_manager_by_name(fbdev, mgrname); + + if (!mgr) { + dev_err(dev, "manager not found\n"); + r = -EINVAL; + goto err; + } + + r = sscanf(buf + idx, "t:%9s", displayname); + + if (r != 1) { + r = -EINVAL; + goto err; + } + + if (strcmp(displayname, "none") == 0) { + display = NULL; + } else { + display = find_display_by_name(fbdev, displayname); + + if (!display) { + dev_err(dev, "display not found\n"); + r = -EINVAL; + goto err; + } + } + + if (mgr->display) { + r = mgr->unset_display(mgr); + if (r) { + dev_err(dev, "failed to unset display\n"); + goto err; + } + } + + if (display) { + r = mgr->set_display(mgr, display); + if (r) { + dev_err(dev, "failed to set manager\n"); + goto err; + } + + r = mgr->apply(mgr); + if (r) { + dev_err(dev, "failed to apply dispc config\n"); + goto err; + } + } + + omapfb_unlock(fbdev); + return count; + +err: + omapfb_unlock(fbdev); + return r; +} + +static ssize_t show_displays(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + ssize_t l = 0, size = PAGE_SIZE; + int i; + + omapfb_lock(fbdev); + + for (i = 0; i < fbdev->num_displays; i++) { + struct omap_display *display; + enum omap_dss_update_mode mode = -1; + int te = 0; + + display = fbdev->displays[i]; + + if (display->get_update_mode) + mode = display->get_update_mode(display); + + if (display->get_te) + te = display->get_te(display); + + l += snprintf(buf + l, size - l, + "%s w:%d h:%d e:%d u:%d t:%d\n", + display->name, + display->x_res, + display->y_res, + display->state != OMAP_DSS_DISPLAY_DISABLED, + mode, te); + } + + omapfb_unlock(fbdev); + + return l; +} + +static ssize_t store_displays(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct platform_device *pdev = to_platform_device(dev); + struct omapfb2_device *fbdev = platform_get_drvdata(pdev); + int idx; + int enable, width, height; + enum omap_dss_update_mode mode; + struct omap_display *display = NULL; + int r; + char displayname[10]; + int te; + + idx = 0; + while (idx < count && buf[idx] != ' ') + ++idx; + + if (idx == count) + return -EINVAL; + + if (idx >= sizeof(displayname)) + return -EINVAL; + + strncpy(displayname, buf, idx); + displayname[idx] = 0; + idx++; + + omapfb_lock(fbdev); + + display = find_display_by_name(fbdev, displayname); + + if (!display) { + dev_err(dev, "display not found\n"); + r = -EINVAL; + goto err; + } + + width = display->x_res; + height = display->y_res; + enable = display->state != OMAP_DSS_DISPLAY_DISABLED; + if (display->get_update_mode) + mode = display->get_update_mode(display); + else + mode = 0; + + if (display->get_te) + te = display->get_te(display); + else + te = 0; + + while (idx < count) { + char c; + int val; + int len; + + r = sscanf(buf + idx, "%c:%d%n", &c, &val, &len); + + if (r != 2) { + dev_err(dev, "sscanf failed, aborting\n"); + r = -EINVAL; + goto err; + } + + switch (c) { + case 'w': + width = val; + break; + + case 'h': + height = val; + break; + + case 'e': + enable = val; + break; + + case 'u': + mode = val; + break; + + case 't': + te = val; + break; + + default: + dev_err(dev, "unknown option %c\n", c); + r = -EINVAL; + goto err; + } + + idx += len + 1; + } + + /* XXX: setmode */ + if (enable != (display->state != OMAP_DSS_DISPLAY_DISABLED)) { + if (enable) { + r = display->enable(display); + if (r) + dev_err(dev, "failed to enable display\n"); + } else { + display->disable(display); + } + } + + if (display->set_update_mode && display->get_update_mode) { + if (mode != display->get_update_mode(display)) + display->set_update_mode(display, mode); + } + + if (display->enable_te && display->get_te) { + if (te != display->get_te(display)) + display->enable_te(display, te); + } + + omapfb_unlock(fbdev); + return count; + +err: + omapfb_unlock(fbdev); + return r; +} + + +static DEVICE_ATTR(framebuffers, S_IRUGO | S_IWUSR, + show_framebuffers, store_framebuffers); +static DEVICE_ATTR(overlays, S_IRUGO | S_IWUSR, + show_overlays, store_overlays); +static DEVICE_ATTR(managers, S_IRUGO | S_IWUSR, + show_managers, store_managers); +static DEVICE_ATTR(displays, S_IRUGO | S_IWUSR, + show_displays, store_displays); + +static struct attribute *omapfb_attrs[] = { + &dev_attr_framebuffers.attr, + &dev_attr_overlays.attr, + &dev_attr_managers.attr, + &dev_attr_displays.attr, + NULL, +}; + +static struct attribute_group omapfb_attr_group = { + .attrs = omapfb_attrs, +}; + +void omapfb_create_sysfs(struct omapfb2_device *fbdev) +{ + int r; + + r = sysfs_create_group(&fbdev->dev->kobj, &omapfb_attr_group); + if (r) + dev_err(fbdev->dev, "failed to create sysfs clk file\n"); +} + +void omapfb_remove_sysfs(struct omapfb2_device *fbdev) +{ + sysfs_remove_group(&fbdev->dev->kobj, &omapfb_attr_group); +} + diff --git a/drivers/video/omap2/omapfb.h b/drivers/video/omap2/omapfb.h new file mode 100644 index 0000000..60352da --- /dev/null +++ b/drivers/video/omap2/omapfb.h @@ -0,0 +1,109 @@ +/* + * linux/drivers/video/omap2/omapfb.h + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * 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, see . + */ + +#ifndef __DRIVERS_VIDEO_OMAP2_OMAPFB_H__ +#define __DRIVERS_VIDEO_OMAP2_OMAPFB_H__ + +#ifdef CONFIG_FB_OMAP2_DEBUG +#define DEBUG +#endif + +#ifdef DEBUG +#define DBG(format, ...) printk(KERN_DEBUG "OMAPFB: " format, ## __VA_ARGS__) +#else +#define DBG(format, ...) +#endif + +#define FB2OFB(fb_info) ((struct omapfb_info *)(fb_info->par)) + +/* max number of overlays to which a framebuffer data can be direct */ +#define OMAPFB_MAX_OVL_PER_FB 3 + +/* appended to fb_info */ +struct omapfb_info { + int id; + struct omapfb_mem_region region; + atomic_t map_count; + int num_overlays; + struct omap_overlay *overlays[OMAPFB_MAX_OVL_PER_FB]; + struct omapfb2_device *fbdev; +}; + +struct omapfb2_device { + struct device *dev; + struct mutex mtx; + + u32 pseudo_palette[17]; + + int state; + + int num_fbs; + struct fb_info *fbs[10]; + + int num_displays; + struct omap_display *displays[10]; + int num_overlays; + struct omap_overlay *overlays[10]; + int num_managers; + struct omap_overlay_manager *managers[10]; +}; + +void set_fb_fix(struct fb_info *fbi); +int check_fb_var(struct fb_info *fbi, struct fb_var_screeninfo *var); +int omapfb_realloc_fbmem(struct omapfb2_device *fbdev, int fbnum, + unsigned long size); +int omapfb_apply_changes(struct fb_info *fbi, int init); +int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, + int posx, int posy, int outw, int outh); + +void omapfb_create_sysfs(struct omapfb2_device *fbdev); +void omapfb_remove_sysfs(struct omapfb2_device *fbdev); + +int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, unsigned long arg); + +/* find the display connected to this fb, if any */ +static inline struct omap_display *fb2display(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + int i; + + /* XXX: returns the display connected to first attached overlay */ + for (i = 0; i < ofbi->num_overlays; i++) { + if (ofbi->overlays[i]->manager) + return ofbi->overlays[i]->manager->display; + } + + return NULL; +} + +static inline void omapfb_lock(struct omapfb2_device *fbdev) +{ + mutex_lock(&fbdev->mtx); +} + +static inline void omapfb_unlock(struct omapfb2_device *fbdev) +{ + mutex_unlock(&fbdev->mtx); +} + + +#endif -- 1.5.6.3