diff options
Diffstat (limited to 'drivers/video/fbdev/omap2/omapfb/omapfb-main.c')
-rw-r--r-- | drivers/video/fbdev/omap2/omapfb/omapfb-main.c | 2636 |
1 files changed, 2636 insertions, 0 deletions
diff --git a/drivers/video/fbdev/omap2/omapfb/omapfb-main.c b/drivers/video/fbdev/omap2/omapfb/omapfb-main.c new file mode 100644 index 000000000..5ccddcfce --- /dev/null +++ b/drivers/video/fbdev/omap2/omapfb/omapfb-main.c @@ -0,0 +1,2636 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * linux/drivers/video/omap2/omapfb-main.c + * + * Copyright (C) 2008 Nokia Corporation + * Author: Tomi Valkeinen <tomi.valkeinen@nokia.com> + * + * Some code and ideas taken from drivers/video/omap/ driver + * by Imre Deak. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/fb.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/omapfb.h> + +#include <video/omapfb_dss.h> +#include <video/omapvrfb.h> + +#include "omapfb.h" + +#define MODULE_NAME "omapfb" + +#define OMAPFB_PLANE_XRES_MIN 8 +#define OMAPFB_PLANE_YRES_MIN 8 + +static char *def_mode; +static char *def_vram; +static bool def_vrfb; +static int def_rotate; +static bool def_mirror; +static bool auto_update; +static unsigned int auto_update_freq; +module_param(auto_update, bool, 0); +module_param(auto_update_freq, uint, 0644); + +#ifdef DEBUG +bool omapfb_debug; +module_param_named(debug, omapfb_debug, bool, 0644); +static bool omapfb_test_pattern; +module_param_named(test, omapfb_test_pattern, bool, 0644); +#endif + +static int omapfb_fb_init(struct omapfb2_device *fbdev, struct fb_info *fbi); +static int omapfb_get_recommended_bpp(struct omapfb2_device *fbdev, + struct omap_dss_device *dssdev); + +#ifdef DEBUG +static void draw_pixel(struct fb_info *fbi, int x, int y, unsigned color) +{ + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + void __iomem *addr = fbi->screen_base; + const unsigned bytespp = var->bits_per_pixel >> 3; + const unsigned line_len = fix->line_length / bytespp; + + int r = (color >> 16) & 0xff; + int g = (color >> 8) & 0xff; + int b = (color >> 0) & 0xff; + + if (var->bits_per_pixel == 16) { + u16 __iomem *p = (u16 __iomem *)addr; + p += y * line_len + x; + + r = r * 32 / 256; + g = g * 64 / 256; + b = b * 32 / 256; + + __raw_writew((r << 11) | (g << 5) | (b << 0), p); + } else if (var->bits_per_pixel == 24) { + u8 __iomem *p = (u8 __iomem *)addr; + p += (y * line_len + x) * 3; + + __raw_writeb(b, p + 0); + __raw_writeb(g, p + 1); + __raw_writeb(r, p + 2); + } else if (var->bits_per_pixel == 32) { + u32 __iomem *p = (u32 __iomem *)addr; + p += y * line_len + x; + __raw_writel(color, p); + } +} + +static void fill_fb(struct fb_info *fbi) +{ + struct fb_var_screeninfo *var = &fbi->var; + const short w = var->xres_virtual; + const short h = var->yres_virtual; + void __iomem *addr = fbi->screen_base; + int y, x; + + if (!addr) + return; + + DBG("fill_fb %dx%d, line_len %d bytes\n", w, h, fbi->fix.line_length); + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + if (x < 20 && y < 20) + draw_pixel(fbi, x, y, 0xffffff); + else if (x < 20 && (y > 20 && y < h - 20)) + draw_pixel(fbi, x, y, 0xff); + else if (y < 20 && (x > 20 && x < w - 20)) + draw_pixel(fbi, x, y, 0xff00); + else if (x > w - 20 && (y > 20 && y < h - 20)) + draw_pixel(fbi, x, y, 0xff0000); + else if (y > h - 20 && (x > 20 && x < w - 20)) + draw_pixel(fbi, x, y, 0xffff00); + else if (x == 20 || x == w - 20 || + y == 20 || y == h - 20) + draw_pixel(fbi, x, y, 0xffffff); + else if (x == y || w - x == h - y) + draw_pixel(fbi, x, y, 0xff00ff); + else if (w - x == y || x == h - y) + draw_pixel(fbi, x, y, 0x00ffff); + else if (x > 20 && y > 20 && x < w - 20 && y < h - 20) { + int t = x * 3 / w; + unsigned r = 0, g = 0, b = 0; + unsigned c; + if (var->bits_per_pixel == 16) { + if (t == 0) + b = (y % 32) * 256 / 32; + else if (t == 1) + g = (y % 64) * 256 / 64; + else if (t == 2) + r = (y % 32) * 256 / 32; + } else { + if (t == 0) + b = (y % 256); + else if (t == 1) + g = (y % 256); + else if (t == 2) + r = (y % 256); + } + c = (r << 16) | (g << 8) | (b << 0); + draw_pixel(fbi, x, y, c); + } else { + draw_pixel(fbi, x, y, 0); + } + } + } +} +#endif + +static unsigned omapfb_get_vrfb_offset(const struct omapfb_info *ofbi, int rot) +{ + const struct vrfb *vrfb = &ofbi->region->vrfb; + unsigned offset; + + switch (rot) { + case FB_ROTATE_UR: + offset = 0; + break; + case FB_ROTATE_CW: + offset = vrfb->yoffset; + break; + case FB_ROTATE_UD: + offset = vrfb->yoffset * OMAP_VRFB_LINE_LEN + vrfb->xoffset; + break; + case FB_ROTATE_CCW: + offset = vrfb->xoffset * OMAP_VRFB_LINE_LEN; + break; + default: + BUG(); + return 0; + } + + offset *= vrfb->bytespp; + + return offset; +} + +static u32 omapfb_get_region_rot_paddr(const struct omapfb_info *ofbi, int rot) +{ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + return ofbi->region->vrfb.paddr[rot] + + omapfb_get_vrfb_offset(ofbi, rot); + } else { + return ofbi->region->paddr; + } +} + +static u32 omapfb_get_region_paddr(const struct omapfb_info *ofbi) +{ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + return ofbi->region->vrfb.paddr[0]; + else + return ofbi->region->paddr; +} + +static void __iomem *omapfb_get_region_vaddr(const struct omapfb_info *ofbi) +{ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + return ofbi->region->vrfb.vaddr[0]; + else + return ofbi->region->vaddr; +} + +static struct omapfb_colormode omapfb_colormodes[] = { + { + .dssmode = OMAP_DSS_COLOR_UYVY, + .bits_per_pixel = 16, + .nonstd = OMAPFB_COLOR_YUV422, + }, { + .dssmode = OMAP_DSS_COLOR_YUV2, + .bits_per_pixel = 16, + .nonstd = OMAPFB_COLOR_YUY422, + }, { + .dssmode = OMAP_DSS_COLOR_ARGB16, + .bits_per_pixel = 16, + .red = { .length = 4, .offset = 8, .msb_right = 0 }, + .green = { .length = 4, .offset = 4, .msb_right = 0 }, + .blue = { .length = 4, .offset = 0, .msb_right = 0 }, + .transp = { .length = 4, .offset = 12, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGB16, + .bits_per_pixel = 16, + .red = { .length = 5, .offset = 11, .msb_right = 0 }, + .green = { .length = 6, .offset = 5, .msb_right = 0 }, + .blue = { .length = 5, .offset = 0, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGB24P, + .bits_per_pixel = 24, + .red = { .length = 8, .offset = 16, .msb_right = 0 }, + .green = { .length = 8, .offset = 8, .msb_right = 0 }, + .blue = { .length = 8, .offset = 0, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGB24U, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 16, .msb_right = 0 }, + .green = { .length = 8, .offset = 8, .msb_right = 0 }, + .blue = { .length = 8, .offset = 0, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_ARGB32, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 16, .msb_right = 0 }, + .green = { .length = 8, .offset = 8, .msb_right = 0 }, + .blue = { .length = 8, .offset = 0, .msb_right = 0 }, + .transp = { .length = 8, .offset = 24, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGBA32, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 24, .msb_right = 0 }, + .green = { .length = 8, .offset = 16, .msb_right = 0 }, + .blue = { .length = 8, .offset = 8, .msb_right = 0 }, + .transp = { .length = 8, .offset = 0, .msb_right = 0 }, + }, { + .dssmode = OMAP_DSS_COLOR_RGBX32, + .bits_per_pixel = 32, + .red = { .length = 8, .offset = 24, .msb_right = 0 }, + .green = { .length = 8, .offset = 16, .msb_right = 0 }, + .blue = { .length = 8, .offset = 8, .msb_right = 0 }, + .transp = { .length = 0, .offset = 0, .msb_right = 0 }, + }, +}; + +static bool cmp_component(struct fb_bitfield *f1, struct fb_bitfield *f2) +{ + return f1->length == f2->length && + f1->offset == f2->offset && + f1->msb_right == f2->msb_right; +} + +static bool cmp_var_to_colormode(struct fb_var_screeninfo *var, + struct omapfb_colormode *color) +{ + if (var->bits_per_pixel == 0 || + var->red.length == 0 || + var->blue.length == 0 || + var->green.length == 0) + return false; + + return var->bits_per_pixel == color->bits_per_pixel && + cmp_component(&var->red, &color->red) && + cmp_component(&var->green, &color->green) && + cmp_component(&var->blue, &color->blue) && + cmp_component(&var->transp, &color->transp); +} + +static void assign_colormode_to_var(struct fb_var_screeninfo *var, + struct omapfb_colormode *color) +{ + var->bits_per_pixel = color->bits_per_pixel; + var->nonstd = color->nonstd; + var->red = color->red; + var->green = color->green; + var->blue = color->blue; + var->transp = color->transp; +} + +static int fb_mode_to_dss_mode(struct fb_var_screeninfo *var, + enum omap_color_mode *mode) +{ + enum omap_color_mode dssmode; + int i; + + /* first match with nonstd field */ + if (var->nonstd) { + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *m = &omapfb_colormodes[i]; + if (var->nonstd == m->nonstd) { + assign_colormode_to_var(var, m); + *mode = m->dssmode; + return 0; + } + } + + return -EINVAL; + } + + /* then try exact match of bpp and colors */ + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *m = &omapfb_colormodes[i]; + if (cmp_var_to_colormode(var, m)) { + assign_colormode_to_var(var, m); + *mode = m->dssmode; + return 0; + } + } + + /* match with bpp if user has not filled color fields + * properly */ + switch (var->bits_per_pixel) { + case 1: + dssmode = OMAP_DSS_COLOR_CLUT1; + break; + case 2: + dssmode = OMAP_DSS_COLOR_CLUT2; + break; + case 4: + dssmode = OMAP_DSS_COLOR_CLUT4; + break; + case 8: + dssmode = OMAP_DSS_COLOR_CLUT8; + break; + case 12: + dssmode = OMAP_DSS_COLOR_RGB12U; + break; + case 16: + dssmode = OMAP_DSS_COLOR_RGB16; + break; + case 24: + dssmode = OMAP_DSS_COLOR_RGB24P; + break; + case 32: + dssmode = OMAP_DSS_COLOR_RGB24U; + break; + default: + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *m = &omapfb_colormodes[i]; + if (dssmode == m->dssmode) { + assign_colormode_to_var(var, m); + *mode = m->dssmode; + return 0; + } + } + + return -EINVAL; +} + +static int check_fb_res_bounds(struct fb_var_screeninfo *var) +{ + int xres_min = OMAPFB_PLANE_XRES_MIN; + int xres_max = 2048; + int yres_min = OMAPFB_PLANE_YRES_MIN; + int yres_max = 2048; + + /* XXX: some applications seem to set virtual res to 0. */ + if (var->xres_virtual == 0) + var->xres_virtual = var->xres; + + if (var->yres_virtual == 0) + var->yres_virtual = var->yres; + + if (var->xres_virtual < xres_min || var->yres_virtual < yres_min) + return -EINVAL; + + if (var->xres < xres_min) + var->xres = xres_min; + if (var->yres < yres_min) + var->yres = yres_min; + if (var->xres > xres_max) + var->xres = xres_max; + if (var->yres > yres_max) + var->yres = yres_max; + + if (var->xres > var->xres_virtual) + var->xres = var->xres_virtual; + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; + + return 0; +} + +static void shrink_height(unsigned long max_frame_size, + struct fb_var_screeninfo *var) +{ + DBG("can't fit FB into memory, reducing y\n"); + var->yres_virtual = max_frame_size / + (var->xres_virtual * var->bits_per_pixel >> 3); + + if (var->yres_virtual < OMAPFB_PLANE_YRES_MIN) + var->yres_virtual = OMAPFB_PLANE_YRES_MIN; + + if (var->yres > var->yres_virtual) + var->yres = var->yres_virtual; +} + +static void shrink_width(unsigned long max_frame_size, + struct fb_var_screeninfo *var) +{ + DBG("can't fit FB into memory, reducing x\n"); + var->xres_virtual = max_frame_size / var->yres_virtual / + (var->bits_per_pixel >> 3); + + if (var->xres_virtual < OMAPFB_PLANE_XRES_MIN) + var->xres_virtual = OMAPFB_PLANE_XRES_MIN; + + if (var->xres > var->xres_virtual) + var->xres = var->xres_virtual; +} + +static int check_vrfb_fb_size(unsigned long region_size, + const struct fb_var_screeninfo *var) +{ + unsigned long min_phys_size = omap_vrfb_min_phys_size(var->xres_virtual, + var->yres_virtual, var->bits_per_pixel >> 3); + + return min_phys_size > region_size ? -EINVAL : 0; +} + +static int check_fb_size(const struct omapfb_info *ofbi, + struct fb_var_screeninfo *var) +{ + unsigned long max_frame_size = ofbi->region->size; + int bytespp = var->bits_per_pixel >> 3; + unsigned long line_size = var->xres_virtual * bytespp; + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + /* One needs to check for both VRFB and OMAPFB limitations. */ + if (check_vrfb_fb_size(max_frame_size, var)) + shrink_height(omap_vrfb_max_height( + max_frame_size, var->xres_virtual, bytespp) * + line_size, var); + + if (check_vrfb_fb_size(max_frame_size, var)) { + DBG("cannot fit FB to memory\n"); + return -EINVAL; + } + + return 0; + } + + DBG("max frame size %lu, line size %lu\n", max_frame_size, line_size); + + if (line_size * var->yres_virtual > max_frame_size) + shrink_height(max_frame_size, var); + + if (line_size * var->yres_virtual > max_frame_size) { + shrink_width(max_frame_size, var); + line_size = var->xres_virtual * bytespp; + } + + if (line_size * var->yres_virtual > max_frame_size) { + DBG("cannot fit FB to memory\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Consider if VRFB assisted rotation is in use and if the virtual space for + * the zero degree view needs to be mapped. The need for mapping also acts as + * the trigger for setting up the hardware on the context in question. This + * ensures that one does not attempt to access the virtual view before the + * hardware is serving the address translations. + */ +static int setup_vrfb_rotation(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_mem_region *rg = ofbi->region; + struct vrfb *vrfb = &rg->vrfb; + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + unsigned bytespp; + bool yuv_mode; + enum omap_color_mode mode; + int r; + bool reconf; + + if (!rg->size || ofbi->rotation_type != OMAP_DSS_ROT_VRFB) + return 0; + + DBG("setup_vrfb_rotation\n"); + + r = fb_mode_to_dss_mode(var, &mode); + if (r) + return r; + + bytespp = var->bits_per_pixel >> 3; + + yuv_mode = mode == OMAP_DSS_COLOR_YUV2 || mode == OMAP_DSS_COLOR_UYVY; + + /* We need to reconfigure VRFB if the resolution changes, if yuv mode + * is enabled/disabled, or if bytes per pixel changes */ + + /* XXX we shouldn't allow this when framebuffer is mmapped */ + + reconf = false; + + if (yuv_mode != vrfb->yuv_mode) + reconf = true; + else if (bytespp != vrfb->bytespp) + reconf = true; + else if (vrfb->xres != var->xres_virtual || + vrfb->yres != var->yres_virtual) + reconf = true; + + if (vrfb->vaddr[0] && reconf) { + fbi->screen_base = NULL; + fix->smem_start = 0; + fix->smem_len = 0; + iounmap(vrfb->vaddr[0]); + vrfb->vaddr[0] = NULL; + DBG("setup_vrfb_rotation: reset fb\n"); + } + + if (vrfb->vaddr[0]) + return 0; + + omap_vrfb_setup(&rg->vrfb, rg->paddr, + var->xres_virtual, + var->yres_virtual, + bytespp, yuv_mode); + + /* Now one can ioremap the 0 angle view */ + r = omap_vrfb_map_angle(vrfb, var->yres_virtual, 0); + if (r) + return r; + + /* used by open/write in fbmem.c */ + fbi->screen_base = ofbi->region->vrfb.vaddr[0]; + + fix->smem_start = ofbi->region->vrfb.paddr[0]; + + switch (var->nonstd) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 2; + break; + default: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 3; + break; + } + + fix->smem_len = var->yres_virtual * fix->line_length; + + return 0; +} + +int dss_mode_to_fb_mode(enum omap_color_mode dssmode, + struct fb_var_screeninfo *var) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(omapfb_colormodes); ++i) { + struct omapfb_colormode *mode = &omapfb_colormodes[i]; + if (dssmode == mode->dssmode) { + assign_colormode_to_var(var, mode); + return 0; + } + } + return -ENOENT; +} + +void set_fb_fix(struct fb_info *fbi) +{ + struct fb_fix_screeninfo *fix = &fbi->fix; + struct fb_var_screeninfo *var = &fbi->var; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_mem_region *rg = ofbi->region; + + DBG("set_fb_fix\n"); + + /* used by open/write in fbmem.c */ + fbi->screen_base = (char __iomem *)omapfb_get_region_vaddr(ofbi); + + /* used by mmap in fbmem.c */ + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + switch (var->nonstd) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 2; + break; + default: + fix->line_length = + (OMAP_VRFB_LINE_LEN * var->bits_per_pixel) >> 3; + break; + } + + fix->smem_len = var->yres_virtual * fix->line_length; + } else { + fix->line_length = + (var->xres_virtual * var->bits_per_pixel) >> 3; + fix->smem_len = rg->size; + } + + fix->smem_start = omapfb_get_region_paddr(ofbi); + + 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->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_dss_device *display = fb2display(fbi); + enum omap_color_mode mode = 0; + int i; + int r; + + DBG("check_fb_var %d\n", ofbi->id); + + WARN_ON(!atomic_read(&ofbi->region->lock_count)); + + r = fb_mode_to_dss_mode(var, &mode); + if (r) { + DBG("cannot convert var to omap dss mode\n"); + return r; + } + + for (i = 0; i < ofbi->num_overlays; ++i) { + if ((ofbi->overlays[i]->supported_modes & mode) == 0) { + DBG("invalid mode\n"); + return -EINVAL; + } + } + + if (var->rotate > 3) + return -EINVAL; + + if (check_fb_res_bounds(var)) + return -EINVAL; + + /* When no memory is allocated ignore the size check */ + if (ofbi->region->size != 0 && check_fb_size(ofbi, var)) + 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; + + DBG("xres = %d, yres = %d, vxres = %d, vyres = %d\n", + var->xres, var->yres, + var->xres_virtual, var->yres_virtual); + + if (display && display->driver->get_dimensions) { + u32 w, h; + display->driver->get_dimensions(display, &w, &h); + var->width = DIV_ROUND_CLOSEST(w, 1000); + var->height = DIV_ROUND_CLOSEST(h, 1000); + } else { + var->height = -1; + var->width = -1; + } + + var->grayscale = 0; + + if (display && display->driver->get_timings) { + struct omap_video_timings timings; + display->driver->get_timings(display, &timings); + + /* pixclock in ps, the rest in pixclock */ + var->pixclock = timings.pixelclock != 0 ? + KHZ2PICOS(timings.pixelclock / 1000) : + 0; + var->left_margin = timings.hbp; + var->right_margin = timings.hfp; + var->upper_margin = timings.vbp; + var->lower_margin = timings.vfp; + var->hsync_len = timings.hsw; + var->vsync_len = timings.vsw; + var->sync |= timings.hsync_level == OMAPDSS_SIG_ACTIVE_HIGH ? + FB_SYNC_HOR_HIGH_ACT : 0; + var->sync |= timings.vsync_level == OMAPDSS_SIG_ACTIVE_HIGH ? + FB_SYNC_VERT_HIGH_ACT : 0; + var->vmode = timings.interlace ? + FB_VMODE_INTERLACED : FB_VMODE_NONINTERLACED; + } 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; + var->sync = 0; + var->vmode = FB_VMODE_NONINTERLACED; + } + + 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) +{ + return 0; +} + +static unsigned calc_rotation_offset_dma(const struct fb_var_screeninfo *var, + const struct fb_fix_screeninfo *fix, int rotation) +{ + unsigned offset; + + offset = var->yoffset * fix->line_length + + var->xoffset * (var->bits_per_pixel >> 3); + + return offset; +} + +static unsigned calc_rotation_offset_vrfb(const struct fb_var_screeninfo *var, + const struct fb_fix_screeninfo *fix, int rotation) +{ + unsigned offset; + + if (rotation == FB_ROTATE_UD) + offset = (var->yres_virtual - var->yres) * + fix->line_length; + else if (rotation == FB_ROTATE_CW) + offset = (var->yres_virtual - var->yres) * + (var->bits_per_pixel >> 3); + else + offset = 0; + + if (rotation == FB_ROTATE_UR) + offset += var->yoffset * fix->line_length + + var->xoffset * (var->bits_per_pixel >> 3); + else if (rotation == FB_ROTATE_UD) + offset -= var->yoffset * fix->line_length + + var->xoffset * (var->bits_per_pixel >> 3); + else if (rotation == FB_ROTATE_CW) + offset -= var->xoffset * fix->line_length + + var->yoffset * (var->bits_per_pixel >> 3); + else if (rotation == FB_ROTATE_CCW) + offset += var->xoffset * fix->line_length + + var->yoffset * (var->bits_per_pixel >> 3); + + return offset; +} + +static void omapfb_calc_addr(const struct omapfb_info *ofbi, + const struct fb_var_screeninfo *var, + const struct fb_fix_screeninfo *fix, + int rotation, u32 *paddr) +{ + u32 data_start_p; + int offset; + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + data_start_p = omapfb_get_region_rot_paddr(ofbi, rotation); + else + data_start_p = omapfb_get_region_paddr(ofbi); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + offset = calc_rotation_offset_vrfb(var, fix, rotation); + else + offset = calc_rotation_offset_dma(var, fix, rotation); + + data_start_p += offset; + + if (offset) + DBG("offset %d, %d = %d\n", + var->xoffset, var->yoffset, offset); + + DBG("paddr %x\n", data_start_p); + + *paddr = data_start_p; +} + +/* setup overlay according to the fb */ +int omapfb_setup_overlay(struct fb_info *fbi, struct omap_overlay *ovl, + u16 posx, u16 posy, u16 outw, u16 outh) +{ + int r = 0; + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo *var = &fbi->var; + struct fb_fix_screeninfo *fix = &fbi->fix; + enum omap_color_mode mode = 0; + u32 data_start_p = 0; + struct omap_overlay_info info; + int xres, yres; + int screen_width; + int mirror; + int rotation = var->rotate; + int i; + + WARN_ON(!atomic_read(&ofbi->region->lock_count)); + + for (i = 0; i < ofbi->num_overlays; i++) { + if (ovl != ofbi->overlays[i]) + continue; + + rotation = (rotation + ofbi->rotation[i]) % 4; + break; + } + + DBG("setup_overlay %d, posx %d, posy %d, outw %d, outh %d\n", ofbi->id, + posx, posy, outw, outh); + + if (rotation == FB_ROTATE_CW || rotation == FB_ROTATE_CCW) { + xres = var->yres; + yres = var->xres; + } else { + xres = var->xres; + yres = var->yres; + } + + if (ofbi->region->size) + omapfb_calc_addr(ofbi, var, fix, rotation, &data_start_p); + + r = fb_mode_to_dss_mode(var, &mode); + if (r) { + DBG("fb_mode_to_dss_mode failed"); + goto err; + } + + switch (var->nonstd) { + case OMAPFB_COLOR_YUV422: + case OMAPFB_COLOR_YUY422: + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + screen_width = fix->line_length + / (var->bits_per_pixel >> 2); + break; + } + fallthrough; + default: + screen_width = fix->line_length / (var->bits_per_pixel >> 3); + break; + } + + ovl->get_overlay_info(ovl, &info); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + mirror = 0; + else + mirror = ofbi->mirror; + + info.paddr = data_start_p; + info.screen_width = screen_width; + info.width = xres; + info.height = yres; + info.color_mode = mode; + info.rotation_type = ofbi->rotation_type; + info.rotation = rotation; + info.mirror = mirror; + + info.pos_x = posx; + info.pos_y = posy; + info.out_width = outw; + info.out_height = outh; + + r = ovl->set_overlay_info(ovl, &info); + if (r) { + DBG("ovl->setup_overlay_info failed\n"); + 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_overlay *ovl; + u16 posx, posy; + u16 outw, outh; + int i; + +#ifdef DEBUG + if (omapfb_test_pattern) + fill_fb(fbi); +#endif + + WARN_ON(!atomic_read(&ofbi->region->lock_count)); + + 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 */ + omapfb_overlay_enable(ovl, 0); + if (!init && ovl->manager) + ovl->manager->apply(ovl->manager); + continue; + } + + if (init || (ovl->caps & OMAP_DSS_OVL_CAP_SCALE) == 0) { + int rotation = (var->rotate + ofbi->rotation[i]) % 4; + if (rotation == FB_ROTATE_CW || + rotation == FB_ROTATE_CCW) { + outw = var->yres; + outh = var->xres; + } else { + outw = var->xres; + outh = var->yres; + } + } else { + struct omap_overlay_info info; + ovl->get_overlay_info(ovl, &info); + outw = info.out_width; + outh = info.out_height; + } + + if (init) { + posx = 0; + posy = 0; + } else { + struct omap_overlay_info info; + ovl->get_overlay_info(ovl, &info); + posx = info.pos_x; + posy = info.pos_y; + } + + r = omapfb_setup_overlay(fbi, ovl, posx, posy, outw, outh); + if (r) + goto err; + + 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) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + int r; + + DBG("check_var(%d)\n", FB2OFB(fbi)->id); + + omapfb_get_mem_region(ofbi->region); + + r = check_fb_var(fbi, var); + + omapfb_put_mem_region(ofbi->region); + + return r; +} + +/* set the video mode according to info->var */ +static int omapfb_set_par(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + int r; + + DBG("set_par(%d)\n", FB2OFB(fbi)->id); + + omapfb_get_mem_region(ofbi->region); + + set_fb_fix(fbi); + + r = setup_vrfb_rotation(fbi); + if (r) + goto out; + + r = omapfb_apply_changes(fbi, 0); + + out: + omapfb_put_mem_region(ofbi->region); + + return r; +} + +static int omapfb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct fb_var_screeninfo new_var; + int r; + + DBG("pan_display(%d)\n", FB2OFB(fbi)->id); + + if (var->xoffset == fbi->var.xoffset && + var->yoffset == fbi->var.yoffset) + return 0; + + new_var = fbi->var; + new_var.xoffset = var->xoffset; + new_var.yoffset = var->yoffset; + + fbi->var = new_var; + + omapfb_get_mem_region(ofbi->region); + + r = omapfb_apply_changes(fbi, 0); + + omapfb_put_mem_region(ofbi->region); + + return r; +} + +static void mmap_user_open(struct vm_area_struct *vma) +{ + struct omapfb2_mem_region *rg = vma->vm_private_data; + + omapfb_get_mem_region(rg); + atomic_inc(&rg->map_count); + omapfb_put_mem_region(rg); +} + +static void mmap_user_close(struct vm_area_struct *vma) +{ + struct omapfb2_mem_region *rg = vma->vm_private_data; + + omapfb_get_mem_region(rg); + atomic_dec(&rg->map_count); + omapfb_put_mem_region(rg); +} + +static const 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 fb_fix_screeninfo *fix = &fbi->fix; + struct omapfb2_mem_region *rg; + unsigned long start; + u32 len; + int r; + + rg = omapfb_get_mem_region(ofbi->region); + + start = omapfb_get_region_paddr(ofbi); + len = fix->smem_len; + + DBG("user mmap region start %lx, len %d, off %lx\n", start, len, + vma->vm_pgoff << PAGE_SHIFT); + + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + vma->vm_ops = &mmap_user_ops; + vma->vm_private_data = rg; + + r = vm_iomap_memory(vma, start, len); + if (r) + goto error; + + /* vm_ops.open won't be called for mmap itself. */ + atomic_inc(&rg->map_count); + + omapfb_put_mem_region(rg); + + return 0; + +error: + omapfb_put_mem_region(ofbi->region); + + return r; +} + +/* 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); + */ + r = -EINVAL; + break; + case OMAPFB_COLOR_RGB565: + case OMAPFB_COLOR_RGB444: + case OMAPFB_COLOR_RGB24P: + case OMAPFB_COLOR_RGB24U: + if (regno < 16) { + u32 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_dss_device *display = fb2display(fbi); + struct omapfb_display_data *d; + int r = 0; + + if (!display) + return -EINVAL; + + omapfb_lock(fbdev); + + d = get_display_data(fbdev, display); + + switch (blank) { + case FB_BLANK_UNBLANK: + if (display->state == OMAP_DSS_DISPLAY_ACTIVE) + goto exit; + + r = display->driver->enable(display); + + if ((display->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) && + d->update_mode == OMAPFB_AUTO_UPDATE && + !d->auto_update_work_enabled) + omapfb_start_auto_update(fbdev, display); + + break; + + case FB_BLANK_NORMAL: + /* FB_BLANK_NORMAL could be implemented. + * Needs DSS additions. */ + case FB_BLANK_VSYNC_SUSPEND: + case FB_BLANK_HSYNC_SUSPEND: + case FB_BLANK_POWERDOWN: + if (display->state != OMAP_DSS_DISPLAY_ACTIVE) + goto exit; + + if (d->auto_update_work_enabled) + omapfb_stop_auto_update(fbdev, display); + + display->driver->disable(display); + + break; + + default: + r = -EINVAL; + } + +exit: + omapfb_unlock(fbdev); + + return r; +} + +#if 0 +/* XXX fb_read and fb_write are needed for VRFB */ +ssize_t omapfb_write(struct fb_info *info, const char __user *buf, + size_t count, loff_t *ppos) +{ + DBG("omapfb_write %d, %lu\n", count, (unsigned long)*ppos); + /* XXX needed for VRFB */ + return count; +} +#endif + +static const 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_pan_display = omapfb_pan_display, + .fb_mmap = omapfb_mmap, + .fb_setcolreg = omapfb_setcolreg, + .fb_setcmap = omapfb_setcmap, + /*.fb_write = omapfb_write,*/ +}; + +static void omapfb_free_fbmem(struct fb_info *fbi) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb2_mem_region *rg; + + rg = ofbi->region; + + if (rg->token == NULL) + return; + + WARN_ON(atomic_read(&rg->map_count)); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + /* unmap the 0 angle rotation */ + if (rg->vrfb.vaddr[0]) { + iounmap(rg->vrfb.vaddr[0]); + rg->vrfb.vaddr[0] = NULL; + } + + omap_vrfb_release_ctx(&rg->vrfb); + } + + dma_free_attrs(fbdev->dev, rg->size, rg->token, rg->dma_handle, + rg->attrs); + + rg->token = NULL; + rg->vaddr = NULL; + rg->paddr = 0; + rg->alloc = 0; + rg->size = 0; +} + +static void clear_fb_info(struct fb_info *fbi) +{ + memset(&fbi->var, 0, sizeof(fbi->var)); + memset(&fbi->fix, 0, sizeof(fbi->fix)); + strscpy(fbi->fix.id, MODULE_NAME, sizeof(fbi->fix.id)); +} + +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++) { + struct fb_info *fbi = fbdev->fbs[i]; + omapfb_free_fbmem(fbi); + clear_fb_info(fbi); + } + + return 0; +} + +static int omapfb_alloc_fbmem(struct fb_info *fbi, unsigned long size, + unsigned long paddr) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb2_mem_region *rg; + void *token; + unsigned long attrs; + dma_addr_t dma_handle; + int r; + + rg = ofbi->region; + + rg->paddr = 0; + rg->vaddr = NULL; + memset(&rg->vrfb, 0, sizeof rg->vrfb); + rg->size = 0; + rg->type = 0; + rg->alloc = false; + rg->map = false; + + size = PAGE_ALIGN(size); + + attrs = DMA_ATTR_WRITE_COMBINE; + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) + attrs |= DMA_ATTR_NO_KERNEL_MAPPING; + + DBG("allocating %lu bytes for fb %d\n", size, ofbi->id); + + token = dma_alloc_attrs(fbdev->dev, size, &dma_handle, + GFP_KERNEL, attrs); + + if (token == NULL) { + dev_err(fbdev->dev, "failed to allocate framebuffer\n"); + return -ENOMEM; + } + + DBG("allocated VRAM paddr %lx, vaddr %p\n", + (unsigned long)dma_handle, token); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + r = omap_vrfb_request_ctx(&rg->vrfb); + if (r) { + dma_free_attrs(fbdev->dev, size, token, dma_handle, + attrs); + dev_err(fbdev->dev, "vrfb create ctx failed\n"); + return r; + } + } + + rg->attrs = attrs; + rg->token = token; + rg->dma_handle = dma_handle; + + rg->paddr = (unsigned long)dma_handle; + rg->vaddr = (void __iomem *)token; + rg->size = size; + rg->alloc = 1; + + return 0; +} + +/* allocate fbmem using display resolution as reference */ +static int omapfb_alloc_fbmem_display(struct fb_info *fbi, unsigned long size, + unsigned long paddr) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omap_dss_device *display; + int bytespp; + + display = fb2display(fbi); + + if (!display) + return 0; + + switch (omapfb_get_recommended_bpp(fbdev, display)) { + case 16: + bytespp = 2; + break; + case 24: + bytespp = 4; + break; + default: + bytespp = 4; + break; + } + + if (!size) { + u16 w, h; + + display->driver->get_resolution(display, &w, &h); + + if (ofbi->rotation_type == OMAP_DSS_ROT_VRFB) { + size = max(omap_vrfb_min_phys_size(w, h, bytespp), + omap_vrfb_min_phys_size(h, w, bytespp)); + + DBG("adjusting fb mem size for VRFB, %u -> %lu\n", + w * h * bytespp, size); + } else { + size = w * h * bytespp; + } + } + + if (!size) + return 0; + + return omapfb_alloc_fbmem(fbi, size, paddr); +} + +static int omapfb_parse_vram_param(const char *param, int max_entries, + unsigned long *sizes, unsigned long *paddrs) +{ + unsigned int fbnum; + unsigned long size; + unsigned long paddr = 0; + char *p, *start; + + start = (char *)param; + + while (1) { + p = start; + + fbnum = simple_strtoul(p, &p, 10); + + if (p == start) + return -EINVAL; + + if (*p != ':') + return -EINVAL; + + if (fbnum >= max_entries) + return -EINVAL; + + size = memparse(p + 1, &p); + + if (!size) + return -EINVAL; + + paddr = 0; + + if (*p == '@') { + paddr = simple_strtoul(p + 1, &p, 16); + + if (!paddr) + return -EINVAL; + + } + + WARN_ONCE(paddr, + "reserving memory at predefined address not supported\n"); + + paddrs[fbnum] = paddr; + sizes[fbnum] = size; + + if (*p == 0) + break; + + if (*p != ',') + return -EINVAL; + + ++p; + + start = p; + } + + return 0; +} + +static int omapfb_allocate_all_fbs(struct omapfb2_device *fbdev) +{ + int i, r; + unsigned long vram_sizes[10]; + unsigned long vram_paddrs[10]; + + memset(&vram_sizes, 0, sizeof(vram_sizes)); + memset(&vram_paddrs, 0, sizeof(vram_paddrs)); + + if (def_vram && omapfb_parse_vram_param(def_vram, 10, + vram_sizes, vram_paddrs)) { + dev_err(fbdev->dev, "failed to parse vram parameter\n"); + + memset(&vram_sizes, 0, sizeof(vram_sizes)); + memset(&vram_paddrs, 0, sizeof(vram_paddrs)); + } + + for (i = 0; i < fbdev->num_fbs; i++) { + /* allocate memory automatically only for fb0, or if + * excplicitly defined with vram or plat data option */ + if (i == 0 || vram_sizes[i] != 0) { + r = omapfb_alloc_fbmem_display(fbdev->fbs[i], + vram_sizes[i], vram_paddrs[i]); + + if (r) + return r; + } + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[i]); + struct omapfb2_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; +} + +static void omapfb_clear_fb(struct fb_info *fbi) +{ + const struct fb_fillrect rect = { + .dx = 0, + .dy = 0, + .width = fbi->var.xres_virtual, + .height = fbi->var.yres_virtual, + .color = 0, + .rop = ROP_COPY, + }; + + cfb_fillrect(fbi, &rect); +} + +int omapfb_realloc_fbmem(struct fb_info *fbi, unsigned long size, int type) +{ + struct omapfb_info *ofbi = FB2OFB(fbi); + struct omapfb2_device *fbdev = ofbi->fbdev; + struct omapfb2_mem_region *rg = ofbi->region; + unsigned long old_size = rg->size; + unsigned long old_paddr = rg->paddr; + int old_type = rg->type; + int r; + + if (type != OMAPFB_MEMTYPE_SDRAM) + return -EINVAL; + + size = PAGE_ALIGN(size); + + if (old_size == size && old_type == type) + return 0; + + omapfb_free_fbmem(fbi); + + if (size == 0) { + clear_fb_info(fbi); + return 0; + } + + r = omapfb_alloc_fbmem(fbi, size, 0); + + if (r) { + if (old_size) + omapfb_alloc_fbmem(fbi, old_size, old_paddr); + + if (rg->size == 0) + clear_fb_info(fbi); + + return r; + } + + if (old_size == size) + return 0; + + if (old_size == 0) { + DBG("initializing fb %d\n", ofbi->id); + r = omapfb_fb_init(fbdev, fbi); + if (r) { + DBG("omapfb_fb_init failed\n"); + goto err; + } + r = omapfb_apply_changes(fbi, 1); + if (r) { + DBG("omapfb_apply_changes failed\n"); + goto err; + } + } else { + struct fb_var_screeninfo new_var; + memcpy(&new_var, &fbi->var, sizeof(new_var)); + r = check_fb_var(fbi, &new_var); + if (r) + goto err; + memcpy(&fbi->var, &new_var, sizeof(fbi->var)); + set_fb_fix(fbi); + r = setup_vrfb_rotation(fbi); + if (r) + goto err; + } + + omapfb_clear_fb(fbi); + + return 0; +err: + omapfb_free_fbmem(fbi); + clear_fb_info(fbi); + return r; +} + +static void omapfb_auto_update_work(struct work_struct *work) +{ + struct omap_dss_device *dssdev; + struct omap_dss_driver *dssdrv; + struct omapfb_display_data *d; + u16 w, h; + unsigned int freq; + struct omapfb2_device *fbdev; + + d = container_of(work, struct omapfb_display_data, + auto_update_work.work); + + dssdev = d->dssdev; + dssdrv = dssdev->driver; + fbdev = d->fbdev; + + if (!dssdrv || !dssdrv->update) + return; + + if (dssdrv->sync) + dssdrv->sync(dssdev); + + dssdrv->get_resolution(dssdev, &w, &h); + dssdrv->update(dssdev, 0, 0, w, h); + + freq = auto_update_freq; + if (freq == 0) + freq = 20; + queue_delayed_work(fbdev->auto_update_wq, + &d->auto_update_work, HZ / freq); +} + +void omapfb_start_auto_update(struct omapfb2_device *fbdev, + struct omap_dss_device *display) +{ + struct omapfb_display_data *d; + + if (fbdev->auto_update_wq == NULL) { + struct workqueue_struct *wq; + + wq = create_singlethread_workqueue("omapfb_auto_update"); + + if (wq == NULL) { + dev_err(fbdev->dev, "Failed to create workqueue for " + "auto-update\n"); + return; + } + + fbdev->auto_update_wq = wq; + } + + d = get_display_data(fbdev, display); + + INIT_DELAYED_WORK(&d->auto_update_work, omapfb_auto_update_work); + + d->auto_update_work_enabled = true; + + omapfb_auto_update_work(&d->auto_update_work.work); +} + +void omapfb_stop_auto_update(struct omapfb2_device *fbdev, + struct omap_dss_device *display) +{ + struct omapfb_display_data *d; + + d = get_display_data(fbdev, display); + + cancel_delayed_work_sync(&d->auto_update_work); + + d->auto_update_work_enabled = false; +} + +/* initialize fb_info, var, fix to something sane based on the display */ +static int omapfb_fb_init(struct omapfb2_device *fbdev, struct fb_info *fbi) +{ + struct fb_var_screeninfo *var = &fbi->var; + struct omap_dss_device *display = fb2display(fbi); + struct omapfb_info *ofbi = FB2OFB(fbi); + int r = 0; + + fbi->fbops = &omapfb_ops; + fbi->flags = FBINFO_FLAG_DEFAULT; + fbi->pseudo_palette = fbdev->pseudo_palette; + + if (ofbi->region->size == 0) { + clear_fb_info(fbi); + return 0; + } + + var->nonstd = 0; + var->bits_per_pixel = 0; + + var->rotate = def_rotate; + + if (display) { + u16 w, h; + int rotation = (var->rotate + ofbi->rotation[0]) % 4; + + display->driver->get_resolution(display, &w, &h); + + if (rotation == FB_ROTATE_CW || + rotation == FB_ROTATE_CCW) { + var->xres = h; + var->yres = w; + } else { + var->xres = w; + var->yres = h; + } + + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + + if (!var->bits_per_pixel) { + switch (omapfb_get_recommended_bpp(fbdev, display)) { + case 16: + 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; + } + } + } else { + /* if there's no display, let's just guess some basic values */ + var->xres = 320; + var->yres = 240; + var->xres_virtual = var->xres; + var->yres_virtual = var->yres; + if (!var->bits_per_pixel) + var->bits_per_pixel = 16; + } + + r = check_fb_var(fbi, var); + if (r) + goto err; + + set_fb_fix(fbi); + r = setup_vrfb_rotation(fbi); + if (r) + goto err; + + r = fb_alloc_cmap(&fbi->cmap, 256, 0); + if (r) + dev_err(fbdev->dev, "unable to allocate color map memory\n"); + +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_overlays; i++) { + struct omap_overlay *ovl = fbdev->overlays[i]; + + ovl->disable(ovl); + + if (ovl->manager) + ovl->unset_manager(ovl); + } + + 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++) { + struct omap_dss_device *dssdev = fbdev->displays[i].dssdev; + + if (fbdev->displays[i].auto_update_work_enabled) + omapfb_stop_auto_update(fbdev, dssdev); + + if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) + dssdev->driver->disable(dssdev); + + dssdev->driver->disconnect(dssdev); + + omap_dss_put_device(dssdev); + } + + if (fbdev->auto_update_wq != NULL) { + destroy_workqueue(fbdev->auto_update_wq); + fbdev->auto_update_wq = NULL; + } + + dev_set_drvdata(fbdev->dev, NULL); +} + +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) + return -ENOMEM; + + clear_fb_info(fbi); + + fbdev->fbs[i] = fbi; + + ofbi = FB2OFB(fbi); + ofbi->fbdev = fbdev; + ofbi->id = i; + + ofbi->region = &fbdev->regions[i]; + ofbi->region->id = i; + init_rwsem(&ofbi->region->lock); + + /* assign these early, so that fb alloc can use them */ + ofbi->rotation_type = def_vrfb ? OMAP_DSS_ROT_VRFB : + OMAP_DSS_ROT_DMA; + ofbi->mirror = def_mirror; + + 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++) { + struct fb_info *fbi = fbdev->fbs[i]; + struct omapfb_info *ofbi = FB2OFB(fbi); + + omapfb_get_mem_region(ofbi->region); + r = omapfb_fb_init(fbdev, fbi); + omapfb_put_mem_region(ofbi->region); + + if (r) { + dev_err(fbdev->dev, "failed to setup fb_info\n"); + return r; + } + } + + for (i = 0; i < fbdev->num_fbs; i++) { + struct fb_info *fbi = fbdev->fbs[i]; + struct omapfb_info *ofbi = FB2OFB(fbi); + + if (ofbi->region->size == 0) + continue; + + omapfb_clear_fb(fbi); + } + + 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++) { + struct fb_info *fbi = fbdev->fbs[i]; + struct omapfb_info *ofbi = FB2OFB(fbi); + + omapfb_get_mem_region(ofbi->region); + r = omapfb_apply_changes(fbi, 1); + omapfb_put_mem_region(ofbi->region); + + if (r) { + dev_err(fbdev->dev, "failed to change mode\n"); + return r; + } + } + + /* Enable fb0 */ + if (fbdev->num_fbs > 0) { + struct omapfb_info *ofbi = FB2OFB(fbdev->fbs[0]); + + if (ofbi->num_overlays > 0) { + struct omap_overlay *ovl = ofbi->overlays[0]; + + ovl->manager->apply(ovl->manager); + + r = omapfb_overlay_enable(ovl, 1); + + if (r) { + dev_err(fbdev->dev, + "failed to enable overlay\n"); + return r; + } + } + } + + DBG("create_framebuffers done\n"); + + return 0; +} + +static int omapfb_mode_to_timings(const char *mode_str, + struct omap_dss_device *display, + struct omap_video_timings *timings, u8 *bpp) +{ + struct fb_info *fbi; + struct fb_var_screeninfo *var; + struct fb_ops *fbops; + int r; + +#ifdef CONFIG_OMAP2_DSS_VENC + if (strcmp(mode_str, "pal") == 0) { + *timings = omap_dss_pal_timings; + *bpp = 24; + return 0; + } else if (strcmp(mode_str, "ntsc") == 0) { + *timings = omap_dss_ntsc_timings; + *bpp = 24; + return 0; + } +#endif + + /* this is quite a hack, but I wanted to use the modedb and for + * that we need fb_info and var, so we create dummy ones */ + + *bpp = 0; + fbi = NULL; + var = NULL; + fbops = NULL; + + fbi = kzalloc(sizeof(*fbi), GFP_KERNEL); + if (fbi == NULL) { + r = -ENOMEM; + goto err; + } + + var = kzalloc(sizeof(*var), GFP_KERNEL); + if (var == NULL) { + r = -ENOMEM; + goto err; + } + + fbops = kzalloc(sizeof(*fbops), GFP_KERNEL); + if (fbops == NULL) { + r = -ENOMEM; + goto err; + } + + fbi->fbops = fbops; + + r = fb_find_mode(var, fbi, mode_str, NULL, 0, NULL, 24); + if (r == 0) { + r = -EINVAL; + goto err; + } + + if (display->driver->get_timings) { + display->driver->get_timings(display, timings); + } else { + timings->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + timings->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + timings->sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE; + } + + timings->pixelclock = PICOS2KHZ(var->pixclock) * 1000; + timings->hbp = var->left_margin; + timings->hfp = var->right_margin; + timings->vbp = var->upper_margin; + timings->vfp = var->lower_margin; + timings->hsw = var->hsync_len; + timings->vsw = var->vsync_len; + timings->x_res = var->xres; + timings->y_res = var->yres; + timings->hsync_level = var->sync & FB_SYNC_HOR_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + timings->vsync_level = var->sync & FB_SYNC_VERT_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + timings->interlace = var->vmode & FB_VMODE_INTERLACED; + + switch (var->bits_per_pixel) { + case 16: + *bpp = 16; + break; + case 24: + case 32: + default: + *bpp = 24; + break; + } + + r = 0; + +err: + kfree(fbi); + kfree(var); + kfree(fbops); + + return r; +} + +static int omapfb_set_def_mode(struct omapfb2_device *fbdev, + struct omap_dss_device *display, char *mode_str) +{ + int r; + u8 bpp; + struct omap_video_timings timings, temp_timings; + struct omapfb_display_data *d; + + r = omapfb_mode_to_timings(mode_str, display, &timings, &bpp); + if (r) + return r; + + d = get_display_data(fbdev, display); + d->bpp_override = bpp; + + if (display->driver->check_timings) { + r = display->driver->check_timings(display, &timings); + if (r) + return r; + } else { + /* If check_timings is not present compare xres and yres */ + if (display->driver->get_timings) { + display->driver->get_timings(display, &temp_timings); + + if (temp_timings.x_res != timings.x_res || + temp_timings.y_res != timings.y_res) + return -EINVAL; + } + } + + if (display->driver->set_timings) + display->driver->set_timings(display, &timings); + + return 0; +} + +static int omapfb_get_recommended_bpp(struct omapfb2_device *fbdev, + struct omap_dss_device *dssdev) +{ + struct omapfb_display_data *d; + + BUG_ON(dssdev->driver->get_recommended_bpp == NULL); + + d = get_display_data(fbdev, dssdev); + + if (d->bpp_override != 0) + return d->bpp_override; + + return dssdev->driver->get_recommended_bpp(dssdev); +} + +static int omapfb_parse_def_modes(struct omapfb2_device *fbdev) +{ + char *str, *options, *this_opt; + int r = 0; + + str = kstrdup(def_mode, GFP_KERNEL); + if (!str) + return -ENOMEM; + options = str; + + while (!r && (this_opt = strsep(&options, ",")) != NULL) { + char *p, *display_str, *mode_str; + struct omap_dss_device *display; + int i; + + p = strchr(this_opt, ':'); + if (!p) { + r = -EINVAL; + break; + } + + *p = 0; + display_str = this_opt; + mode_str = p + 1; + + display = NULL; + for (i = 0; i < fbdev->num_displays; ++i) { + if (strcmp(fbdev->displays[i].dssdev->name, + display_str) == 0) { + display = fbdev->displays[i].dssdev; + break; + } + } + + if (!display) { + r = -EINVAL; + break; + } + + r = omapfb_set_def_mode(fbdev, display, mode_str); + if (r) + break; + } + + kfree(str); + + return r; +} + +static void fb_videomode_to_omap_timings(struct fb_videomode *m, + struct omap_dss_device *display, + struct omap_video_timings *t) +{ + if (display->driver->get_timings) { + display->driver->get_timings(display, t); + } else { + t->data_pclk_edge = OMAPDSS_DRIVE_SIG_RISING_EDGE; + t->de_level = OMAPDSS_SIG_ACTIVE_HIGH; + t->sync_pclk_edge = OMAPDSS_DRIVE_SIG_FALLING_EDGE; + } + + t->x_res = m->xres; + t->y_res = m->yres; + t->pixelclock = PICOS2KHZ(m->pixclock) * 1000; + t->hsw = m->hsync_len; + t->hfp = m->right_margin; + t->hbp = m->left_margin; + t->vsw = m->vsync_len; + t->vfp = m->lower_margin; + t->vbp = m->upper_margin; + t->hsync_level = m->sync & FB_SYNC_HOR_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + t->vsync_level = m->sync & FB_SYNC_VERT_HIGH_ACT ? + OMAPDSS_SIG_ACTIVE_HIGH : + OMAPDSS_SIG_ACTIVE_LOW; + t->interlace = m->vmode & FB_VMODE_INTERLACED; +} + +static int omapfb_find_best_mode(struct omap_dss_device *display, + struct omap_video_timings *timings) +{ + struct fb_monspecs *specs; + u8 *edid; + int r, i, best_idx, len; + + if (!display->driver->read_edid) + return -ENODEV; + + len = 0x80 * 2; + edid = kmalloc(len, GFP_KERNEL); + if (edid == NULL) + return -ENOMEM; + + r = display->driver->read_edid(display, edid, len); + if (r < 0) + goto err1; + + specs = kzalloc(sizeof(*specs), GFP_KERNEL); + if (specs == NULL) { + r = -ENOMEM; + goto err1; + } + + fb_edid_to_monspecs(edid, specs); + + best_idx = -1; + + for (i = 0; i < specs->modedb_len; ++i) { + struct fb_videomode *m; + struct omap_video_timings t; + + m = &specs->modedb[i]; + + if (m->pixclock == 0) + continue; + + /* skip repeated pixel modes */ + if (m->xres == 2880 || m->xres == 1440) + continue; + + if (m->vmode & FB_VMODE_INTERLACED || + m->vmode & FB_VMODE_DOUBLE) + continue; + + fb_videomode_to_omap_timings(m, display, &t); + + r = display->driver->check_timings(display, &t); + if (r == 0) { + best_idx = i; + break; + } + } + + if (best_idx == -1) { + r = -ENOENT; + goto err2; + } + + fb_videomode_to_omap_timings(&specs->modedb[best_idx], display, + timings); + + r = 0; + +err2: + fb_destroy_modedb(specs->modedb); + kfree(specs); +err1: + kfree(edid); + + return r; +} + +static int omapfb_init_display(struct omapfb2_device *fbdev, + struct omap_dss_device *dssdev) +{ + struct omap_dss_driver *dssdrv = dssdev->driver; + struct omapfb_display_data *d; + int r; + + r = dssdrv->enable(dssdev); + if (r) { + dev_warn(fbdev->dev, "Failed to enable display '%s'\n", + dssdev->name); + return r; + } + + d = get_display_data(fbdev, dssdev); + + d->fbdev = fbdev; + + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) { + u16 w, h; + + if (auto_update) { + omapfb_start_auto_update(fbdev, dssdev); + d->update_mode = OMAPFB_AUTO_UPDATE; + } else { + d->update_mode = OMAPFB_MANUAL_UPDATE; + } + + if (dssdrv->enable_te) { + r = dssdrv->enable_te(dssdev, 1); + if (r) { + dev_err(fbdev->dev, "Failed to set TE\n"); + return r; + } + } + + dssdrv->get_resolution(dssdev, &w, &h); + r = dssdrv->update(dssdev, 0, 0, w, h); + if (r) { + dev_err(fbdev->dev, + "Failed to update display\n"); + return r; + } + } else { + d->update_mode = OMAPFB_AUTO_UPDATE; + } + + return 0; +} + +static int omapfb_init_connections(struct omapfb2_device *fbdev, + struct omap_dss_device *def_dssdev) +{ + int i, r; + struct omap_overlay_manager *mgr; + + r = def_dssdev->driver->connect(def_dssdev); + if (r) { + dev_err(fbdev->dev, "failed to connect default display\n"); + return r; + } + + for (i = 0; i < fbdev->num_displays; ++i) { + struct omap_dss_device *dssdev = fbdev->displays[i].dssdev; + + if (dssdev == def_dssdev) + continue; + + /* + * We don't care if the connect succeeds or not. We just want to + * connect as many displays as possible. + */ + dssdev->driver->connect(dssdev); + } + + mgr = omapdss_find_mgr_from_display(def_dssdev); + + if (!mgr) { + dev_err(fbdev->dev, "no ovl manager for the default display\n"); + return -EINVAL; + } + + for (i = 0; i < fbdev->num_overlays; i++) { + struct omap_overlay *ovl = fbdev->overlays[i]; + + if (ovl->manager) + ovl->unset_manager(ovl); + + r = ovl->set_manager(ovl, mgr); + if (r) + dev_warn(fbdev->dev, + "failed to connect overlay %s to manager %s\n", + ovl->name, mgr->name); + } + + return 0; +} + +static struct omap_dss_device * +omapfb_find_default_display(struct omapfb2_device *fbdev) +{ + const char *def_name; + int i; + + /* + * Search with the display name from the user or the board file, + * comparing to display names and aliases + */ + + def_name = omapdss_get_default_display_name(); + + if (def_name) { + for (i = 0; i < fbdev->num_displays; ++i) { + struct omap_dss_device *dssdev; + + dssdev = fbdev->displays[i].dssdev; + + if (dssdev->name && strcmp(def_name, dssdev->name) == 0) + return dssdev; + + if (strcmp(def_name, dssdev->alias) == 0) + return dssdev; + } + + /* def_name given but not found */ + return NULL; + } + + /* then look for DT alias display0 */ + for (i = 0; i < fbdev->num_displays; ++i) { + struct omap_dss_device *dssdev; + int id; + + dssdev = fbdev->displays[i].dssdev; + + if (dssdev->dev->of_node == NULL) + continue; + + id = of_alias_get_id(dssdev->dev->of_node, "display"); + if (id == 0) + return dssdev; + } + + /* return the first display we have in the list */ + return fbdev->displays[0].dssdev; +} + +static int omapfb_probe(struct platform_device *pdev) +{ + struct omapfb2_device *fbdev = NULL; + int r = 0; + int i; + struct omap_dss_device *def_display; + struct omap_dss_device *dssdev; + + DBG("omapfb_probe\n"); + + if (omapdss_is_initialized() == false) + return -EPROBE_DEFER; + + if (pdev->num_resources != 0) { + dev_err(&pdev->dev, "probed for an unknown device\n"); + r = -ENODEV; + goto err0; + } + + fbdev = devm_kzalloc(&pdev->dev, sizeof(struct omapfb2_device), + GFP_KERNEL); + if (fbdev == NULL) { + r = -ENOMEM; + goto err0; + } + + if (def_vrfb && !omap_vrfb_supported()) { + def_vrfb = 0; + dev_warn(&pdev->dev, "VRFB is not supported on this hardware, " + "ignoring the module parameter vrfb=y\n"); + } + + r = omapdss_compat_init(); + if (r) + goto err0; + + mutex_init(&fbdev->mtx); + + fbdev->dev = &pdev->dev; + platform_set_drvdata(pdev, fbdev); + + fbdev->num_displays = 0; + dssdev = NULL; + for_each_dss_dev(dssdev) { + struct omapfb_display_data *d; + + omap_dss_get_device(dssdev); + + if (!dssdev->driver) { + dev_warn(&pdev->dev, "no driver for display: %s\n", + dssdev->name); + omap_dss_put_device(dssdev); + continue; + } + + d = &fbdev->displays[fbdev->num_displays++]; + d->dssdev = dssdev; + if (dssdev->caps & OMAP_DSS_DISPLAY_CAP_MANUAL_UPDATE) + d->update_mode = OMAPFB_MANUAL_UPDATE; + else + d->update_mode = OMAPFB_AUTO_UPDATE; + } + + if (fbdev->num_displays == 0) { + dev_err(&pdev->dev, "no displays\n"); + r = -EPROBE_DEFER; + 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); + + def_display = omapfb_find_default_display(fbdev); + if (def_display == NULL) { + dev_err(fbdev->dev, "failed to find default display\n"); + r = -EPROBE_DEFER; + goto cleanup; + } + + r = omapfb_init_connections(fbdev, def_display); + if (r) { + dev_err(fbdev->dev, "failed to init overlay connections\n"); + goto cleanup; + } + + if (def_mode && strlen(def_mode) > 0) { + if (omapfb_parse_def_modes(fbdev)) + dev_warn(&pdev->dev, "cannot parse default modes\n"); + } else if (def_display && def_display->driver->set_timings && + def_display->driver->check_timings) { + struct omap_video_timings t; + + r = omapfb_find_best_mode(def_display, &t); + + if (r == 0) + def_display->driver->set_timings(def_display, &t); + } + + 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_warn(fbdev->dev, "failed to apply dispc config\n"); + } + + DBG("mgr->apply'ed\n"); + + if (def_display) { + r = omapfb_init_display(fbdev, def_display); + if (r) { + dev_err(fbdev->dev, + "failed to initialize default " + "display\n"); + goto cleanup; + } + } + + DBG("create sysfs for fbs\n"); + r = omapfb_create_sysfs(fbdev); + if (r) { + dev_err(fbdev->dev, "failed to create sysfs entries\n"); + goto cleanup; + } + + if (def_display) { + u16 w, h; + + def_display->driver->get_resolution(def_display, &w, &h); + + dev_info(fbdev->dev, "using display '%s' mode %dx%d\n", + def_display->name, w, h); + } + + return 0; + +cleanup: + omapfb_free_resources(fbdev); + omapdss_compat_uninit(); +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); + + omapdss_compat_uninit(); + + return 0; +} + +static struct platform_driver omapfb_driver = { + .probe = omapfb_probe, + .remove = omapfb_remove, + .driver = { + .name = "omapfb", + }, +}; + +module_param_named(mode, def_mode, charp, 0); +module_param_named(vram, def_vram, charp, 0); +module_param_named(rotate, def_rotate, int, 0); +module_param_named(vrfb, def_vrfb, bool, 0); +module_param_named(mirror, def_mirror, bool, 0); + +module_platform_driver(omapfb_driver); + +MODULE_ALIAS("platform:omapfb"); +MODULE_AUTHOR("Tomi Valkeinen <tomi.valkeinen@nokia.com>"); +MODULE_DESCRIPTION("OMAP2/3 Framebuffer"); +MODULE_LICENSE("GPL v2"); |