/* linux/drivers/video/samsung/s3cfb2.c * * Core file for Samsung Display Controller (FIMD) driver * * Jinsung Yang, Copyright (c) 2009 Samsung Electronics * http://www.samsungsemi.com/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "logo_rgb24.h" #include "s3cfb2.h" static struct s3cfb_global *ctrl; struct s3c_platform_fb *to_fb_plat(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); return (struct s3c_platform_fb *) pdev->dev.platform_data; } #ifndef CONFIG_FRAMEBUFFER_CONSOLE static int s3cfb_draw_logo(struct fb_info *fb) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct fb_fix_screeninfo *fix = &fb->fix; struct fb_var_screeninfo *var = &fb->var; memcpy(ctrl->fb[pdata->default_win]->screen_base, LOGO_RGB24, fix->line_length * var->yres); return 0; } #else int fb_is_primary_device(struct fb_info *fb) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct s3cfb_window *win = fb->par; dev_dbg(ctrl->dev, "[fb%d] checking for primary device\n", win->id); if (win->id == pdata->default_win) return 1; else return 0; } #endif static irqreturn_t s3cfb_irq_frame(int irq, void *dev_id) { s3cfb_clear_interrupt(ctrl); ctrl->wq_count++; wake_up_interruptible(&ctrl->wq); return IRQ_HANDLED; } #ifdef CONFIG_FB_S3C_V2_TRACE_UNDERRUN static irqreturn_t s3cfb_irq_fifo(int irq, void *dev_id) { s3cfb_clear_interrupt(ctrl); return IRQ_HANDLED; } #endif static int s3cfb_enable_window(int id) { struct s3cfb_window *win = ctrl->fb[id]->par; if (s3cfb_window_on(ctrl, id)) { win->enabled = 0; return -EFAULT; } else { win->enabled = 1; return 0; } } static int s3cfb_disable_window(int id) { struct s3cfb_window *win = ctrl->fb[id]->par; if (s3cfb_window_off(ctrl, id)) { win->enabled = 1; return -EFAULT; } else { win->enabled = 0; return 0; } } static int s3cfb_init_global(void) { ctrl->output = OUTPUT_RGB; ctrl->rgb_mode = MODE_RGB_P; ctrl->wq_count = 0; init_waitqueue_head(&ctrl->wq); mutex_init(&ctrl->lock); s3cfb_set_output(ctrl); s3cfb_set_display_mode(ctrl); s3cfb_set_polarity(ctrl); s3cfb_set_timing(ctrl); s3cfb_set_lcd_size(ctrl); return 0; } static int s3cfb_map_video_memory(struct fb_info *fb) { struct fb_fix_screeninfo *fix = &fb->fix; struct s3cfb_window *win = fb->par; if (win->path == DATA_PATH_FIFO) return 0; fb->screen_base = dma_alloc_writecombine(ctrl->dev, PAGE_ALIGN(fix->smem_len), (unsigned int *) &fix->smem_start, GFP_KERNEL); if (!fb->screen_base) return -ENOMEM; else dev_info(ctrl->dev, "[fb%d] dma: 0x%08x, cpu: 0x%08x, size: 0x%08x\n", win->id, (unsigned int) fix->smem_start, (unsigned int) fb->screen_base, fix->smem_len); memset(fb->screen_base, 0, fix->smem_len); return 0; } static int s3cfb_unmap_video_memory(struct fb_info *fb) { struct fb_fix_screeninfo *fix = &fb->fix; struct s3cfb_window *win = fb->par; if (fix->smem_start) { dma_free_writecombine(ctrl->dev, fix->smem_len, fb->screen_base, fix->smem_start); fix->smem_start = 0; fix->smem_len = 0; dev_info(ctrl->dev, "[fb%d] video memory released\n", win->id); } return 0; } static int s3cfb_set_bitfield(struct fb_var_screeninfo *var) { switch (var->bits_per_pixel) { case 16: if (var->transp.length == 1) { var->red.offset = 10; var->red.length = 5; var->green.offset = 5; var->green.length = 5; var->blue.offset = 0; var->blue.length = 5; var->transp.offset = 15; } else if (var->transp.length == 4) { var->red.offset = 8; var->red.length = 4; var->green.offset = 4; var->green.length = 4; var->blue.offset = 0; var->blue.length = 4; var->transp.offset = 12; } else { var->red.offset = 11; var->red.length = 5; var->green.offset = 5; var->green.length = 6; var->blue.offset = 0; var->blue.length = 5; var->transp.offset = 0; } break; case 24: var->red.offset = 16; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 0; var->blue.length = 8; var->transp.offset = 0; var->transp.length = 0; break; case 32: var->red.offset = 16; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 0; var->blue.length = 8; var->transp.offset = 24; break; } return 0; } static int s3cfb_set_alpha_info(struct fb_var_screeninfo *var, struct s3cfb_window *win) { if (var->transp.length > 0) win->alpha.mode = PIXEL_BLENDING; else { win->alpha.mode = PLANE_BLENDING; win->alpha.channel = 0; win->alpha.value = S3CFB_AVALUE(0xf, 0xf, 0xf); } return 0; } static int s3cfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct fb_fix_screeninfo *fix = &fb->fix; struct s3cfb_window *win = fb->par; struct s3cfb_lcd *lcd = ctrl->lcd; dev_dbg(ctrl->dev, "[fb%d] check_var\n", win->id); if (var->bits_per_pixel != 16 && var->bits_per_pixel != 24 && var->bits_per_pixel != 32) { dev_err(ctrl->dev, "invalid bits per pixel\n"); return -EINVAL; } if (var->xres > lcd->width) var->xres = lcd->width; if (var->yres > lcd->height) var->yres = lcd->height; if (var->xres_virtual != var->xres) var->xres_virtual = var->xres; if (var->yres_virtual > var->yres * (fb->fix.ypanstep + 1)) var->yres_virtual = var->yres * (fb->fix.ypanstep + 1); if (var->xoffset != 0) var->xoffset = 0; if (var->yoffset + var->yres > var->yres_virtual) var->yoffset = var->yres_virtual - var->yres; if (win->x + var->xres > lcd->width) win->x = lcd->width - var->xres; if (win->y + var->yres > lcd->height) win->y = lcd->height - var->yres; /* modify the fix info */ if (win->id != pdata->default_win) { fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; fix->smem_len = fix->line_length * var->yres_virtual; } s3cfb_set_bitfield(var); s3cfb_set_alpha_info(var, win); return 0; } static int s3cfb_set_par(struct fb_info *fb) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct s3cfb_window *win = fb->par; dev_dbg(ctrl->dev, "[fb%d] set_par\n", win->id); if ((win->id != pdata->default_win) && !fb->fix.smem_start) s3cfb_map_video_memory(fb); s3cfb_set_window_control(ctrl, win->id); s3cfb_set_window_position(ctrl, win->id); s3cfb_set_window_size(ctrl, win->id); s3cfb_set_buffer_address(ctrl, win->id); s3cfb_set_buffer_size(ctrl, win->id); if (win->id > 0) s3cfb_set_alpha_blending(ctrl, win->id); return 0; } static int s3cfb_blank(int blank_mode, struct fb_info *fb) { struct s3cfb_window *win = fb->par; dev_dbg(ctrl->dev, "change blank mode\n"); switch (blank_mode) { case FB_BLANK_UNBLANK: if (fb->fix.smem_start) s3cfb_enable_window(win->id); else dev_info(ctrl->dev, "[fb%d] no allocated memory for unblank\n", win->id); break; case FB_BLANK_POWERDOWN: s3cfb_disable_window(win->id); break; default: dev_dbg(ctrl->dev, "unsupported blank mode\n"); return -EINVAL; } return 0; } static int s3cfb_pan_display(struct fb_var_screeninfo *var, struct fb_info *fb) { struct s3cfb_window *win = fb->par; if (var->yoffset + var->yres > var->yres_virtual) { dev_err(ctrl->dev, "invalid yoffset value\n"); return -EINVAL; } fb->var.yoffset = var->yoffset; dev_dbg(ctrl->dev, "[fb%d] yoffset for pan display: %d\n", win->id, var->yoffset); s3cfb_set_buffer_address(ctrl, win->id); return 0; } static inline unsigned int __chan_to_field(unsigned int chan, struct fb_bitfield bf) { chan &= 0xffff; chan >>= 16 - bf.length; return chan << bf.offset; } static int s3cfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *fb) { unsigned int *pal = (unsigned int *) fb->pseudo_palette; unsigned int val = 0; if (regno < 16) { /* fake palette of 16 colors */ val |= __chan_to_field(red, fb->var.red); val |= __chan_to_field(green, fb->var.green); val |= __chan_to_field(blue, fb->var.blue); val |= __chan_to_field(transp, fb->var.transp); pal[regno] = val; } return 0; } static int s3cfb_open(struct fb_info *fb, int user) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct s3cfb_window *win = fb->par; int ret = 0; mutex_lock(&ctrl->lock); if (atomic_read(&win->in_use)) { if (win->id == pdata->default_win) { dev_dbg(ctrl->dev, "multiple open for default window\n"); ret = 0; } else { dev_dbg(ctrl->dev, "do not allow multiple open " "for non-default window\n"); ret = -EBUSY; } } else atomic_inc(&win->in_use); mutex_unlock(&ctrl->lock); return ret; } static int s3cfb_release_window(struct fb_info *fb) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct s3cfb_window *win = fb->par; if (win->id != pdata->default_win) { s3cfb_disable_window(win->id); s3cfb_unmap_video_memory(fb); s3cfb_set_buffer_address(ctrl, win->id); } win->x = 0; win->y = 0; return 0; } static int s3cfb_release(struct fb_info *fb, int user) { struct s3cfb_window *win = fb->par; s3cfb_release_window(fb); mutex_lock(&ctrl->lock); atomic_dec(&win->in_use); mutex_unlock(&ctrl->lock); return 0; } static int s3cfb_cursor(struct fb_info *info, struct fb_cursor *cursor) { /* nothing to do for removing cursor */ return 0; } static int s3cfb_wait_for_vsync(void) { int count = ctrl->wq_count; dev_dbg(ctrl->dev, "waiting for VSYNC interrupt\n"); wait_event_interruptible_timeout(ctrl->wq, count != ctrl->wq_count, HZ / 10); dev_dbg(ctrl->dev, "got a VSYNC interrupt\n"); return 0; } static int s3cfb_ioctl(struct fb_info *fb, unsigned int cmd, unsigned long arg) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct fb_var_screeninfo *var = &fb->var; struct s3cfb_window *win = fb->par, *win_temp; struct s3cfb_lcd *lcd = ctrl->lcd; int ret = 0, i; union { struct s3cfb_user_window user_window; struct s3cfb_user_plane_alpha user_alpha; struct s3cfb_user_chroma user_chroma; int vsync; } p; switch (cmd) { case FBIO_WAITFORVSYNC: s3cfb_wait_for_vsync(); break; case S3CFB_WIN_ON: s3cfb_enable_window(win->id); break; case S3CFB_WIN_OFF: s3cfb_disable_window(win->id); break; case S3CFB_WIN_OFF_ALL: for (i = 0; i < pdata->nr_wins; i++) { win_temp = ctrl->fb[i]->par; s3cfb_disable_window(win_temp->id); } break; case S3CFB_WIN_POSITION: if (copy_from_user(&p.user_window, (struct s3cfb_user_window __user *) arg, sizeof(p.user_window))) ret = -EFAULT; else { if (p.user_window.x < 0) p.user_window.x = 0; if (p.user_window.y < 0) p.user_window.y = 0; if (p.user_window.x + var->xres > lcd->width) win->x = lcd->width - var->xres; else win->x = p.user_window.x; if (p.user_window.y + var->yres > lcd->height) win->y = lcd->height - var->yres; else win->y = p.user_window.y; s3cfb_set_window_position(ctrl, win->id); } break; case S3CFB_WIN_SET_PLANE_ALPHA: if (copy_from_user(&p.user_alpha, (struct s3cfb_user_plane_alpha __user *) arg, sizeof(p.user_alpha))) ret = -EFAULT; else { win->alpha.mode = PLANE_BLENDING; win->alpha.channel = p.user_alpha.channel; win->alpha.value = S3CFB_AVALUE(p.user_alpha.red, p.user_alpha.green, p.user_alpha.blue); s3cfb_set_alpha_blending(ctrl, win->id); } break; case S3CFB_WIN_SET_CHROMA: if (copy_from_user(&p.user_chroma, (struct s3cfb_user_chroma __user *) arg, sizeof(p.user_chroma))) ret = -EFAULT; else { win->chroma.enabled = p.user_chroma.enabled; win->chroma.key = S3CFB_CHROMA(p.user_chroma.red, p.user_chroma.green, p.user_chroma.blue); s3cfb_set_chroma_key(ctrl, win->id); } break; case S3CFB_SET_VSYNC_INT: if (get_user(p.vsync, (int __user *) arg)) ret = -EFAULT; else { if (p.vsync) s3cfb_set_global_interrupt(ctrl, 1); s3cfb_set_vsync_interrupt(ctrl, p.vsync); } break; } return ret; } struct fb_ops s3cfb_ops = { .owner = THIS_MODULE, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, .fb_check_var = s3cfb_check_var, .fb_set_par = s3cfb_set_par, .fb_blank = s3cfb_blank, .fb_pan_display = s3cfb_pan_display, .fb_setcolreg = s3cfb_setcolreg, .fb_cursor = s3cfb_cursor, .fb_ioctl = s3cfb_ioctl, .fb_open = s3cfb_open, .fb_release = s3cfb_release, }; /* new function to open fifo */ int s3cfb_open_fifo(int id, int ch, int (*do_priv)(void *), void *param) { struct s3cfb_window *win = ctrl->fb[id]->par; dev_dbg(ctrl->dev, "[fb%d] open fifo\n", win->id); win->path = DATA_PATH_FIFO; win->local_channel = ch; s3cfb_set_vsync_interrupt(ctrl, 1); s3cfb_wait_for_vsync(); s3cfb_set_vsync_interrupt(ctrl, 0); if (do_priv) { if (do_priv(param)) { dev_err(ctrl->dev, "failed to run for private fifo open\n"); s3cfb_enable_window(id); return -EFAULT; } } s3cfb_set_window_control(ctrl, id); s3cfb_enable_window(id); return 0; } /* new function to close fifo */ int s3cfb_close_fifo(int id, int (*do_priv)(void *), void *param, int sleep) { struct s3cfb_window *win = ctrl->fb[id]->par; dev_dbg(ctrl->dev, "[fb%d] close fifo\n", win->id); if (sleep) win->path = DATA_PATH_FIFO; else win->path = DATA_PATH_DMA; s3cfb_set_vsync_interrupt(ctrl, 1); s3cfb_wait_for_vsync(); s3cfb_set_vsync_interrupt(ctrl, 0); s3cfb_display_off(ctrl); s3cfb_check_line_count(ctrl); s3cfb_disable_window(id); if (do_priv) { if (do_priv(param)) { dev_err(ctrl->dev, "failed to run for private fifo close\n"); s3cfb_enable_window(id); s3cfb_display_on(ctrl); return -EFAULT; } } s3cfb_display_on(ctrl); return 0; } /* for backward compatibilities */ void s3cfb_enable_local(int id, int in_yuv, int ch) { struct s3cfb_window *win = ctrl->fb[id]->par; win->path = DATA_PATH_FIFO; win->local_channel = ch; s3cfb_set_vsync_interrupt(ctrl, 1); s3cfb_wait_for_vsync(); s3cfb_set_vsync_interrupt(ctrl, 0); s3cfb_set_window_control(ctrl, id); s3cfb_enable_window(id); } /* for backward compatibilities */ void s3cfb_enable_dma(int id) { struct s3cfb_window *win = ctrl->fb[id]->par; win->path = DATA_PATH_DMA; s3cfb_set_vsync_interrupt(ctrl, 1); s3cfb_wait_for_vsync(); s3cfb_set_vsync_interrupt(ctrl, 0); s3cfb_disable_window(id); s3cfb_display_off(ctrl); s3cfb_set_window_control(ctrl, id); s3cfb_display_on(ctrl); } int s3cfb_direct_ioctl(int id, unsigned int cmd, unsigned long arg) { struct fb_info *fb = ctrl->fb[id]; struct fb_var_screeninfo *var = &fb->var; struct s3cfb_window *win = fb->par; struct s3cfb_lcd *lcd = ctrl->lcd; struct s3cfb_user_window user_win; void *argp = (void *) arg; int ret = 0; switch (cmd) { case FBIO_ALLOC: win->path = (enum s3cfb_data_path_t) argp; break; case FBIOGET_FSCREENINFO: ret = memcpy(argp, &fb->fix, sizeof(fb->fix)) ? 0 : -EFAULT; break; case FBIOGET_VSCREENINFO: ret = memcpy(argp, &fb->var, sizeof(fb->var)) ? 0 : -EFAULT; break; case FBIOPUT_VSCREENINFO: ret = s3cfb_check_var((struct fb_var_screeninfo *) argp, fb); if (ret) { dev_err(ctrl->dev, "invalid vscreeninfo\n"); break; } ret = memcpy(&fb->var, (struct fb_var_screeninfo *) argp, sizeof(fb->var)) ? 0 : -EFAULT; if (ret) { dev_err(ctrl->dev, "failed to put new vscreeninfo\n"); break; } ret = s3cfb_set_par(fb); break; case S3CFB_WIN_POSITION: ret = memcpy(&user_win, (struct s3cfb_user_window __user *) arg, sizeof(user_win)) ? 0 : -EFAULT; if (ret) { dev_err(ctrl->dev, "failed to S3CFB_WIN_POSITION.\n"); break; } if (user_win.x < 0) user_win.x = 0; if (user_win.y < 0) user_win.y = 0; if (user_win.x + var->xres > lcd->width) win->x = lcd->width - var->xres; else win->x = user_win.x; if (user_win.y + var->yres > lcd->height) win->y = lcd->height - var->yres; else win->y = user_win.y; s3cfb_set_window_position(ctrl, win->id); break; case S3CFB_SET_SUSPEND_FIFO: win->suspend_fifo = argp; break; case S3CFB_SET_RESUME_FIFO: win->resume_fifo = argp; break; /* * for FBIO_WAITFORVSYNC */ default: ret = s3cfb_ioctl(fb, cmd, arg); break; } return ret; } EXPORT_SYMBOL(s3cfb_direct_ioctl); static int s3cfb_init_fbinfo(int id) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); struct fb_info *fb = ctrl->fb[id]; struct fb_fix_screeninfo *fix = &fb->fix; struct fb_var_screeninfo *var = &fb->var; struct s3cfb_window *win = fb->par; struct s3cfb_alpha *alpha = &win->alpha; struct s3cfb_lcd *lcd = ctrl->lcd; struct s3cfb_lcd_timing *timing = &lcd->timing; memset(win, 0, sizeof(struct s3cfb_window)); platform_set_drvdata(to_platform_device(ctrl->dev), fb); strcpy(fix->id, S3CFB_NAME); /* fimd specific */ win->id = id; win->path = DATA_PATH_DMA; win->dma_burst = 16; alpha->mode = PLANE_BLENDING; /* fbinfo */ fb->fbops = &s3cfb_ops; fb->flags = FBINFO_FLAG_DEFAULT; fb->pseudo_palette = &win->pseudo_pal; fix->xpanstep = 0; fix->ypanstep = pdata->nr_buffers[id] - 1; fix->type = FB_TYPE_PACKED_PIXELS; fix->accel = FB_ACCEL_NONE; fix->visual = FB_VISUAL_TRUECOLOR; var->xres = lcd->width; var->yres = lcd->height; var->xres_virtual = var->xres; var->yres_virtual = var->yres + (var->yres * fix->ypanstep); var->bits_per_pixel = 32; var->xoffset = 0; var->yoffset = 0; var->width = 0; var->height = 0; var->transp.length = 0; fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; fix->smem_len = fix->line_length * var->yres_virtual; var->nonstd = 0; var->activate = FB_ACTIVATE_NOW; var->vmode = FB_VMODE_NONINTERLACED; var->hsync_len = timing->h_sw; var->vsync_len = timing->v_sw; var->left_margin = timing->h_bp; var->right_margin = timing->h_fp; var->upper_margin = timing->v_bp; var->lower_margin = timing->v_fp; var->pixclock = lcd->freq * (var->left_margin + var->right_margin + var->hsync_len + var->xres) * (var->upper_margin + var->lower_margin + var->vsync_len + var->yres); dev_dbg(ctrl->dev, "pixclock: %d\n", var->pixclock); s3cfb_set_bitfield(var); s3cfb_set_alpha_info(var, win); return 0; } static int s3cfb_alloc_framebuffer(void) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); int ret, i; /* alloc for framebuffers */ ctrl->fb = (struct fb_info **) kmalloc(pdata->nr_wins * sizeof(struct fb_info *), GFP_KERNEL); if (!ctrl->fb) { dev_err(ctrl->dev, "not enough memory\n"); ret = -ENOMEM; goto err_alloc; } /* alloc for each framebuffer */ for (i = 0; i < pdata->nr_wins; i++) { ctrl->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window), ctrl->dev); if (!ctrl->fb[i]) { dev_err(ctrl->dev, "not enough memory\n"); ret = -ENOMEM; goto err_alloc_fb; } ret = s3cfb_init_fbinfo(i); if (ret) { dev_err(ctrl->dev, "failed to allocate memory for fb%d\n", i); ret = -ENOMEM; goto err_alloc_fb; } if (i == pdata->default_win) { if (s3cfb_map_video_memory(ctrl->fb[i])) { dev_err(ctrl->dev, "failed to map video memory " "for default window (%d)\n", i); ret = -ENOMEM; goto err_alloc_fb; } } } return 0; err_alloc_fb: for (i = 0; i < pdata->nr_wins; i++) { if (ctrl->fb[i]) framebuffer_release(ctrl->fb[i]); } kfree(ctrl->fb); err_alloc: return ret; } int s3cfb_register_framebuffer(void) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); int ret, i; for (i = 0; i < pdata->nr_wins; i++) { ret = register_framebuffer(ctrl->fb[i]); if (ret) { dev_err(ctrl->dev, "failed to register framebuffer device\n"); return -EINVAL; } #ifndef CONFIG_FRAMEBUFFER_CONSOLE if (i == pdata->default_win) { s3cfb_check_var(&ctrl->fb[i]->var, ctrl->fb[i]); s3cfb_set_par(ctrl->fb[i]); s3cfb_draw_logo(ctrl->fb[i]); } #endif } return 0; } static int s3cfb_sysfs_show_win_power(struct device *dev, struct device_attribute *attr, char *buf) { struct s3c_platform_fb *pdata = to_fb_plat(dev); struct s3cfb_window *win; char temp[16]; int i; for (i = 0; i < pdata->nr_wins; i++) { win = ctrl->fb[i]->par; sprintf(temp, "[fb%d] %s\n", i, win->enabled ? "on" : "off"); strcat(buf, temp); } return strlen(buf); } static int s3cfb_sysfs_store_win_power(struct device *dev, struct device_attribute *attr, const char *buf, size_t len) { struct s3c_platform_fb *pdata = to_fb_plat(dev); char temp[4] = {0, }; const char *p = buf; int id, to; while (*p != '\0') { if (!isspace(*p)) strncat(temp, p, 1); p++; } if (strlen(temp) != 2) return -EINVAL; id = simple_strtoul(temp, NULL, 10) / 10; to = simple_strtoul(temp, NULL, 10) % 10; if (id < 0 || id > pdata->nr_wins) return -EINVAL; if (to != 0 && to != 1) return -EINVAL; if (to == 0) s3cfb_disable_window(id); else s3cfb_enable_window(id); return len; } static DEVICE_ATTR(win_power, 0644, s3cfb_sysfs_show_win_power, s3cfb_sysfs_store_win_power); static int s3cfb_probe(struct platform_device *pdev) { struct s3c_platform_fb *pdata; struct resource *res; int ret = 0; /* initialzing global structure */ ctrl = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); if (!ctrl) { dev_err(ctrl->dev, "failed to allocate for global fb structure\n"); goto err_global; } ctrl->dev = &pdev->dev; s3cfb_set_lcd_info(ctrl); /* gpio */ pdata = to_fb_plat(&pdev->dev); if (pdata->cfg_gpio) pdata->cfg_gpio(pdev); /* clock */ ctrl->clock = clk_get(&pdev->dev, pdata->clk_name); if (IS_ERR(ctrl->clock)) { dev_err(ctrl->dev, "failed to get fimd clock source\n"); ret = -EINVAL; goto err_clk; } clk_enable(ctrl->clock); /* io memory */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(ctrl->dev, "failed to get io memory region\n"); ret = -EINVAL; goto err_io; } /* request mem region */ res = request_mem_region(res->start, res->end - res->start + 1, pdev->name); if (!res) { dev_err(ctrl->dev, "failed to request io memory region\n"); ret = -EINVAL; goto err_io; } /* ioremap for register block */ ctrl->regs = ioremap(res->start, res->end - res->start + 1); if (!ctrl->regs) { dev_err(ctrl->dev, "failed to remap io region\n"); ret = -EINVAL; goto err_io; } /* irq */ ctrl->irq = platform_get_irq(pdev, 0); if (request_irq(ctrl->irq, s3cfb_irq_frame, IRQF_DISABLED, pdev->name, ctrl)) { dev_err(ctrl->dev, "request_irq failed\n"); ret = -EINVAL; goto err_irq; } #ifdef CONFIG_FB_S3C_V2_TRACE_UNDERRUN if (request_irq(platform_get_irq(pdev, 1), s3cfb_irq_fifo, IRQF_DISABLED, pdev->name, ctrl)) { dev_err(ctrl->dev, "request_irq failed\n"); ret = -EINVAL; goto err_irq; } s3cfb_set_fifo_interrupt(ctrl, 1); dev_info(ctrl->dev, "fifo underrun trace\n"); #endif /* init global */ s3cfb_init_global(); s3cfb_display_on(ctrl); /* panel control */ if (pdata->backlight_on) pdata->backlight_on(pdev); if (pdata->lcd_on) pdata->lcd_on(pdev); if (ctrl->lcd->init_ldi) ctrl->lcd->init_ldi(); /* prepare memory */ if (s3cfb_alloc_framebuffer()) goto err_alloc; if (s3cfb_register_framebuffer()) goto err_alloc; s3cfb_set_clock(ctrl); s3cfb_enable_window(pdata->default_win); ret = device_create_file(&(pdev->dev), &dev_attr_win_power); if (ret < 0) dev_err(ctrl->dev, "failed to add sysfs entries\n"); dev_info(ctrl->dev, "registered successfully\n"); return 0; err_alloc: free_irq(ctrl->irq, ctrl); err_irq: iounmap(ctrl->regs); err_io: clk_disable(ctrl->clock); err_clk: clk_put(ctrl->clock); err_global: return ret; } static int s3cfb_remove(struct platform_device *pdev) { struct s3c_platform_fb *pdata = to_fb_plat(&pdev->dev); struct fb_info *fb; int i; free_irq(ctrl->irq, ctrl); iounmap(ctrl->regs); clk_disable(ctrl->clock); clk_put(ctrl->clock); for (i = 0; i < pdata->nr_wins; i++) { fb = ctrl->fb[i]; /* free if exists */ if (fb) { s3cfb_unmap_video_memory(fb); s3cfb_set_buffer_address(ctrl, i); framebuffer_release(fb); } } kfree(ctrl->fb); kfree(ctrl); return 0; } #ifdef CONFIG_PM int s3cfb_suspend(struct platform_device *pdev, pm_message_t state) { struct s3c_platform_fb *pdata = to_fb_plat(&pdev->dev); struct s3cfb_window *win; int i; for (i = 0; i < pdata->nr_wins; i++) { win = ctrl->fb[i]->par; if (win->path == DATA_PATH_FIFO && win->suspend_fifo) { if (win->suspend_fifo()) dev_info(ctrl->dev, "failed to run the suspend for fifo\n"); } } s3cfb_display_off(ctrl); clk_disable(ctrl->clock); return 0; } int s3cfb_resume(struct platform_device *pdev) { struct s3c_platform_fb *pdata = to_fb_plat(&pdev->dev); struct fb_info *fb; struct s3cfb_window *win; int i; dev_dbg(ctrl->dev, "wake up from suspend\n"); if (pdata->cfg_gpio) pdata->cfg_gpio(pdev); if (pdata->backlight_on) pdata->backlight_on(pdev); if (pdata->lcd_on) pdata->lcd_on(pdev); if (ctrl->lcd->init_ldi) ctrl->lcd->init_ldi(); clk_enable(ctrl->clock); s3cfb_init_global(); s3cfb_set_clock(ctrl); s3cfb_display_on(ctrl); for (i = 0; i < pdata->nr_wins; i++) { fb = ctrl->fb[i]; win = fb->par; if (win->path == DATA_PATH_FIFO && win->resume_fifo) { if (win->resume_fifo()) dev_info(ctrl->dev, "failed to run the resume for fifo\n"); } else { if (win->enabled) { s3cfb_check_var(&fb->var, fb); s3cfb_set_par(fb); s3cfb_enable_window(win->id); } } } return 0; } #else #define s3cfb_suspend NULL #define s3cfb_resume NULL #endif static struct platform_driver s3cfb_driver = { .probe = s3cfb_probe, .remove = s3cfb_remove, .suspend = s3cfb_suspend, .resume = s3cfb_resume, .driver = { .name = S3CFB_NAME, .owner = THIS_MODULE, }, }; static int s3cfb_register(void) { platform_driver_register(&s3cfb_driver); return 0; } static void s3cfb_unregister(void) { platform_driver_unregister(&s3cfb_driver); } module_init(s3cfb_register); module_exit(s3cfb_unregister); MODULE_AUTHOR("Jinsung, Yang "); MODULE_DESCRIPTION("Samsung Display Controller (FIMD) driver"); MODULE_LICENSE("GPL");