| // 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/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) { |
| if (size) { |
| memcpy(new_var, &fbi->var, sizeof(*new_var)); |
| r = set_fb_var(fbi, new_var); |
| if (r < 0) |
| goto out; |
| } |
| } |
| |
| if (fbdev->ctrl->sync) |
| fbdev->ctrl->sync(); |
| r = fbdev->ctrl->setup_mem(plane->idx, size, mi->type, &paddr); |
| if (r < 0) { |
| /* Revert changes. */ |
| rg->size = old_size; |
| rg->type = old_type; |
| goto out; |
| } |
| rg->paddr = paddr; |
| |
| if (old_size != size) { |
| if (size) { |
| memcpy(&fbi->var, new_var, sizeof(fbi->var)); |
| set_fb_fix(fbi, 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; |
| |
| omapfb_rqueue_lock(fbdev); |
| r = fbdev->ctrl->mmap(info, vma); |
| omapfb_rqueue_unlock(fbdev); |
| |
| return r; |
| } |
| |
| /* |
| * Callback table for the frame buffer framework. Some of these pointers |
| * will be changed according to the current setting of fb_info->accel_flags. |
| */ |
| static struct fb_ops omapfb_ops = { |
| .owner = THIS_MODULE, |
| .fb_open = omapfb_open, |
| .fb_release = omapfb_release, |
| .fb_setcolreg = omapfb_setcolreg, |
| .fb_setcmap = omapfb_setcmap, |
| .fb_fillrect = cfb_fillrect, |
| .fb_copyarea = cfb_copyarea, |
| .fb_imageblit = cfb_imageblit, |
| .fb_blank = omapfb_blank, |
| .fb_ioctl = omapfb_ioctl, |
| .fb_check_var = omapfb_check_var, |
| .fb_set_par = omapfb_set_par, |
| .fb_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; |
| struct omapfb_caps caps; |
| |
| plane = 0; |
| size = 0; |
| while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) { |
| omapfb_get_caps(fbdev, plane, &caps); |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| "plane#%d %#010x %#010x %#010x\n", |
| plane, caps.ctrl, caps.plane_color, caps.wnd_color); |
| plane++; |
| } |
| return size; |
| } |
| |
| static ssize_t omapfb_show_caps_text(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct omapfb_device *fbdev = dev_get_drvdata(dev); |
| int i; |
| struct omapfb_caps caps; |
| int plane; |
| size_t size; |
| |
| plane = 0; |
| size = 0; |
| while (size < PAGE_SIZE && plane < OMAPFB_PLANE_NUM) { |
| omapfb_get_caps(fbdev, plane, &caps); |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| "plane#%d:\n", plane); |
| for (i = 0; i < ARRAY_SIZE(ctrl_caps) && |
| size < PAGE_SIZE; i++) { |
| if (ctrl_caps[i].flag & caps.ctrl) |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| " %s\n", ctrl_caps[i].name); |
| } |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| " plane colors:\n"); |
| for (i = 0; i < ARRAY_SIZE(color_caps) && |
| size < PAGE_SIZE; i++) { |
| if (color_caps[i].flag & caps.plane_color) |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| " %s\n", color_caps[i].name); |
| } |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| " window colors:\n"); |
| for (i = 0; i < ARRAY_SIZE(color_caps) && |
| size < PAGE_SIZE; i++) { |
| if (color_caps[i].flag & caps.wnd_color) |
| size += scnprintf(&buf[size], PAGE_SIZE - size, |
| " %s\n", color_caps[i].name); |
| } |
| |
| plane++; |
| } |
| return size; |
| } |
| |
| static DEVICE_ATTR(caps_num, 0444, omapfb_show_caps_num, NULL); |
| static DEVICE_ATTR(caps_text, 0444, omapfb_show_caps_text, NULL); |
| |
| /* panel sysfs entries */ |
| static ssize_t omapfb_show_panel_name(struct device *dev, |
| struct device_attribute *attr, char *buf) |
| { |
| struct omapfb_device *fbdev = 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; |
| info->flags = FBINFO_FLAG_DEFAULT; |
| |
| 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(sizeof(*fbdev), GFP_KERNEL); |
| if (fbdev == NULL) { |
| dev_err(&pdev->dev, |
| "unable to allocate memory for device info\n"); |
| r = -ENOMEM; |
| goto cleanup; |
| } |
| fbdev->int_irq = platform_get_irq(pdev, 0); |
| if (fbdev->int_irq < 0) { |
| r = ENXIO; |
| goto cleanup; |
| } |
| |
| fbdev->ext_irq = platform_get_irq(pdev, 1); |
| if (fbdev->ext_irq < 0) { |
| r = ENXIO; |
| goto cleanup; |
| } |
| |
| 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_new = 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)) { |
| char *suffix; |
| unsigned long vram; |
| vram = (simple_strtoul(this_opt + 5, &suffix, 0)); |
| switch (suffix[0]) { |
| case '\0': |
| break; |
| case 'm': |
| case 'M': |
| vram *= 1024; |
| fallthrough; |
| case 'k': |
| case 'K': |
| vram *= 1024; |
| break; |
| default: |
| pr_debug("omapfb: invalid vram suffix %c\n", |
| suffix[0]); |
| r = -1; |
| } |
| def_vram[def_vram_cnt++] = vram; |
| } |
| else if (!strncmp(this_opt, "vxres:", 6)) |
| def_vxres = simple_strtoul(this_opt + 6, NULL, 0); |
| else if (!strncmp(this_opt, "vyres:", 6)) |
| def_vyres = simple_strtoul(this_opt + 6, NULL, 0); |
| else if (!strncmp(this_opt, "rotate:", 7)) |
| def_rotate = (simple_strtoul(this_opt + 7, NULL, 0)); |
| else if (!strncmp(this_opt, "mirror:", 7)) |
| def_mirror = (simple_strtoul(this_opt + 7, NULL, 0)); |
| else if (!strncmp(this_opt, "manual_update", 13)) |
| manual_update = 1; |
| else { |
| pr_debug("omapfb: invalid option\n"); |
| r = -1; |
| } |
| } |
| |
| return r; |
| } |
| |
| #endif |
| |
| /* Register both the driver and the device */ |
| static int __init omapfb_init(void) |
| { |
| #ifndef MODULE |
| char *option; |
| |
| if (fb_get_options("omapfb", &option)) |
| return -ENODEV; |
| omapfb_setup(option); |
| #endif |
| /* Register the driver with LDM */ |
| if (platform_driver_register(&omapfb_driver)) { |
| pr_debug("failed to register omapfb driver\n"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static void __exit omapfb_cleanup(void) |
| { |
| platform_driver_unregister(&omapfb_driver); |
| } |
| |
| module_param_named(accel, def_accel, uint, 0664); |
| module_param_array_named(vram, def_vram, ulong, &def_vram_cnt, 0664); |
| module_param_named(vxres, def_vxres, long, 0664); |
| module_param_named(vyres, def_vyres, long, 0664); |
| module_param_named(rotate, def_rotate, uint, 0664); |
| module_param_named(mirror, def_mirror, uint, 0664); |
| module_param_named(manual_update, manual_update, bool, 0664); |
| |
| module_init(omapfb_init); |
| module_exit(omapfb_cleanup); |
| |
| MODULE_DESCRIPTION("TI OMAP framebuffer driver"); |
| MODULE_AUTHOR("Imre Deak <imre.deak@nokia.com>"); |
| MODULE_LICENSE("GPL"); |