/* * Copyright (C) 2009 HTC * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Referenced from drivers/video/msm/msm_fb.c, Google Incorporated. * */ #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_HTC_HEADSET_MGR #include #endif #include "include/fb-hdmi.h" #include "include/sil902x.h" #if 1 #define HDMI_DBG(s...) printk("[hdmi/fb]" s) #else #define HDMI_DBG(s...) do {} while (0) #endif struct update_info_t { int left; int top; int eright; /* exclusive */ int ebottom; /* exclusive */ unsigned yoffset; }; struct hdmifb_info { struct fb_info *fb; struct msm_panel_data *panel; struct notifier_block fb_hdmi_event; struct msmfb_callback dma_callback; struct msmfb_callback vsync_callback; struct update_info_t update_info; struct early_suspend earlier_suspend; struct early_suspend early_suspend; spinlock_t update_lock; int xres; int yres; unsigned long state; atomic_t use_count; }; static struct mdp_device *mdp; static struct hdmi_device *hdmi; static unsigned PP[16]; void hdmi_pre_change(struct hdmi_info *hdmi); void hdmi_post_change(struct hdmi_info *info, struct fb_var_screeninfo *var); static int hdmifb_open(struct fb_info *info, int user) { return 0; } static int hdmifb_release(struct fb_info *info, int user) { return 0; } static int hdmifb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) { u32 size; if (mdp->check_output_format(mdp, var->bits_per_pixel)) return -EINVAL; if (hdmi->check_res(hdmi, var)) return -EINVAL; size = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3); if (size > info->fix.smem_len) return -EINVAL; return 0; } static int hdmifb_set_par(struct fb_info *info) { struct fb_var_screeninfo *var = &info->var; struct fb_fix_screeninfo *fix = &info->fix; struct hdmifb_info *hdmi_fb = info->par; struct msm_panel_data *panel = hdmi_fb->panel; struct msm_lcdc_timing *timing; struct hdmi_info *hinfo = container_of(hdmi, struct hdmi_info, hdmi_dev); HDMI_DBG("%s\n", __func__); /* we only support RGB ordering for now */ if (var->bits_per_pixel == 32 || var->bits_per_pixel == 24) { var->red.offset = 0; var->red.length = 8; var->green.offset = 8; var->green.length = 8; var->blue.offset = 16; var->blue.length = 8; } else if (var->bits_per_pixel == 16) { var->red.offset = 11; var->red.length = 5; var->green.offset = 5; var->green.length = 6; var->blue.offset = 0; var->blue.length = 5; } else return -1; HDMI_DBG("set res (%d, %d)\n", var->xres, var->yres); timing = hdmi->set_res(hdmi, var); panel->adjust_timing(panel, timing, var->xres, var->yres); hdmi_post_change(hinfo, var); mdp->set_output_format(mdp, var->bits_per_pixel); hdmi_fb->xres = var->xres; hdmi_fb->yres = var->yres; fix->line_length = var->xres * var->bits_per_pixel / 8; return 0; } /* core update function */ static void hdmifb_pan_update(struct fb_info *info, uint32_t left, uint32_t top, uint32_t eright, uint32_t ebottom, uint32_t yoffset) { struct hdmifb_info *hdmi_fb = info->par; struct msm_panel_data *panel = hdmi_fb->panel; unsigned long irq_flags; /* printk(KERN_DEBUG "%s\n", __func__); */ if ((test_bit(fb_enabled, &hdmi_fb->state) == 0) || (test_bit(hdmi_enabled, &hdmi_fb->state) == 0)) return; spin_lock_irqsave(&hdmi_fb->update_lock, irq_flags); hdmi_fb->update_info.left = left; hdmi_fb->update_info.top = top; hdmi_fb->update_info.eright = eright; hdmi_fb->update_info.ebottom = ebottom; hdmi_fb->update_info.yoffset = yoffset; spin_unlock_irqrestore(&hdmi_fb->update_lock, irq_flags); panel->request_vsync(panel, &hdmi_fb->vsync_callback); } /* fb ops, fb_pan_display */ static int hdmifb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) { /* full update */ hdmifb_pan_update(info, 0, 0, info->var.xres, info->var.yres, var->yoffset); return 0; } static void hdmifb_fillrect(struct fb_info *p, const struct fb_fillrect *rect) { cfb_fillrect(p, rect); hdmifb_pan_update(p, rect->dx, rect->dy, rect->dx + rect->width, rect->dy + rect->height, 0); } static void hdmifb_copyarea(struct fb_info *p, const struct fb_copyarea *area) { cfb_copyarea(p, area); hdmifb_pan_update(p, area->dx, area->dy, area->dx + area->width, area->dy + area->height, 0); } static void hdmifb_imageblit(struct fb_info *p, const struct fb_image *image) { cfb_imageblit(p, image); hdmifb_pan_update(p, image->dx, image->dy, image->dx + image->width, image->dy + image->height, 0); } static int hdmifb_change_mode(struct fb_info *info, unsigned int mode) { struct hdmifb_info *hdmi_fb = info->par; /* printk(KERN_DEBUG "%s mode = %d\n", __func__, mode); */ if (mode) set_bit(hdmi_mode, &hdmi_fb->state); else clear_bit(hdmi_mode, &hdmi_fb->state); return 0; } struct hdmifb_info *_hdmi_fb; int hdmifb_get_mode(void) { return test_bit(hdmi_mode, &_hdmi_fb->state); } bool hdmifb_suspending = false; static int hdmifb_pause(struct fb_info *fb, unsigned int mode) { int ret = 0; struct hdmifb_info *hdmi_fb = fb->par; struct msm_panel_data *panel = hdmi_fb->panel; struct hdmi_info *info = container_of(hdmi, struct hdmi_info, hdmi_dev); pr_info("%s: %d %s\n", __func__, atomic_read(&hdmi_fb->use_count), mode == 1 ? "pause" : "resume"); if (mode == 1) { hdmifb_suspending = false; HDMI_DBG("%s: hdmifb_suspending = false\n", __func__); /* pause */ if (atomic_read(&hdmi_fb->use_count) == 0) goto done; if (atomic_dec_return(&hdmi_fb->use_count) == 0) { hdmi_pre_change(info); ret = panel->blank(panel); clear_bit(hdmi_enabled, &hdmi_fb->state); #ifdef CONFIG_HTC_HEADSET_MGR switch_send_event(BIT_HDMI_AUDIO, 0); #endif } } else if (mode == 0) { /* resume */ if (atomic_inc_return(&hdmi_fb->use_count) == 1) { hdmi_pre_change(info); ret = panel->unblank(panel); /* // set timing again to prevent TV been out of range var = &fb->var; timing = hdmi->set_res(hdmi, var); panel->adjust_timing(panel, timing, var->xres, var->yres); hdmi_post_change(info, var); */ set_bit(hdmi_enabled, &hdmi_fb->state); #ifdef CONFIG_HTC_HEADSET_MGR switch_send_event(BIT_HDMI_AUDIO, 1); #endif } } else ret = -EINVAL; done: return ret; } static int hdmifb_blit(struct fb_info *info, void __user *p) { struct mdp_blit_req req; struct mdp_blit_req_list req_list; int i; int ret; if (copy_from_user(&req_list, p, sizeof(req_list))) return -EFAULT; for (i = 0; i < req_list.count; i++) { struct mdp_blit_req_list *list = (struct mdp_blit_req_list *)p; if (copy_from_user(&req, &list->req[i], sizeof(req))) return -EFAULT; req.flags |= MDP_DITHER; ret = mdp->blit(mdp, info, &req); if (ret) return ret; } return 0; } enum ioctl_cmd_index { CMD_SET_MODE, CMD_GET_MODE, CMD_DISABLE, CMD_ENABLE, CMD_GET_STATE, CMD_BLIT, CMD_CABLE_STAT, CMD_ESTABLISH_TIMING, }; static char *cmd_str[] = { "HDMI_SET_MODE", "HDMI_GET_MODE", "HDMI_DISABLE", "HDMI_ENABLE", "HDMI_GET_STATE", "HDMI_BLIT", "HDMI_CABLE_STAT", "HDMI_ESTABLISH_TIMING", }; static int hdmifb_ioctl(struct fb_info *p, unsigned int cmd, unsigned long arg) { struct hdmifb_info *hdmi_fb = p->par; void __user *argp = (void __user *)arg; unsigned int val; int ret = -EINVAL; struct hdmi_info *hinfo = container_of(hdmi, struct hdmi_info, hdmi_dev); /* if (cmd != HDMI_BLIT) HDMI_DBG("%s, cmd=%d=%s\n", __func__, cmd - HDMI_SET_MODE, cmd_str[cmd-HDMI_SET_MODE]); */ switch (cmd) { case HDMI_SET_MODE: get_user(val, (unsigned __user *) arg); //pr_info("[hdmi] SET_MODE: %d\n", val); ret = hdmifb_change_mode(p, val); break; case HDMI_GET_MODE: /* pr_info("[hdmi] GET_MODE: %d\n", test_bit(hdmi_mode, &hdmi_fb->state)); */ ret = put_user(test_bit(hdmi_mode, &hdmi_fb->state), (unsigned __user *) arg); break; case HDMI_DISABLE: get_user(val, (unsigned __user *) arg); ret = hdmifb_pause(p, 1); break; case HDMI_ENABLE: get_user(val, (unsigned __user *) arg); ret = hdmifb_pause(p, 0); break; case HDMI_GET_STATE: ret = put_user(test_bit(hdmi_enabled, &hdmi_fb->state), (unsigned __user *) arg); break; case HDMI_BLIT: if (test_bit(hdmi_enabled, &hdmi_fb->state)) ret = hdmifb_blit(p, argp); else ret = -EPERM; break; case HDMI_CABLE_STAT: { int connect; ret = hdmi->get_cable_state(hdmi, &connect); ret = put_user(connect, (unsigned __user *) arg); break; } case HDMI_ESTABLISH_TIMING: { u8 tmp[3]; hdmi->get_establish_timing(hdmi, tmp); ret = copy_to_user((unsigned __user *) arg, tmp, 3); if (ret) ret = -EFAULT; break; } case HDMI_GET_EDID: ret = copy_to_user((unsigned __user *) arg, hinfo->edid_buf, 512); break; case HDMI_GET_DISPLAY_INFO: { struct display_info dinfo; u8 *ptr = hinfo->edid_buf; dinfo.visible_width = (((u32)ptr[68] & 0xf0) << 4) | ptr[66]; dinfo.visible_height = (((u32)ptr[68] & 0x0f) << 8) | ptr[67]; dinfo.resolution_width = (((u32)ptr[58] & 0xf0) << 4) | ptr[56]; dinfo.resolution_height = (((u32)ptr[61] & 0xf0) << 4) | ptr[59]; ret = copy_to_user((unsigned __user *) arg, &dinfo, sizeof(dinfo)); break; } default: printk(KERN_ERR "hdmi: unknown cmd, cmd = %d\n", cmd); } return ret; } static struct fb_ops hdmi_fb_ops = { .owner = THIS_MODULE, .fb_open = hdmifb_open, .fb_release = hdmifb_release, .fb_check_var = hdmifb_check_var, .fb_set_par = hdmifb_set_par, .fb_pan_display = hdmifb_pan_display, .fb_fillrect = hdmifb_fillrect, .fb_copyarea = hdmifb_copyarea, .fb_imageblit = hdmifb_imageblit, .fb_ioctl = hdmifb_ioctl, }; #ifdef CONFIG_HAS_EARLYSUSPEND static void hdmifb_suspend(struct early_suspend *h) { struct hdmifb_info *hdmi_fb = container_of(h, struct hdmifb_info, early_suspend); struct msm_panel_data *panel = hdmi_fb->panel; HDMI_DBG("%s, use_count=%d\n", __func__, atomic_read(&hdmi_fb->use_count)); hdmifb_suspending = true; HDMI_DBG("%s: hdmifb_suspending = true\n", __func__); if (atomic_read(&hdmi_fb->use_count) && false == test_bit(hdmi_enabled, &hdmi_fb->state) ) { if (panel->blank) panel->blank(panel); } if (panel->suspend) panel->suspend(panel); clear_bit(hdmi_enabled, &hdmi_fb->state); clear_bit(fb_enabled, &hdmi_fb->state); } static void hdmifb_resume(struct early_suspend *h) { struct hdmifb_info *hdmi_fb = container_of(h, struct hdmifb_info, early_suspend); struct msm_panel_data *panel = hdmi_fb->panel; HDMI_DBG("%s\n", __func__); if (panel->resume) panel->resume(panel); atomic_set(&hdmi_fb->use_count, 0); set_bit(fb_enabled, &hdmi_fb->state); } #endif #define BITS_PER_PIXEL 16 static void setup_fb_info(struct hdmifb_info *hdmi_fb) { struct fb_info *fb_info = hdmi_fb->fb; int r; /* finish setting up the fb_info struct */ strncpy(fb_info->fix.id, "hdmi_fb", 16); fb_info->fix.ypanstep = 1; fb_info->fbops = &hdmi_fb_ops; fb_info->flags = FBINFO_DEFAULT; fb_info->fix.type = FB_TYPE_PACKED_PIXELS; fb_info->fix.visual = FB_VISUAL_TRUECOLOR; fb_info->fix.line_length = hdmi_fb->xres * 2; fb_info->var.xres = hdmi_fb->xres; fb_info->var.yres = hdmi_fb->yres; fb_info->var.width = hdmi_fb->panel->fb_data->width; fb_info->var.height = hdmi_fb->panel->fb_data->height; fb_info->var.xres_virtual = hdmi_fb->xres; fb_info->var.yres_virtual = hdmi_fb->yres * 2; fb_info->var.bits_per_pixel = BITS_PER_PIXEL; fb_info->var.accel_flags = 0; fb_info->var.yoffset = 0; fb_info->var.red.offset = 11; fb_info->var.red.length = 5; fb_info->var.red.msb_right = 0; fb_info->var.green.offset = 5; fb_info->var.green.length = 6; fb_info->var.green.msb_right = 0; fb_info->var.blue.offset = 0; fb_info->var.blue.length = 5; fb_info->var.blue.msb_right = 0; r = fb_alloc_cmap(&fb_info->cmap, 16, 0); fb_info->pseudo_palette = PP; PP[0] = 0; for (r = 1; r < 16; r++) PP[r] = 0xffffffff; } static int setup_fbmem(struct hdmifb_info *hdmi_fb, struct platform_device *pdev) { struct fb_info *fb = hdmi_fb->fb; struct resource *res; unsigned long size = hdmi_fb->xres * hdmi_fb->yres * (BITS_PER_PIXEL >> 3) * 2; unsigned char *fbram; res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) return -EINVAL; /* check the resource is large enough to fit the fb */ if (resource_size(res) < size) { printk(KERN_ERR "allocated resource(%d) is too small(%lu)" "for fb\n", resource_size(res), size); return -ENOMEM; } fb->fix.smem_start = res->start; fb->fix.smem_len = resource_size(res); fbram = ioremap(res->start, resource_size(res)); if (fbram == 0) { printk(KERN_ERR "hdmi_fb: cannot allocate fbram!\n"); return -ENOMEM; } fb->screen_base = fbram; memset(fbram, 0, resource_size(res)); printk(KERN_DEBUG "HDMI FB: 0x%x 0x%x\n", res->start, res->end); return 0; } /* Called from dma interrupt handler, must not sleep */ static void hdmi_handle_dma(struct msmfb_callback *callback) { /* printk(KERN_DEBUG "%s\n", __func__); */ } /* Called from vsync interrupt handler, must not sleep */ static void hdmi_handle_vsync(struct msmfb_callback *callback) { uint32_t x, y, w, h; unsigned yoffset; unsigned addr; unsigned long irq_flags; struct fb_info *mirror_fb = registered_fb[0], *fb_hdmi; struct hdmifb_info *hdmi = container_of(callback, struct hdmifb_info, vsync_callback); struct msm_panel_data *panel = hdmi->panel; spin_lock_irqsave(&hdmi->update_lock, irq_flags); x = hdmi->update_info.left; y = hdmi->update_info.top; w = hdmi->update_info.eright - x; h = hdmi->update_info.ebottom - y; yoffset = hdmi->update_info.yoffset; hdmi->update_info.left = hdmi->xres + 1; hdmi->update_info.top = hdmi->yres + 1; hdmi->update_info.eright = 0; hdmi->update_info.ebottom = 0; if (unlikely(w > hdmi->xres || h > hdmi->yres || w == 0 || h == 0)) { printk(KERN_INFO "invalid update: %d %d %d " "%d\n", x, y, w, h); goto error; } spin_unlock_irqrestore(&hdmi->update_lock, irq_flags); addr = ((hdmi->xres * (yoffset + y) + x) * 2); if (test_bit(hdmi_mode, &hdmi->state) == 0) { mdp->dma(mdp, addr + mirror_fb->fix.smem_start, hdmi->xres * 2, w, h, x, y, &hdmi->dma_callback, panel->interface_type); } else { fb_hdmi = hdmi->fb; mdp->dma(mdp, addr + fb_hdmi->fix.smem_start, hdmi->xres * 2, w, h, x, y, &hdmi->dma_callback, panel->interface_type); } return; error: spin_unlock_irqrestore(&hdmi->update_lock, irq_flags); } static int hdmifb_probe(struct platform_device *pdev) { struct fb_info *info; struct hdmifb_info *hdmi_fb; struct msm_panel_data *panel = pdev->dev.platform_data; int ret; printk(KERN_DEBUG "%s\n", __func__); if (!panel) { pr_err("hdmi_fb_probe: no platform data\n"); return -EINVAL; } if (!panel->fb_data) { pr_err("hdmi_fb_probe: no fb_data\n"); return -EINVAL; } info = framebuffer_alloc(sizeof(struct hdmifb_info), &pdev->dev); if (!info) return -ENOMEM; hdmi_fb = info->par; _hdmi_fb = hdmi_fb; hdmi_fb->fb = info; hdmi_fb->panel = panel; set_bit(hdmi_mode, &hdmi_fb->state); hdmi_fb->dma_callback.func = hdmi_handle_dma; hdmi_fb->vsync_callback.func = hdmi_handle_vsync; hdmi_fb->xres = panel->fb_data->xres; hdmi_fb->yres = panel->fb_data->yres; spin_lock_init(&hdmi_fb->update_lock); ret = setup_fbmem(hdmi_fb, pdev); if (ret) goto error_setup_fbmem; setup_fb_info(hdmi_fb); ret = register_framebuffer(info); if (ret) goto error_register_fb; printk(KERN_INFO "hdmi_fb %d * %d initialed\n", hdmi_fb->xres, hdmi_fb->yres); #ifdef CONFIG_HAS_EARLYSUSPEND hdmi_fb->early_suspend.suspend = hdmifb_suspend; hdmi_fb->early_suspend.resume = hdmifb_resume; hdmi_fb->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; register_early_suspend(&hdmi_fb->early_suspend); #endif /* blank panel explicitly because we turn on clk on initial */ if (panel->blank) panel->blank(panel); set_bit(fb_enabled, &hdmi_fb->state); return 0; error_register_fb: error_setup_fbmem: framebuffer_release(hdmi_fb->fb); printk(KERN_ERR "msm probe fail with %d\n", ret); return ret; } static struct platform_driver hdmi_frame_buffer = { .probe = hdmifb_probe, .driver = {.name = "msm_hdmi"}, }; static int hdmifb_add_mdp_device(struct device *dev, struct class_interface *class_intf) { /* might need locking if mulitple mdp devices */ if (mdp) return 0; mdp = container_of(dev, struct mdp_device, dev); return platform_driver_register(&hdmi_frame_buffer); } static void hdmifb_remove_mdp_device(struct device *dev, struct class_interface *class_intf) { /* might need locking if mulitple mdp devices */ if (dev != &mdp->dev) return; platform_driver_unregister(&hdmi_frame_buffer); mdp = NULL; } static struct class_interface hdmi_fb_interface = { .add_dev = &hdmifb_add_mdp_device, .remove_dev = &hdmifb_remove_mdp_device, }; static int hdmifb_add_hdmi_device(struct device *dev, struct class_interface *class_intf) { dev_dbg(dev, "%s\n", __func__); if (hdmi) return 0; hdmi = container_of(dev, struct hdmi_device, dev); return 0; } static struct class_interface hdmi_interface = { .add_dev = hdmifb_add_hdmi_device, }; static int __init hdmifb_init(void) { int rc; rc = register_mdp_client(&hdmi_fb_interface); if (rc) return rc; rc = register_hdmi_client(&hdmi_interface); if (rc) return rc; return 0; } module_init(hdmifb_init);