| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Framebuffer driver for TI OMAP boards | |
| * | |
| * Copyright (C) 2004 Nokia Corporation | |
| * Author: Imre Deak <imre.deak@nokia.com> | |
| * | |
| * Acknowledgements: | |
| * Alex McMains <aam@ridgerun.com> - Original driver | |
| * Juha Yrjola <juha.yrjola@nokia.com> - Original driver and improvements | |
| * Dirk Behme <dirk.behme@de.bosch.com> - changes for 2.6 kernel API | |
| * Texas Instruments - H3 support | |
| */ | |
| #include <linux/export.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/mm.h> | |
| #include <linux/slab.h> | |
| #include <linux/uaccess.h> | |
| #include <linux/module.h> | |
| #include <linux/sysfs.h> | |
| #include <linux/omap-dma.h> | |
| #include <linux/soc/ti/omap1-soc.h> | |
| #include "omapfb.h" | |
| #include "lcdc.h" | |
| #define MODULE_NAME "omapfb" | |
| static unsigned int def_accel; | |
| static unsigned long def_vram[OMAPFB_PLANE_NUM]; | |
| static unsigned int def_vram_cnt; | |
| static unsigned long def_vxres; | |
| static unsigned long def_vyres; | |
| static unsigned int def_rotate; | |
| static unsigned int def_mirror; | |
| static bool manual_update = IS_BUILTIN(CONFIG_FB_OMAP_MANUAL_UPDATE); | |
| static struct platform_device *fbdev_pdev; | |
| static struct lcd_panel *fbdev_panel; | |
| static struct omapfb_device *omapfb_dev; | |
| struct caps_table_struct { | |
| unsigned long flag; | |
| const char *name; | |
| }; | |
| static const struct caps_table_struct ctrl_caps[] = { | |
| { OMAPFB_CAPS_MANUAL_UPDATE, "manual update" }, | |
| { OMAPFB_CAPS_TEARSYNC, "tearing synchronization" }, | |
| { OMAPFB_CAPS_PLANE_RELOCATE_MEM, "relocate plane memory" }, | |
| { OMAPFB_CAPS_PLANE_SCALE, "scale plane" }, | |
| { OMAPFB_CAPS_WINDOW_PIXEL_DOUBLE, "pixel double window" }, | |
| { OMAPFB_CAPS_WINDOW_SCALE, "scale window" }, | |
| { OMAPFB_CAPS_WINDOW_OVERLAY, "overlay window" }, | |
| { OMAPFB_CAPS_WINDOW_ROTATE, "rotate window" }, | |
| { OMAPFB_CAPS_SET_BACKLIGHT, "backlight setting" }, | |
| }; | |
| static const struct caps_table_struct color_caps[] = { | |
| { 1 << OMAPFB_COLOR_RGB565, "RGB565", }, | |
| { 1 << OMAPFB_COLOR_YUV422, "YUV422", }, | |
| { 1 << OMAPFB_COLOR_YUV420, "YUV420", }, | |
| { 1 << OMAPFB_COLOR_CLUT_8BPP, "CLUT8", }, | |
| { 1 << OMAPFB_COLOR_CLUT_4BPP, "CLUT4", }, | |
| { 1 << OMAPFB_COLOR_CLUT_2BPP, "CLUT2", }, | |
| { 1 << OMAPFB_COLOR_CLUT_1BPP, "CLUT1", }, | |
| { 1 << OMAPFB_COLOR_RGB444, "RGB444", }, | |
| { 1 << OMAPFB_COLOR_YUY422, "YUY422", }, | |
| }; | |
| static void omapdss_release(struct device *dev) | |
| { | |
| } | |
| /* dummy device for clocks */ | |
| static struct platform_device omapdss_device = { | |
| .name = "omapdss_dss", | |
| .id = -1, | |
| .dev = { | |
| .release = omapdss_release, | |
| }, | |
| }; | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * LCD panel | |
| * --------------------------------------------------------------------------- | |
| */ | |
| extern struct lcd_ctrl hwa742_ctrl; | |
| static const struct lcd_ctrl *ctrls[] = { | |
| &omap1_int_ctrl, | |
| #ifdef CONFIG_FB_OMAP_LCDC_HWA742 | |
| &hwa742_ctrl, | |
| #endif | |
| }; | |
| #ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL | |
| extern struct lcd_ctrl_extif omap1_ext_if; | |
| #endif | |
| static void omapfb_rqueue_lock(struct omapfb_device *fbdev) | |
| { | |
| mutex_lock(&fbdev->rqueue_mutex); | |
| } | |
| static void omapfb_rqueue_unlock(struct omapfb_device *fbdev) | |
| { | |
| mutex_unlock(&fbdev->rqueue_mutex); | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * LCD controller and LCD DMA | |
| * --------------------------------------------------------------------------- | |
| */ | |
| /* | |
| * Allocate resources needed for LCD controller and LCD DMA operations. Video | |
| * memory is allocated from system memory according to the virtual display | |
| * size, except if a bigger memory size is specified explicitly as a kernel | |
| * parameter. | |
| */ | |
| static int ctrl_init(struct omapfb_device *fbdev) | |
| { | |
| int r; | |
| int i; | |
| /* kernel/module vram parameters override boot tags/board config */ | |
| if (def_vram_cnt) { | |
| for (i = 0; i < def_vram_cnt; i++) | |
| fbdev->mem_desc.region[i].size = | |
| PAGE_ALIGN(def_vram[i]); | |
| fbdev->mem_desc.region_cnt = i; | |
| } | |
| if (!fbdev->mem_desc.region_cnt) { | |
| struct lcd_panel *panel = fbdev->panel; | |
| int def_size; | |
| int bpp = panel->bpp; | |
| /* 12 bpp is packed in 16 bits */ | |
| if (bpp == 12) | |
| bpp = 16; | |
| def_size = def_vxres * def_vyres * bpp / 8; | |
| fbdev->mem_desc.region_cnt = 1; | |
| fbdev->mem_desc.region[0].size = PAGE_ALIGN(def_size); | |
| } | |
| r = fbdev->ctrl->init(fbdev, 0, &fbdev->mem_desc); | |
| if (r < 0) { | |
| dev_err(fbdev->dev, "controller initialization failed (%d)\n", | |
| r); | |
| return r; | |
| } | |
| #ifdef DEBUG | |
| for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { | |
| dev_dbg(fbdev->dev, "region%d phys %08x virt %p size=%lu\n", | |
| i, | |
| fbdev->mem_desc.region[i].paddr, | |
| fbdev->mem_desc.region[i].vaddr, | |
| fbdev->mem_desc.region[i].size); | |
| } | |
| #endif | |
| return 0; | |
| } | |
| static void ctrl_cleanup(struct omapfb_device *fbdev) | |
| { | |
| fbdev->ctrl->cleanup(); | |
| } | |
| /* Must be called with fbdev->rqueue_mutex held. */ | |
| static int ctrl_change_mode(struct fb_info *fbi) | |
| { | |
| int r; | |
| unsigned long offset; | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct fb_var_screeninfo *var = &fbi->var; | |
| offset = var->yoffset * fbi->fix.line_length + | |
| var->xoffset * var->bits_per_pixel / 8; | |
| if (fbdev->ctrl->sync) | |
| fbdev->ctrl->sync(); | |
| r = fbdev->ctrl->setup_plane(plane->idx, plane->info.channel_out, | |
| offset, var->xres_virtual, | |
| plane->info.pos_x, plane->info.pos_y, | |
| var->xres, var->yres, plane->color_mode); | |
| if (r < 0) | |
| return r; | |
| if (fbdev->ctrl->set_rotate != NULL) { | |
| r = fbdev->ctrl->set_rotate(var->rotate); | |
| if (r < 0) | |
| return r; | |
| } | |
| if (fbdev->ctrl->set_scale != NULL) | |
| r = fbdev->ctrl->set_scale(plane->idx, | |
| var->xres, var->yres, | |
| plane->info.out_width, | |
| plane->info.out_height); | |
| return r; | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * fbdev framework callbacks and the ioctl interface | |
| * --------------------------------------------------------------------------- | |
| */ | |
| /* Called each time the omapfb device is opened */ | |
| static int omapfb_open(struct fb_info *info, int user) | |
| { | |
| return 0; | |
| } | |
| static void omapfb_sync(struct fb_info *info); | |
| /* Called when the omapfb device is closed. We make sure that any pending | |
| * gfx DMA operations are ended, before we return. */ | |
| static int omapfb_release(struct fb_info *info, int user) | |
| { | |
| omapfb_sync(info); | |
| return 0; | |
| } | |
| /* Store a single color palette entry into a pseudo palette or the hardware | |
| * palette if one is available. For now we support only 16bpp and thus store | |
| * the entry only to the pseudo palette. | |
| */ | |
| static int _setcolreg(struct fb_info *info, u_int regno, u_int red, u_int green, | |
| u_int blue, u_int transp, int update_hw_pal) | |
| { | |
| struct omapfb_plane_struct *plane = info->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct fb_var_screeninfo *var = &info->var; | |
| int r = 0; | |
| switch (plane->color_mode) { | |
| case OMAPFB_COLOR_YUV422: | |
| case OMAPFB_COLOR_YUV420: | |
| case OMAPFB_COLOR_YUY422: | |
| r = -EINVAL; | |
| break; | |
| case OMAPFB_COLOR_CLUT_8BPP: | |
| case OMAPFB_COLOR_CLUT_4BPP: | |
| case OMAPFB_COLOR_CLUT_2BPP: | |
| case OMAPFB_COLOR_CLUT_1BPP: | |
| if (fbdev->ctrl->setcolreg) | |
| r = fbdev->ctrl->setcolreg(regno, red, green, blue, | |
| transp, update_hw_pal); | |
| fallthrough; | |
| case OMAPFB_COLOR_RGB565: | |
| case OMAPFB_COLOR_RGB444: | |
| if (r != 0) | |
| break; | |
| if (regno < 16) { | |
| u16 pal; | |
| pal = ((red >> (16 - var->red.length)) << | |
| var->red.offset) | | |
| ((green >> (16 - var->green.length)) << | |
| var->green.offset) | | |
| (blue >> (16 - var->blue.length)); | |
| ((u32 *)(info->pseudo_palette))[regno] = pal; | |
| } | |
| break; | |
| default: | |
| BUG(); | |
| } | |
| return r; | |
| } | |
| static int omapfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, | |
| u_int transp, struct fb_info *info) | |
| { | |
| return _setcolreg(info, regno, red, green, blue, transp, 1); | |
| } | |
| static int omapfb_setcmap(struct fb_cmap *cmap, struct fb_info *info) | |
| { | |
| int count, index, r; | |
| u16 *red, *green, *blue, *transp; | |
| u16 trans = 0xffff; | |
| red = cmap->red; | |
| green = cmap->green; | |
| blue = cmap->blue; | |
| transp = cmap->transp; | |
| index = cmap->start; | |
| for (count = 0; count < cmap->len; count++) { | |
| if (transp) | |
| trans = *transp++; | |
| r = _setcolreg(info, index++, *red++, *green++, *blue++, trans, | |
| count == cmap->len - 1); | |
| if (r != 0) | |
| return r; | |
| } | |
| return 0; | |
| } | |
| static int omapfb_update_full_screen(struct fb_info *fbi); | |
| static int omapfb_blank(int blank, struct fb_info *fbi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| int do_update = 0; | |
| int r = 0; | |
| omapfb_rqueue_lock(fbdev); | |
| switch (blank) { | |
| case FB_BLANK_UNBLANK: | |
| if (fbdev->state == OMAPFB_SUSPENDED) { | |
| if (fbdev->ctrl->resume) | |
| fbdev->ctrl->resume(); | |
| if (fbdev->panel->enable) | |
| fbdev->panel->enable(fbdev->panel); | |
| fbdev->state = OMAPFB_ACTIVE; | |
| if (fbdev->ctrl->get_update_mode() == | |
| OMAPFB_MANUAL_UPDATE) | |
| do_update = 1; | |
| } | |
| break; | |
| case FB_BLANK_POWERDOWN: | |
| if (fbdev->state == OMAPFB_ACTIVE) { | |
| if (fbdev->panel->disable) | |
| fbdev->panel->disable(fbdev->panel); | |
| if (fbdev->ctrl->suspend) | |
| fbdev->ctrl->suspend(); | |
| fbdev->state = OMAPFB_SUSPENDED; | |
| } | |
| break; | |
| default: | |
| r = -EINVAL; | |
| } | |
| omapfb_rqueue_unlock(fbdev); | |
| if (r == 0 && do_update) | |
| r = omapfb_update_full_screen(fbi); | |
| return r; | |
| } | |
| static void omapfb_sync(struct fb_info *fbi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| omapfb_rqueue_lock(fbdev); | |
| if (fbdev->ctrl->sync) | |
| fbdev->ctrl->sync(); | |
| omapfb_rqueue_unlock(fbdev); | |
| } | |
| /* | |
| * Set fb_info.fix fields and also updates fbdev. | |
| * When calling this fb_info.var must be set up already. | |
| */ | |
| static void set_fb_fix(struct fb_info *fbi, int from_init) | |
| { | |
| struct fb_fix_screeninfo *fix = &fbi->fix; | |
| struct fb_var_screeninfo *var = &fbi->var; | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_mem_region *rg; | |
| int bpp; | |
| rg = &plane->fbdev->mem_desc.region[plane->idx]; | |
| fbi->screen_base = rg->vaddr; | |
| if (!from_init) { | |
| mutex_lock(&fbi->mm_lock); | |
| fix->smem_start = rg->paddr; | |
| fix->smem_len = rg->size; | |
| mutex_unlock(&fbi->mm_lock); | |
| } else { | |
| fix->smem_start = rg->paddr; | |
| fix->smem_len = rg->size; | |
| } | |
| fix->type = FB_TYPE_PACKED_PIXELS; | |
| bpp = var->bits_per_pixel; | |
| if (var->nonstd) | |
| fix->visual = FB_VISUAL_PSEUDOCOLOR; | |
| else switch (var->bits_per_pixel) { | |
| case 16: | |
| case 12: | |
| fix->visual = FB_VISUAL_TRUECOLOR; | |
| /* 12bpp is stored in 16 bits */ | |
| bpp = 16; | |
| break; | |
| case 1: | |
| case 2: | |
| case 4: | |
| case 8: | |
| fix->visual = FB_VISUAL_PSEUDOCOLOR; | |
| break; | |
| } | |
| fix->accel = FB_ACCEL_OMAP1610; | |
| fix->line_length = var->xres_virtual * bpp / 8; | |
| } | |
| static int set_color_mode(struct omapfb_plane_struct *plane, | |
| struct fb_var_screeninfo *var) | |
| { | |
| switch (var->nonstd) { | |
| case 0: | |
| break; | |
| case OMAPFB_COLOR_YUV422: | |
| var->bits_per_pixel = 16; | |
| plane->color_mode = var->nonstd; | |
| return 0; | |
| case OMAPFB_COLOR_YUV420: | |
| var->bits_per_pixel = 12; | |
| plane->color_mode = var->nonstd; | |
| return 0; | |
| case OMAPFB_COLOR_YUY422: | |
| var->bits_per_pixel = 16; | |
| plane->color_mode = var->nonstd; | |
| return 0; | |
| default: | |
| return -EINVAL; | |
| } | |
| switch (var->bits_per_pixel) { | |
| case 1: | |
| plane->color_mode = OMAPFB_COLOR_CLUT_1BPP; | |
| return 0; | |
| case 2: | |
| plane->color_mode = OMAPFB_COLOR_CLUT_2BPP; | |
| return 0; | |
| case 4: | |
| plane->color_mode = OMAPFB_COLOR_CLUT_4BPP; | |
| return 0; | |
| case 8: | |
| plane->color_mode = OMAPFB_COLOR_CLUT_8BPP; | |
| return 0; | |
| case 12: | |
| var->bits_per_pixel = 16; | |
| fallthrough; | |
| case 16: | |
| if (plane->fbdev->panel->bpp == 12) | |
| plane->color_mode = OMAPFB_COLOR_RGB444; | |
| else | |
| plane->color_mode = OMAPFB_COLOR_RGB565; | |
| return 0; | |
| default: | |
| return -EINVAL; | |
| } | |
| } | |
| /* | |
| * Check the values in var against our capabilities and in case of out of | |
| * bound values try to adjust them. | |
| */ | |
| static int set_fb_var(struct fb_info *fbi, | |
| struct fb_var_screeninfo *var) | |
| { | |
| int bpp; | |
| unsigned long max_frame_size; | |
| unsigned long line_size; | |
| int xres_min, xres_max; | |
| int yres_min, yres_max; | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct lcd_panel *panel = fbdev->panel; | |
| if (set_color_mode(plane, var) < 0) | |
| return -EINVAL; | |
| bpp = var->bits_per_pixel; | |
| if (plane->color_mode == OMAPFB_COLOR_RGB444) | |
| bpp = 16; | |
| switch (var->rotate) { | |
| case 0: | |
| case 180: | |
| xres_min = OMAPFB_PLANE_XRES_MIN; | |
| xres_max = panel->x_res; | |
| yres_min = OMAPFB_PLANE_YRES_MIN; | |
| yres_max = panel->y_res; | |
| if (cpu_is_omap15xx()) { | |
| var->xres = panel->x_res; | |
| var->yres = panel->y_res; | |
| } | |
| break; | |
| case 90: | |
| case 270: | |
| xres_min = OMAPFB_PLANE_YRES_MIN; | |
| xres_max = panel->y_res; | |
| yres_min = OMAPFB_PLANE_XRES_MIN; | |
| yres_max = panel->x_res; | |
| if (cpu_is_omap15xx()) { | |
| var->xres = panel->y_res; | |
| var->yres = panel->x_res; | |
| } | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| if (var->xres < xres_min) | |
| var->xres = xres_min; | |
| if (var->yres < yres_min) | |
| var->yres = yres_min; | |
| if (var->xres > xres_max) | |
| var->xres = xres_max; | |
| if (var->yres > yres_max) | |
| var->yres = yres_max; | |
| if (var->xres_virtual < var->xres) | |
| var->xres_virtual = var->xres; | |
| if (var->yres_virtual < var->yres) | |
| var->yres_virtual = var->yres; | |
| max_frame_size = fbdev->mem_desc.region[plane->idx].size; | |
| line_size = var->xres_virtual * bpp / 8; | |
| if (line_size * var->yres_virtual > max_frame_size) { | |
| /* Try to keep yres_virtual first */ | |
| line_size = max_frame_size / var->yres_virtual; | |
| var->xres_virtual = line_size * 8 / bpp; | |
| if (var->xres_virtual < var->xres) { | |
| /* Still doesn't fit. Shrink yres_virtual too */ | |
| var->xres_virtual = var->xres; | |
| line_size = var->xres * bpp / 8; | |
| var->yres_virtual = max_frame_size / line_size; | |
| } | |
| /* Recheck this, as the virtual size changed. */ | |
| if (var->xres_virtual < var->xres) | |
| var->xres = var->xres_virtual; | |
| if (var->yres_virtual < var->yres) | |
| var->yres = var->yres_virtual; | |
| if (var->xres < xres_min || var->yres < yres_min) | |
| return -EINVAL; | |
| } | |
| if (var->xres + var->xoffset > var->xres_virtual) | |
| var->xoffset = var->xres_virtual - var->xres; | |
| if (var->yres + var->yoffset > var->yres_virtual) | |
| var->yoffset = var->yres_virtual - var->yres; | |
| if (plane->color_mode == OMAPFB_COLOR_RGB444) { | |
| var->red.offset = 8; | |
| var->red.length = 4; | |
| var->red.msb_right = 0; | |
| var->green.offset = 4; | |
| var->green.length = 4; | |
| var->green.msb_right = 0; | |
| var->blue.offset = 0; | |
| var->blue.length = 4; | |
| var->blue.msb_right = 0; | |
| } else { | |
| var->red.offset = 11; | |
| var->red.length = 5; | |
| var->red.msb_right = 0; | |
| var->green.offset = 5; | |
| var->green.length = 6; | |
| var->green.msb_right = 0; | |
| var->blue.offset = 0; | |
| var->blue.length = 5; | |
| var->blue.msb_right = 0; | |
| } | |
| var->height = -1; | |
| var->width = -1; | |
| var->grayscale = 0; | |
| /* pixclock in ps, the rest in pixclock */ | |
| var->pixclock = 10000000 / (panel->pixel_clock / 100); | |
| var->left_margin = panel->hfp; | |
| var->right_margin = panel->hbp; | |
| var->upper_margin = panel->vfp; | |
| var->lower_margin = panel->vbp; | |
| var->hsync_len = panel->hsw; | |
| var->vsync_len = panel->vsw; | |
| /* TODO: get these from panel->config */ | |
| var->vmode = FB_VMODE_NONINTERLACED; | |
| var->sync = 0; | |
| return 0; | |
| } | |
| /* | |
| * Set new x,y offsets in the virtual display for the visible area and switch | |
| * to the new mode. | |
| */ | |
| static int omapfb_pan_display(struct fb_var_screeninfo *var, | |
| struct fb_info *fbi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| int r = 0; | |
| omapfb_rqueue_lock(fbdev); | |
| if (var->xoffset != fbi->var.xoffset || | |
| var->yoffset != fbi->var.yoffset) { | |
| struct fb_var_screeninfo *new_var = &fbdev->new_var; | |
| memcpy(new_var, &fbi->var, sizeof(*new_var)); | |
| new_var->xoffset = var->xoffset; | |
| new_var->yoffset = var->yoffset; | |
| if (set_fb_var(fbi, new_var)) | |
| r = -EINVAL; | |
| else { | |
| memcpy(&fbi->var, new_var, sizeof(*new_var)); | |
| ctrl_change_mode(fbi); | |
| } | |
| } | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| /* Set mirror to vertical axis and switch to the new mode. */ | |
| static int omapfb_mirror(struct fb_info *fbi, int mirror) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| int r = 0; | |
| omapfb_rqueue_lock(fbdev); | |
| mirror = mirror ? 1 : 0; | |
| if (cpu_is_omap15xx()) | |
| r = -EINVAL; | |
| else if (mirror != plane->info.mirror) { | |
| plane->info.mirror = mirror; | |
| r = ctrl_change_mode(fbi); | |
| } | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| /* | |
| * Check values in var, try to adjust them in case of out of bound values if | |
| * possible, or return error. | |
| */ | |
| static int omapfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| int r; | |
| omapfb_rqueue_lock(fbdev); | |
| if (fbdev->ctrl->sync != NULL) | |
| fbdev->ctrl->sync(); | |
| r = set_fb_var(fbi, var); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| /* | |
| * Switch to a new mode. The parameters for it has been check already by | |
| * omapfb_check_var. | |
| */ | |
| static int omapfb_set_par(struct fb_info *fbi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| int r = 0; | |
| omapfb_rqueue_lock(fbdev); | |
| set_fb_fix(fbi, 0); | |
| r = ctrl_change_mode(fbi); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static int omapfb_update_window_async(struct fb_info *fbi, | |
| struct omapfb_update_window *win, | |
| void (*callback)(void *), | |
| void *callback_data) | |
| { | |
| int xres, yres; | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct fb_var_screeninfo *var = &fbi->var; | |
| switch (var->rotate) { | |
| case 0: | |
| case 180: | |
| xres = fbdev->panel->x_res; | |
| yres = fbdev->panel->y_res; | |
| break; | |
| case 90: | |
| case 270: | |
| xres = fbdev->panel->y_res; | |
| yres = fbdev->panel->x_res; | |
| break; | |
| default: | |
| return -EINVAL; | |
| } | |
| if (win->x >= xres || win->y >= yres || | |
| win->out_x > xres || win->out_y > yres) | |
| return -EINVAL; | |
| if (!fbdev->ctrl->update_window || | |
| fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE) | |
| return -ENODEV; | |
| if (win->x + win->width > xres) | |
| win->width = xres - win->x; | |
| if (win->y + win->height > yres) | |
| win->height = yres - win->y; | |
| if (win->out_x + win->out_width > xres) | |
| win->out_width = xres - win->out_x; | |
| if (win->out_y + win->out_height > yres) | |
| win->out_height = yres - win->out_y; | |
| if (!win->width || !win->height || !win->out_width || !win->out_height) | |
| return 0; | |
| return fbdev->ctrl->update_window(fbi, win, callback, callback_data); | |
| } | |
| static int omapfb_update_win(struct fb_info *fbi, | |
| struct omapfb_update_window *win) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| int ret; | |
| omapfb_rqueue_lock(plane->fbdev); | |
| ret = omapfb_update_window_async(fbi, win, NULL, NULL); | |
| omapfb_rqueue_unlock(plane->fbdev); | |
| return ret; | |
| } | |
| static int omapfb_update_full_screen(struct fb_info *fbi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct omapfb_update_window win; | |
| int r; | |
| if (!fbdev->ctrl->update_window || | |
| fbdev->ctrl->get_update_mode() != OMAPFB_MANUAL_UPDATE) | |
| return -ENODEV; | |
| win.x = 0; | |
| win.y = 0; | |
| win.width = fbi->var.xres; | |
| win.height = fbi->var.yres; | |
| win.out_x = 0; | |
| win.out_y = 0; | |
| win.out_width = fbi->var.xres; | |
| win.out_height = fbi->var.yres; | |
| win.format = 0; | |
| omapfb_rqueue_lock(fbdev); | |
| r = fbdev->ctrl->update_window(fbi, &win, NULL, NULL); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static int omapfb_setup_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct lcd_panel *panel = fbdev->panel; | |
| struct omapfb_plane_info old_info; | |
| int r = 0; | |
| if (pi->pos_x + pi->out_width > panel->x_res || | |
| pi->pos_y + pi->out_height > panel->y_res) | |
| return -EINVAL; | |
| omapfb_rqueue_lock(fbdev); | |
| if (pi->enabled && !fbdev->mem_desc.region[plane->idx].size) { | |
| /* | |
| * This plane's memory was freed, can't enable it | |
| * until it's reallocated. | |
| */ | |
| r = -EINVAL; | |
| goto out; | |
| } | |
| old_info = plane->info; | |
| plane->info = *pi; | |
| if (pi->enabled) { | |
| r = ctrl_change_mode(fbi); | |
| if (r < 0) { | |
| plane->info = old_info; | |
| goto out; | |
| } | |
| } | |
| r = fbdev->ctrl->enable_plane(plane->idx, pi->enabled); | |
| if (r < 0) { | |
| plane->info = old_info; | |
| goto out; | |
| } | |
| out: | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static int omapfb_query_plane(struct fb_info *fbi, struct omapfb_plane_info *pi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| *pi = plane->info; | |
| return 0; | |
| } | |
| static int omapfb_setup_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct omapfb_mem_region *rg = &fbdev->mem_desc.region[plane->idx]; | |
| size_t size; | |
| int r = 0; | |
| if (fbdev->ctrl->setup_mem == NULL) | |
| return -ENODEV; | |
| if (mi->type != OMAPFB_MEMTYPE_SDRAM) | |
| return -EINVAL; | |
| size = PAGE_ALIGN(mi->size); | |
| omapfb_rqueue_lock(fbdev); | |
| if (plane->info.enabled) { | |
| r = -EBUSY; | |
| goto out; | |
| } | |
| if (rg->size != size || rg->type != mi->type) { | |
| struct fb_var_screeninfo *new_var = &fbdev->new_var; | |
| unsigned long old_size = rg->size; | |
| u8 old_type = rg->type; | |
| unsigned long paddr; | |
| rg->size = size; | |
| rg->type = mi->type; | |
| /* | |
| * size == 0 is a special case, for which we | |
| * don't check / adjust the screen parameters. | |
| * This isn't a problem since the plane can't | |
| * be reenabled unless its size is > 0. | |
| */ | |
| if (old_size != size && size) { | |
| memcpy(new_var, &fbi->var, sizeof(*new_var)); | |
| r = set_fb_var(fbi, new_var); | |
| if (r < 0) | |
| goto out; | |
| } | |
| if (fbdev->ctrl->sync) | |
| fbdev->ctrl->sync(); | |
| r = fbdev->ctrl->setup_mem(plane->idx, size, mi->type, &paddr); | |
| if (r < 0) { | |
| /* Revert changes. */ | |
| rg->size = old_size; | |
| rg->type = old_type; | |
| goto out; | |
| } | |
| rg->paddr = paddr; | |
| if (old_size != size) { | |
| if (size) { | |
| memcpy(&fbi->var, new_var, sizeof(fbi->var)); | |
| set_fb_fix(fbi, 0); | |
| } else { | |
| /* | |
| * Set these explicitly to indicate that the | |
| * plane memory is dealloce'd, the other | |
| * screen parameters in var / fix are invalid. | |
| */ | |
| mutex_lock(&fbi->mm_lock); | |
| fbi->fix.smem_start = 0; | |
| fbi->fix.smem_len = 0; | |
| mutex_unlock(&fbi->mm_lock); | |
| } | |
| } | |
| } | |
| out: | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static int omapfb_query_mem(struct fb_info *fbi, struct omapfb_mem_info *mi) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| struct omapfb_mem_region *rg; | |
| rg = &fbdev->mem_desc.region[plane->idx]; | |
| memset(mi, 0, sizeof(*mi)); | |
| mi->size = rg->size; | |
| mi->type = rg->type; | |
| return 0; | |
| } | |
| static int omapfb_set_color_key(struct omapfb_device *fbdev, | |
| struct omapfb_color_key *ck) | |
| { | |
| int r; | |
| if (!fbdev->ctrl->set_color_key) | |
| return -ENODEV; | |
| omapfb_rqueue_lock(fbdev); | |
| r = fbdev->ctrl->set_color_key(ck); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static int omapfb_get_color_key(struct omapfb_device *fbdev, | |
| struct omapfb_color_key *ck) | |
| { | |
| int r; | |
| if (!fbdev->ctrl->get_color_key) | |
| return -ENODEV; | |
| omapfb_rqueue_lock(fbdev); | |
| r = fbdev->ctrl->get_color_key(ck); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static struct blocking_notifier_head omapfb_client_list[OMAPFB_PLANE_NUM]; | |
| static int notifier_inited; | |
| static void omapfb_init_notifier(void) | |
| { | |
| int i; | |
| for (i = 0; i < OMAPFB_PLANE_NUM; i++) | |
| BLOCKING_INIT_NOTIFIER_HEAD(&omapfb_client_list[i]); | |
| } | |
| int omapfb_register_client(struct omapfb_notifier_block *omapfb_nb, | |
| omapfb_notifier_callback_t callback, | |
| void *callback_data) | |
| { | |
| int r; | |
| if ((unsigned)omapfb_nb->plane_idx >= OMAPFB_PLANE_NUM) | |
| return -EINVAL; | |
| if (!notifier_inited) { | |
| omapfb_init_notifier(); | |
| notifier_inited = 1; | |
| } | |
| omapfb_nb->nb.notifier_call = (int (*)(struct notifier_block *, | |
| unsigned long, void *))callback; | |
| omapfb_nb->data = callback_data; | |
| r = blocking_notifier_chain_register( | |
| &omapfb_client_list[omapfb_nb->plane_idx], | |
| &omapfb_nb->nb); | |
| if (r) | |
| return r; | |
| if (omapfb_dev != NULL && | |
| omapfb_dev->ctrl && omapfb_dev->ctrl->bind_client) { | |
| omapfb_dev->ctrl->bind_client(omapfb_nb); | |
| } | |
| return 0; | |
| } | |
| EXPORT_SYMBOL(omapfb_register_client); | |
| int omapfb_unregister_client(struct omapfb_notifier_block *omapfb_nb) | |
| { | |
| return blocking_notifier_chain_unregister( | |
| &omapfb_client_list[omapfb_nb->plane_idx], &omapfb_nb->nb); | |
| } | |
| EXPORT_SYMBOL(omapfb_unregister_client); | |
| void omapfb_notify_clients(struct omapfb_device *fbdev, unsigned long event) | |
| { | |
| int i; | |
| if (!notifier_inited) | |
| /* no client registered yet */ | |
| return; | |
| for (i = 0; i < OMAPFB_PLANE_NUM; i++) | |
| blocking_notifier_call_chain(&omapfb_client_list[i], event, | |
| fbdev->fb_info[i]); | |
| } | |
| EXPORT_SYMBOL(omapfb_notify_clients); | |
| static int omapfb_set_update_mode(struct omapfb_device *fbdev, | |
| enum omapfb_update_mode mode) | |
| { | |
| int r; | |
| omapfb_rqueue_lock(fbdev); | |
| r = fbdev->ctrl->set_update_mode(mode); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static enum omapfb_update_mode omapfb_get_update_mode(struct omapfb_device *fbdev) | |
| { | |
| int r; | |
| omapfb_rqueue_lock(fbdev); | |
| r = fbdev->ctrl->get_update_mode(); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| static void omapfb_get_caps(struct omapfb_device *fbdev, int plane, | |
| struct omapfb_caps *caps) | |
| { | |
| memset(caps, 0, sizeof(*caps)); | |
| fbdev->ctrl->get_caps(plane, caps); | |
| if (fbdev->panel->get_caps) | |
| caps->ctrl |= fbdev->panel->get_caps(fbdev->panel); | |
| } | |
| /* For lcd testing */ | |
| void omapfb_write_first_pixel(struct omapfb_device *fbdev, u16 pixval) | |
| { | |
| omapfb_rqueue_lock(fbdev); | |
| *(u16 *)fbdev->mem_desc.region[0].vaddr = pixval; | |
| if (fbdev->ctrl->get_update_mode() == OMAPFB_MANUAL_UPDATE) { | |
| struct omapfb_update_window win; | |
| memset(&win, 0, sizeof(win)); | |
| win.width = 2; | |
| win.height = 2; | |
| win.out_width = 2; | |
| win.out_height = 2; | |
| fbdev->ctrl->update_window(fbdev->fb_info[0], &win, NULL, NULL); | |
| } | |
| omapfb_rqueue_unlock(fbdev); | |
| } | |
| EXPORT_SYMBOL(omapfb_write_first_pixel); | |
| /* | |
| * Ioctl interface. Part of the kernel mode frame buffer API is duplicated | |
| * here to be accessible by user mode code. | |
| */ | |
| static int omapfb_ioctl(struct fb_info *fbi, unsigned int cmd, | |
| unsigned long arg) | |
| { | |
| struct omapfb_plane_struct *plane = fbi->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| const struct fb_ops *ops = fbi->fbops; | |
| union { | |
| struct omapfb_update_window update_window; | |
| struct omapfb_plane_info plane_info; | |
| struct omapfb_mem_info mem_info; | |
| struct omapfb_color_key color_key; | |
| enum omapfb_update_mode update_mode; | |
| struct omapfb_caps caps; | |
| unsigned int mirror; | |
| int plane_out; | |
| int enable_plane; | |
| } p; | |
| int r = 0; | |
| BUG_ON(!ops); | |
| switch (cmd) { | |
| case OMAPFB_MIRROR: | |
| if (get_user(p.mirror, (int __user *)arg)) | |
| r = -EFAULT; | |
| else | |
| omapfb_mirror(fbi, p.mirror); | |
| break; | |
| case OMAPFB_SYNC_GFX: | |
| omapfb_sync(fbi); | |
| break; | |
| case OMAPFB_VSYNC: | |
| break; | |
| case OMAPFB_SET_UPDATE_MODE: | |
| if (get_user(p.update_mode, (int __user *)arg)) | |
| r = -EFAULT; | |
| else | |
| r = omapfb_set_update_mode(fbdev, p.update_mode); | |
| break; | |
| case OMAPFB_GET_UPDATE_MODE: | |
| p.update_mode = omapfb_get_update_mode(fbdev); | |
| if (put_user(p.update_mode, | |
| (enum omapfb_update_mode __user *)arg)) | |
| r = -EFAULT; | |
| break; | |
| case OMAPFB_UPDATE_WINDOW_OLD: | |
| if (copy_from_user(&p.update_window, (void __user *)arg, | |
| sizeof(struct omapfb_update_window_old))) | |
| r = -EFAULT; | |
| else { | |
| struct omapfb_update_window *u = &p.update_window; | |
| u->out_x = u->x; | |
| u->out_y = u->y; | |
| u->out_width = u->width; | |
| u->out_height = u->height; | |
| memset(u->reserved, 0, sizeof(u->reserved)); | |
| r = omapfb_update_win(fbi, u); | |
| } | |
| break; | |
| case OMAPFB_UPDATE_WINDOW: | |
| if (copy_from_user(&p.update_window, (void __user *)arg, | |
| sizeof(p.update_window))) | |
| r = -EFAULT; | |
| else | |
| r = omapfb_update_win(fbi, &p.update_window); | |
| break; | |
| case OMAPFB_SETUP_PLANE: | |
| if (copy_from_user(&p.plane_info, (void __user *)arg, | |
| sizeof(p.plane_info))) | |
| r = -EFAULT; | |
| else | |
| r = omapfb_setup_plane(fbi, &p.plane_info); | |
| break; | |
| case OMAPFB_QUERY_PLANE: | |
| if ((r = omapfb_query_plane(fbi, &p.plane_info)) < 0) | |
| break; | |
| if (copy_to_user((void __user *)arg, &p.plane_info, | |
| sizeof(p.plane_info))) | |
| r = -EFAULT; | |
| break; | |
| case OMAPFB_SETUP_MEM: | |
| if (copy_from_user(&p.mem_info, (void __user *)arg, | |
| sizeof(p.mem_info))) | |
| r = -EFAULT; | |
| else | |
| r = omapfb_setup_mem(fbi, &p.mem_info); | |
| break; | |
| case OMAPFB_QUERY_MEM: | |
| if ((r = omapfb_query_mem(fbi, &p.mem_info)) < 0) | |
| break; | |
| if (copy_to_user((void __user *)arg, &p.mem_info, | |
| sizeof(p.mem_info))) | |
| r = -EFAULT; | |
| break; | |
| case OMAPFB_SET_COLOR_KEY: | |
| if (copy_from_user(&p.color_key, (void __user *)arg, | |
| sizeof(p.color_key))) | |
| r = -EFAULT; | |
| else | |
| r = omapfb_set_color_key(fbdev, &p.color_key); | |
| break; | |
| case OMAPFB_GET_COLOR_KEY: | |
| if ((r = omapfb_get_color_key(fbdev, &p.color_key)) < 0) | |
| break; | |
| if (copy_to_user((void __user *)arg, &p.color_key, | |
| sizeof(p.color_key))) | |
| r = -EFAULT; | |
| break; | |
| case OMAPFB_GET_CAPS: | |
| omapfb_get_caps(fbdev, plane->idx, &p.caps); | |
| if (copy_to_user((void __user *)arg, &p.caps, sizeof(p.caps))) | |
| r = -EFAULT; | |
| break; | |
| case OMAPFB_LCD_TEST: | |
| { | |
| int test_num; | |
| if (get_user(test_num, (int __user *)arg)) { | |
| r = -EFAULT; | |
| break; | |
| } | |
| if (!fbdev->panel->run_test) { | |
| r = -EINVAL; | |
| break; | |
| } | |
| r = fbdev->panel->run_test(fbdev->panel, test_num); | |
| break; | |
| } | |
| case OMAPFB_CTRL_TEST: | |
| { | |
| int test_num; | |
| if (get_user(test_num, (int __user *)arg)) { | |
| r = -EFAULT; | |
| break; | |
| } | |
| if (!fbdev->ctrl->run_test) { | |
| r = -EINVAL; | |
| break; | |
| } | |
| r = fbdev->ctrl->run_test(test_num); | |
| break; | |
| } | |
| default: | |
| r = -EINVAL; | |
| } | |
| return r; | |
| } | |
| static int omapfb_mmap(struct fb_info *info, struct vm_area_struct *vma) | |
| { | |
| struct omapfb_plane_struct *plane = info->par; | |
| struct omapfb_device *fbdev = plane->fbdev; | |
| int r; | |
| vma->vm_page_prot = pgprot_decrypted(vma->vm_page_prot); | |
| omapfb_rqueue_lock(fbdev); | |
| r = fbdev->ctrl->mmap(info, vma); | |
| omapfb_rqueue_unlock(fbdev); | |
| return r; | |
| } | |
| /* | |
| * Callback table for the frame buffer framework. Some of these pointers | |
| * will be changed according to the current setting of fb_info->accel_flags. | |
| */ | |
| static struct fb_ops omapfb_ops = { | |
| .owner = THIS_MODULE, | |
| FB_DEFAULT_IOMEM_OPS, | |
| .fb_open = omapfb_open, | |
| .fb_release = omapfb_release, | |
| .fb_setcolreg = omapfb_setcolreg, | |
| .fb_setcmap = omapfb_setcmap, | |
| .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, | |
| }; | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * Sysfs interface | |
| * --------------------------------------------------------------------------- | |
| */ | |
| /* omapfbX sysfs entries */ | |
| static ssize_t omapfb_show_caps_num(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| int plane; | |
| size_t size = 0; | |
| struct omapfb_caps caps; | |
| plane = 0; | |
| while (plane < OMAPFB_PLANE_NUM) { | |
| omapfb_get_caps(fbdev, plane, &caps); | |
| size += sysfs_emit_at(buf, size, | |
| "plane#%d %#010x %#010x %#010x\n", | |
| plane, caps.ctrl, caps.plane_color, caps.wnd_color); | |
| plane++; | |
| } | |
| return size; | |
| } | |
| static ssize_t omapfb_show_caps_text(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| int i; | |
| struct omapfb_caps caps; | |
| int plane; | |
| size_t size = 0; | |
| plane = 0; | |
| while (plane < OMAPFB_PLANE_NUM) { | |
| omapfb_get_caps(fbdev, plane, &caps); | |
| size += sysfs_emit_at(buf, size, "plane#%d:\n", plane); | |
| for (i = 0; i < ARRAY_SIZE(ctrl_caps); i++) { | |
| if (ctrl_caps[i].flag & caps.ctrl) | |
| size += sysfs_emit_at(buf, size, | |
| " %s\n", ctrl_caps[i].name); | |
| } | |
| size += sysfs_emit_at(buf, size, " plane colors:\n"); | |
| for (i = 0; i < ARRAY_SIZE(color_caps); i++) { | |
| if (color_caps[i].flag & caps.plane_color) | |
| size += sysfs_emit_at(buf, size, | |
| " %s\n", color_caps[i].name); | |
| } | |
| size += sysfs_emit_at(buf, size, " window colors:\n"); | |
| for (i = 0; i < ARRAY_SIZE(color_caps); i++) { | |
| if (color_caps[i].flag & caps.wnd_color) | |
| size += sysfs_emit_at(buf, size, | |
| " %s\n", color_caps[i].name); | |
| } | |
| plane++; | |
| } | |
| return size; | |
| } | |
| static DEVICE_ATTR(caps_num, 0444, omapfb_show_caps_num, NULL); | |
| static DEVICE_ATTR(caps_text, 0444, omapfb_show_caps_text, NULL); | |
| /* panel sysfs entries */ | |
| static ssize_t omapfb_show_panel_name(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%s\n", fbdev->panel->name); | |
| } | |
| static ssize_t omapfb_show_bklight_level(struct device *dev, | |
| struct device_attribute *attr, | |
| char *buf) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| int r; | |
| if (fbdev->panel->get_bklight_level) { | |
| r = sysfs_emit(buf, "%d\n", | |
| fbdev->panel->get_bklight_level(fbdev->panel)); | |
| } else | |
| r = -ENODEV; | |
| return r; | |
| } | |
| static ssize_t omapfb_store_bklight_level(struct device *dev, | |
| struct device_attribute *attr, | |
| const char *buf, size_t size) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| int r; | |
| if (fbdev->panel->set_bklight_level) { | |
| unsigned int level; | |
| if (sscanf(buf, "%10d", &level) == 1) { | |
| r = fbdev->panel->set_bklight_level(fbdev->panel, | |
| level); | |
| } else | |
| r = -EINVAL; | |
| } else | |
| r = -ENODEV; | |
| return r ? r : size; | |
| } | |
| static ssize_t omapfb_show_bklight_max(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| int r; | |
| if (fbdev->panel->get_bklight_level) { | |
| r = sysfs_emit(buf, "%d\n", | |
| fbdev->panel->get_bklight_max(fbdev->panel)); | |
| } else | |
| r = -ENODEV; | |
| return r; | |
| } | |
| static struct device_attribute dev_attr_panel_name = | |
| __ATTR(name, 0444, omapfb_show_panel_name, NULL); | |
| static DEVICE_ATTR(backlight_level, 0664, | |
| omapfb_show_bklight_level, omapfb_store_bklight_level); | |
| static DEVICE_ATTR(backlight_max, 0444, omapfb_show_bklight_max, NULL); | |
| static struct attribute *panel_attrs[] = { | |
| &dev_attr_panel_name.attr, | |
| &dev_attr_backlight_level.attr, | |
| &dev_attr_backlight_max.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group panel_attr_grp = { | |
| .name = "panel", | |
| .attrs = panel_attrs, | |
| }; | |
| /* ctrl sysfs entries */ | |
| static ssize_t omapfb_show_ctrl_name(struct device *dev, | |
| struct device_attribute *attr, char *buf) | |
| { | |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); | |
| return sysfs_emit(buf, "%s\n", fbdev->ctrl->name); | |
| } | |
| static struct device_attribute dev_attr_ctrl_name = | |
| __ATTR(name, 0444, omapfb_show_ctrl_name, NULL); | |
| static struct attribute *ctrl_attrs[] = { | |
| &dev_attr_ctrl_name.attr, | |
| NULL, | |
| }; | |
| static const struct attribute_group ctrl_attr_grp = { | |
| .name = "ctrl", | |
| .attrs = ctrl_attrs, | |
| }; | |
| static int omapfb_register_sysfs(struct omapfb_device *fbdev) | |
| { | |
| int r; | |
| if ((r = device_create_file(fbdev->dev, &dev_attr_caps_num))) | |
| goto fail0; | |
| if ((r = device_create_file(fbdev->dev, &dev_attr_caps_text))) | |
| goto fail1; | |
| if ((r = sysfs_create_group(&fbdev->dev->kobj, &panel_attr_grp))) | |
| goto fail2; | |
| if ((r = sysfs_create_group(&fbdev->dev->kobj, &ctrl_attr_grp))) | |
| goto fail3; | |
| return 0; | |
| fail3: | |
| sysfs_remove_group(&fbdev->dev->kobj, &panel_attr_grp); | |
| fail2: | |
| device_remove_file(fbdev->dev, &dev_attr_caps_text); | |
| fail1: | |
| device_remove_file(fbdev->dev, &dev_attr_caps_num); | |
| fail0: | |
| dev_err(fbdev->dev, "unable to register sysfs interface\n"); | |
| return r; | |
| } | |
| static void omapfb_unregister_sysfs(struct omapfb_device *fbdev) | |
| { | |
| sysfs_remove_group(&fbdev->dev->kobj, &ctrl_attr_grp); | |
| sysfs_remove_group(&fbdev->dev->kobj, &panel_attr_grp); | |
| device_remove_file(fbdev->dev, &dev_attr_caps_num); | |
| device_remove_file(fbdev->dev, &dev_attr_caps_text); | |
| } | |
| /* | |
| * --------------------------------------------------------------------------- | |
| * LDM callbacks | |
| * --------------------------------------------------------------------------- | |
| */ | |
| /* Initialize system fb_info object and set the default video mode. | |
| * The frame buffer memory already allocated by lcddma_init | |
| */ | |
| static int fbinfo_init(struct omapfb_device *fbdev, struct fb_info *info) | |
| { | |
| struct fb_var_screeninfo *var = &info->var; | |
| struct fb_fix_screeninfo *fix = &info->fix; | |
| int r = 0; | |
| info->fbops = &omapfb_ops; | |
| strscpy(fix->id, MODULE_NAME, sizeof(fix->id)); | |
| info->pseudo_palette = fbdev->pseudo_palette; | |
| var->accel_flags = def_accel ? FB_ACCELF_TEXT : 0; | |
| var->xres = def_vxres; | |
| var->yres = def_vyres; | |
| var->xres_virtual = def_vxres; | |
| var->yres_virtual = def_vyres; | |
| var->rotate = def_rotate; | |
| var->bits_per_pixel = fbdev->panel->bpp; | |
| set_fb_var(info, var); | |
| set_fb_fix(info, 1); | |
| r = fb_alloc_cmap(&info->cmap, 16, 0); | |
| if (r != 0) | |
| dev_err(fbdev->dev, "unable to allocate color map memory\n"); | |
| return r; | |
| } | |
| /* Release the fb_info object */ | |
| static void fbinfo_cleanup(struct omapfb_device *fbdev, struct fb_info *fbi) | |
| { | |
| fb_dealloc_cmap(&fbi->cmap); | |
| } | |
| static void planes_cleanup(struct omapfb_device *fbdev) | |
| { | |
| int i; | |
| for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { | |
| if (fbdev->fb_info[i] == NULL) | |
| break; | |
| fbinfo_cleanup(fbdev, fbdev->fb_info[i]); | |
| framebuffer_release(fbdev->fb_info[i]); | |
| } | |
| } | |
| static int planes_init(struct omapfb_device *fbdev) | |
| { | |
| struct fb_info *fbi; | |
| int i; | |
| int r; | |
| for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { | |
| struct omapfb_plane_struct *plane; | |
| fbi = framebuffer_alloc(sizeof(struct omapfb_plane_struct), | |
| fbdev->dev); | |
| if (fbi == NULL) { | |
| planes_cleanup(fbdev); | |
| return -ENOMEM; | |
| } | |
| plane = fbi->par; | |
| plane->idx = i; | |
| plane->fbdev = fbdev; | |
| plane->info.mirror = def_mirror; | |
| fbdev->fb_info[i] = fbi; | |
| if ((r = fbinfo_init(fbdev, fbi)) < 0) { | |
| framebuffer_release(fbi); | |
| planes_cleanup(fbdev); | |
| return r; | |
| } | |
| plane->info.out_width = fbi->var.xres; | |
| plane->info.out_height = fbi->var.yres; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Free driver resources. Can be called to rollback an aborted initialization | |
| * sequence. | |
| */ | |
| static void omapfb_free_resources(struct omapfb_device *fbdev, int state) | |
| { | |
| int i; | |
| switch (state) { | |
| case OMAPFB_ACTIVE: | |
| for (i = 0; i < fbdev->mem_desc.region_cnt; i++) | |
| unregister_framebuffer(fbdev->fb_info[i]); | |
| fallthrough; | |
| case 7: | |
| omapfb_unregister_sysfs(fbdev); | |
| fallthrough; | |
| case 6: | |
| if (fbdev->panel->disable) | |
| fbdev->panel->disable(fbdev->panel); | |
| fallthrough; | |
| case 5: | |
| omapfb_set_update_mode(fbdev, OMAPFB_UPDATE_DISABLED); | |
| fallthrough; | |
| case 4: | |
| planes_cleanup(fbdev); | |
| fallthrough; | |
| case 3: | |
| ctrl_cleanup(fbdev); | |
| fallthrough; | |
| case 2: | |
| if (fbdev->panel->cleanup) | |
| fbdev->panel->cleanup(fbdev->panel); | |
| fallthrough; | |
| case 1: | |
| dev_set_drvdata(fbdev->dev, NULL); | |
| kfree(fbdev); | |
| break; | |
| case 0: | |
| /* nothing to free */ | |
| break; | |
| default: | |
| BUG(); | |
| } | |
| } | |
| static int omapfb_find_ctrl(struct omapfb_device *fbdev) | |
| { | |
| struct omapfb_platform_data *conf; | |
| char name[17]; | |
| int i; | |
| conf = dev_get_platdata(fbdev->dev); | |
| fbdev->ctrl = NULL; | |
| strscpy(name, conf->lcd.ctrl_name, sizeof(name)); | |
| if (strcmp(name, "internal") == 0) { | |
| fbdev->ctrl = fbdev->int_ctrl; | |
| return 0; | |
| } | |
| for (i = 0; i < ARRAY_SIZE(ctrls); i++) { | |
| dev_dbg(fbdev->dev, "ctrl %s\n", ctrls[i]->name); | |
| if (strcmp(ctrls[i]->name, name) == 0) { | |
| fbdev->ctrl = ctrls[i]; | |
| break; | |
| } | |
| } | |
| if (fbdev->ctrl == NULL) { | |
| dev_dbg(fbdev->dev, "ctrl %s not supported\n", name); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Called by LDM binding to probe and attach a new device. | |
| * Initialization sequence: | |
| * 1. allocate system omapfb_device structure | |
| * 2. select controller type according to platform configuration | |
| * init LCD panel | |
| * 3. init LCD controller and LCD DMA | |
| * 4. init system fb_info structure for all planes | |
| * 5. setup video mode for first plane and enable it | |
| * 6. enable LCD panel | |
| * 7. register sysfs attributes | |
| * OMAPFB_ACTIVE: register system fb_info structure for all planes | |
| */ | |
| static int omapfb_do_probe(struct platform_device *pdev, | |
| struct lcd_panel *panel) | |
| { | |
| struct omapfb_device *fbdev = NULL; | |
| int init_state; | |
| unsigned long phz, hhz, vhz; | |
| unsigned long vram; | |
| int i; | |
| int r = 0; | |
| init_state = 0; | |
| if (pdev->num_resources != 2) { | |
| dev_err(&pdev->dev, "probed for an unknown device\n"); | |
| r = -ENODEV; | |
| goto cleanup; | |
| } | |
| if (dev_get_platdata(&pdev->dev) == NULL) { | |
| dev_err(&pdev->dev, "missing platform data\n"); | |
| r = -ENOENT; | |
| goto cleanup; | |
| } | |
| fbdev = kzalloc_obj(*fbdev); | |
| if (fbdev == NULL) { | |
| dev_err(&pdev->dev, | |
| "unable to allocate memory for device info\n"); | |
| r = -ENOMEM; | |
| goto cleanup; | |
| } | |
| r = platform_get_irq(pdev, 0); | |
| if (r < 0) | |
| goto cleanup; | |
| fbdev->int_irq = r; | |
| r = platform_get_irq(pdev, 1); | |
| if (r < 0) | |
| goto cleanup; | |
| fbdev->ext_irq = r; | |
| init_state++; | |
| fbdev->dev = &pdev->dev; | |
| fbdev->panel = panel; | |
| fbdev->dssdev = &omapdss_device; | |
| platform_set_drvdata(pdev, fbdev); | |
| mutex_init(&fbdev->rqueue_mutex); | |
| fbdev->int_ctrl = &omap1_int_ctrl; | |
| #ifdef CONFIG_FB_OMAP_LCDC_EXTERNAL | |
| fbdev->ext_if = &omap1_ext_if; | |
| #endif | |
| if (omapfb_find_ctrl(fbdev) < 0) { | |
| dev_err(fbdev->dev, | |
| "LCD controller not found, board not supported\n"); | |
| r = -ENODEV; | |
| goto cleanup; | |
| } | |
| if (fbdev->panel->init) { | |
| r = fbdev->panel->init(fbdev->panel, fbdev); | |
| if (r) | |
| goto cleanup; | |
| } | |
| pr_info("omapfb: configured for panel %s\n", fbdev->panel->name); | |
| def_vxres = def_vxres ? def_vxres : fbdev->panel->x_res; | |
| def_vyres = def_vyres ? def_vyres : fbdev->panel->y_res; | |
| init_state++; | |
| r = ctrl_init(fbdev); | |
| if (r) | |
| goto cleanup; | |
| if (fbdev->ctrl->mmap != NULL) | |
| omapfb_ops.fb_mmap = omapfb_mmap; | |
| init_state++; | |
| r = planes_init(fbdev); | |
| if (r) | |
| goto cleanup; | |
| init_state++; | |
| #ifdef CONFIG_FB_OMAP_DMA_TUNE | |
| /* Set DMA priority for EMIFF access to highest */ | |
| omap_set_dma_priority(0, OMAP_DMA_PORT_EMIFF, 15); | |
| #endif | |
| r = ctrl_change_mode(fbdev->fb_info[0]); | |
| if (r) { | |
| dev_err(fbdev->dev, "mode setting failed\n"); | |
| goto cleanup; | |
| } | |
| /* GFX plane is enabled by default */ | |
| r = fbdev->ctrl->enable_plane(OMAPFB_PLANE_GFX, 1); | |
| if (r) | |
| goto cleanup; | |
| omapfb_set_update_mode(fbdev, manual_update ? | |
| OMAPFB_MANUAL_UPDATE : OMAPFB_AUTO_UPDATE); | |
| init_state++; | |
| if (fbdev->panel->enable) { | |
| r = fbdev->panel->enable(fbdev->panel); | |
| if (r) | |
| goto cleanup; | |
| } | |
| init_state++; | |
| r = omapfb_register_sysfs(fbdev); | |
| if (r) | |
| goto cleanup; | |
| init_state++; | |
| vram = 0; | |
| for (i = 0; i < fbdev->mem_desc.region_cnt; i++) { | |
| r = register_framebuffer(fbdev->fb_info[i]); | |
| if (r != 0) { | |
| dev_err(fbdev->dev, | |
| "registering framebuffer %d failed\n", i); | |
| goto cleanup; | |
| } | |
| vram += fbdev->mem_desc.region[i].size; | |
| } | |
| fbdev->state = OMAPFB_ACTIVE; | |
| panel = fbdev->panel; | |
| phz = panel->pixel_clock * 1000; | |
| hhz = phz * 10 / (panel->hfp + panel->x_res + panel->hbp + panel->hsw); | |
| vhz = hhz / (panel->vfp + panel->y_res + panel->vbp + panel->vsw); | |
| omapfb_dev = fbdev; | |
| pr_info("omapfb: Framebuffer initialized. Total vram %lu planes %d\n", | |
| vram, fbdev->mem_desc.region_cnt); | |
| pr_info("omapfb: Pixclock %lu kHz hfreq %lu.%lu kHz " | |
| "vfreq %lu.%lu Hz\n", | |
| phz / 1000, hhz / 10000, hhz % 10, vhz / 10, vhz % 10); | |
| return 0; | |
| cleanup: | |
| omapfb_free_resources(fbdev, init_state); | |
| return r; | |
| } | |
| static int omapfb_probe(struct platform_device *pdev) | |
| { | |
| int r; | |
| BUG_ON(fbdev_pdev != NULL); | |
| r = platform_device_register(&omapdss_device); | |
| if (r) { | |
| dev_err(&pdev->dev, "can't register omapdss device\n"); | |
| return r; | |
| } | |
| /* Delay actual initialization until the LCD is registered */ | |
| fbdev_pdev = pdev; | |
| if (fbdev_panel != NULL) | |
| omapfb_do_probe(fbdev_pdev, fbdev_panel); | |
| return 0; | |
| } | |
| void omapfb_register_panel(struct lcd_panel *panel) | |
| { | |
| BUG_ON(fbdev_panel != NULL); | |
| fbdev_panel = panel; | |
| if (fbdev_pdev != NULL) | |
| omapfb_do_probe(fbdev_pdev, fbdev_panel); | |
| } | |
| EXPORT_SYMBOL_GPL(omapfb_register_panel); | |
| /* Called when the device is being detached from the driver */ | |
| static void omapfb_remove(struct platform_device *pdev) | |
| { | |
| struct omapfb_device *fbdev = platform_get_drvdata(pdev); | |
| enum omapfb_state saved_state = fbdev->state; | |
| /* FIXME: wait till completion of pending events */ | |
| fbdev->state = OMAPFB_DISABLED; | |
| omapfb_free_resources(fbdev, saved_state); | |
| platform_device_unregister(&omapdss_device); | |
| fbdev->dssdev = NULL; | |
| } | |
| /* PM suspend */ | |
| static int omapfb_suspend(struct platform_device *pdev, pm_message_t mesg) | |
| { | |
| struct omapfb_device *fbdev = platform_get_drvdata(pdev); | |
| if (fbdev != NULL) | |
| omapfb_blank(FB_BLANK_POWERDOWN, fbdev->fb_info[0]); | |
| return 0; | |
| } | |
| /* PM resume */ | |
| static int omapfb_resume(struct platform_device *pdev) | |
| { | |
| struct omapfb_device *fbdev = platform_get_drvdata(pdev); | |
| if (fbdev != NULL) | |
| omapfb_blank(FB_BLANK_UNBLANK, fbdev->fb_info[0]); | |
| return 0; | |
| } | |
| static struct platform_driver omapfb_driver = { | |
| .probe = omapfb_probe, | |
| .remove = omapfb_remove, | |
| .suspend = omapfb_suspend, | |
| .resume = omapfb_resume, | |
| .driver = { | |
| .name = MODULE_NAME, | |
| }, | |
| }; | |
| #ifndef MODULE | |
| /* Process kernel command line parameters */ | |
| static int __init omapfb_setup(char *options) | |
| { | |
| char *this_opt = NULL; | |
| int r = 0; | |
| pr_debug("omapfb: options %s\n", options); | |
| if (!options || !*options) | |
| return 0; | |
| while (!r && (this_opt = strsep(&options, ",")) != NULL) { | |
| if (!strncmp(this_opt, "accel", 5)) | |
| def_accel = 1; | |
| else if (!strncmp(this_opt, "vram:", 5)) { | |
| unsigned long long vram; | |
| char *suffix; | |
| vram = memparse(this_opt + 5, &suffix); | |
| switch (suffix[0]) { | |
| case '\0': | |
| break; | |
| default: | |
| pr_debug("omapfb: invalid vram suffix %c\n", | |
| suffix[0]); | |
| r = -1; | |
| } | |
| def_vram[def_vram_cnt++] = vram; | |
| } | |
| else if (!strncmp(this_opt, "vxres:", 6)) | |
| def_vxres = simple_strtoul(this_opt + 6, NULL, 0); | |
| else if (!strncmp(this_opt, "vyres:", 6)) | |
| def_vyres = simple_strtoul(this_opt + 6, NULL, 0); | |
| else if (!strncmp(this_opt, "rotate:", 7)) | |
| def_rotate = (simple_strtoul(this_opt + 7, NULL, 0)); | |
| else if (!strncmp(this_opt, "mirror:", 7)) | |
| def_mirror = (simple_strtoul(this_opt + 7, NULL, 0)); | |
| else if (!strncmp(this_opt, "manual_update", 13)) | |
| manual_update = 1; | |
| else { | |
| pr_debug("omapfb: invalid option\n"); | |
| r = -1; | |
| } | |
| } | |
| return r; | |
| } | |
| #endif | |
| /* Register both the driver and the device */ | |
| static int __init omapfb_init(void) | |
| { | |
| #ifndef MODULE | |
| char *option; | |
| if (fb_get_options("omapfb", &option)) | |
| return -ENODEV; | |
| omapfb_setup(option); | |
| #endif | |
| /* Register the driver with LDM */ | |
| if (platform_driver_register(&omapfb_driver)) { | |
| pr_debug("failed to register omapfb driver\n"); | |
| return -ENODEV; | |
| } | |
| return 0; | |
| } | |
| static void __exit omapfb_cleanup(void) | |
| { | |
| platform_driver_unregister(&omapfb_driver); | |
| } | |
| module_param_named(accel, def_accel, uint, 0664); | |
| module_param_array_named(vram, def_vram, ulong, &def_vram_cnt, 0664); | |
| module_param_named(vxres, def_vxres, long, 0664); | |
| module_param_named(vyres, def_vyres, long, 0664); | |
| module_param_named(rotate, def_rotate, uint, 0664); | |
| module_param_named(mirror, def_mirror, uint, 0664); | |
| module_param_named(manual_update, manual_update, bool, 0664); | |
| module_init(omapfb_init); | |
| module_exit(omapfb_cleanup); | |
| MODULE_DESCRIPTION("TI OMAP framebuffer driver"); | |
| MODULE_AUTHOR("Imre Deak <imre.deak@nokia.com>"); | |
| MODULE_LICENSE("GPL"); |