diff options
Diffstat (limited to 'drivers/video/tegra/fb.c')
-rw-r--r-- | drivers/video/tegra/fb.c | 343 |
1 files changed, 221 insertions, 122 deletions
diff --git a/drivers/video/tegra/fb.c b/drivers/video/tegra/fb.c index f69048f62cc2..b7aa066751fc 100644 --- a/drivers/video/tegra/fb.c +++ b/drivers/video/tegra/fb.c @@ -55,9 +55,6 @@ struct tegra_fb_info { bool valid; struct resource *fb_mem; - - int xres; - int yres; }; /* palette array used by the fbcon */ @@ -75,10 +72,18 @@ static int tegra_fb_check_var(struct fb_var_screeninfo *var, info->screen_size) return -EINVAL; + fb_var_to_videomode(&mode, var); + +#if defined(CONFIG_MACH_APALIS_T30) || defined(CONFIG_MACH_COLIBRI_T20) || \ +defined(CONFIG_MACH_COLIBRI_T30) + /* Hack: avoid 24 Hz mode in X resulting in no display at all */ + if (mode.refresh < 50) + return -EINVAL; +#endif /* CONFIG_MACH_APALIS_T30 | CONFIG_MACH_COLIBRI_T20 | + CONFIG_MACH_COLIBRI_T30 */ + /* Apply mode filter for HDMI only -LVDS supports only fix mode */ if (ops && ops->mode_filter) { - - fb_var_to_videomode(&mode, var); if (!ops->mode_filter(dc, &mode)) return -EINVAL; @@ -87,8 +92,35 @@ static int tegra_fb_check_var(struct fb_var_screeninfo *var, } /* Double yres_virtual to allow double buffering through pan_display */ + var->xres_virtual = var->xres; var->yres_virtual = var->yres * 2; + /* we only support RGB ordering for now */ + switch (var->bits_per_pixel) { + case 32: + case 24: + var->bits_per_pixel = 32; + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + case 16: + default: + 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; + break; + } + return 0; } @@ -97,103 +129,61 @@ static int tegra_fb_set_par(struct fb_info *info) struct tegra_fb_info *tegra_fb = info->par; struct fb_var_screeninfo *var = &info->var; struct tegra_dc *dc = tegra_fb->win->dc; + int err; - if (var->bits_per_pixel) { - /* we only support RGB ordering for now */ - switch (var->bits_per_pixel) { - case 32: - var->red.offset = 0; - var->red.length = 8; - var->green.offset = 8; - var->green.length = 8; - var->blue.offset = 16; - var->blue.length = 8; - var->transp.offset = 24; - var->transp.length = 8; - tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8; - break; - case 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; - tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5; - break; - - default: - return -EINVAL; - } - /* if line_length unset, then pad the stride */ - info->fix.line_length = var->xres * var->bits_per_pixel / 8; - info->fix.line_length = round_up(info->fix.line_length, - TEGRA_LINEAR_PITCH_ALIGNMENT); - tegra_fb->win->stride = info->fix.line_length; - tegra_fb->win->stride_uv = 0; - tegra_fb->win->phys_addr_u = 0; - tegra_fb->win->phys_addr_v = 0; - } - - if (var->pixclock) { - bool stereo; - unsigned old_len = 0; - struct fb_videomode m; - struct fb_videomode *old_mode = NULL; - - fb_var_to_videomode(&m, var); + struct tegra_dc_mode mode; - /* Load framebuffer info with new mode details*/ - old_mode = info->mode; - old_len = info->fix.line_length; - - info->mode = (struct fb_videomode *) - fb_find_nearest_mode(&m, &info->modelist); - if (!info->mode) { - dev_warn(&tegra_fb->ndev->dev, "can't match video mode\n"); - info->mode = old_mode; - return -EINVAL; - } + /* This is usually altered to 16/32 by tegra_fb_check_var + * above which is called before this function + */ + switch (var->bits_per_pixel) { + case 32: + tegra_fb->win->fmt = TEGRA_WIN_FMT_R8G8B8A8; + break; + case 16: + tegra_fb->win->fmt = TEGRA_WIN_FMT_B5G6R5; + break; + default: + return -EINVAL; + break; + } - /* Update fix line_length and window stride as per new mode */ - info->fix.line_length = var->xres * var->bits_per_pixel / 8; - info->fix.line_length = round_up(info->fix.line_length, - TEGRA_LINEAR_PITCH_ALIGNMENT); - tegra_fb->win->stride = info->fix.line_length; + /* if line_length unset, then pad the stride */ + info->fix.line_length = var->xres * var->bits_per_pixel / 8; + info->fix.line_length = round_up(info->fix.line_length, + TEGRA_LINEAR_PITCH_ALIGNMENT); + tegra_fb->win->stride = info->fix.line_length; + tegra_fb->win->stride_uv = 0; + tegra_fb->win->phys_addr_u = 0; + tegra_fb->win->phys_addr_v = 0; + + tegra_fb->win->w.full = dfixed_const(var->xres); + tegra_fb->win->h.full = dfixed_const(var->yres); + tegra_fb->win->out_w = var->xres; + tegra_fb->win->out_h = var->yres; + + dev_info(&tegra_fb->ndev->dev, "switching framebuffer to %dx%d\n", + var->xres, var->yres); + + err = tegra_dc_var_to_dc_mode(dc, var, &mode); + if (err) { + dev_warn(&tegra_fb->ndev->dev, "could not convert var %d\n", err); + return -EINVAL; + } - /* - * only enable stereo if the mode supports it and - * client requests it - */ - stereo = !!(var->vmode & info->mode->vmode & -#ifndef CONFIG_TEGRA_HDMI_74MHZ_LIMIT - FB_VMODE_STEREO_FRAME_PACK); -#else - FB_VMODE_STEREO_LEFT_RIGHT); -#endif + err = tegra_dc_set_mode(dc, &mode); + if (err) { + dev_warn(&tegra_fb->ndev->dev, "could not set dc mode %d\n", err); + return -EINVAL; + } - /* Configure DC with new mode */ - if (tegra_dc_set_fb_mode(dc, info->mode, stereo)) { - /* Error while configuring DC, fallback to old mode */ - dev_warn(&tegra_fb->ndev->dev, "can't configure dc with mode %ux%u\n", - info->mode->xres, info->mode->yres); - info->mode = old_mode; - info->fix.line_length = old_len; - tegra_fb->win->stride = old_len; - return -EINVAL; - } + /* Reflect changes on HW */ + if (dc->enabled) + tegra_dc_disable(dc); + tegra_dc_enable(dc); - /* Reflect mode chnage on DC HW */ - if (dc->enabled) - tegra_dc_disable(dc); - tegra_dc_enable(dc); + return err; - tegra_fb->win->w.full = dfixed_const(info->mode->xres); - tegra_fb->win->h.full = dfixed_const(info->mode->yres); - tegra_fb->win->out_w = info->mode->xres; - tegra_fb->win->out_h = info->mode->yres; - } - return 0; } static int tegra_fb_setcolreg(unsigned regno, unsigned red, unsigned green, @@ -417,6 +407,9 @@ static int tegra_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long } int tegra_fb_get_mode(struct tegra_dc *dc) { + /* Avoid error when reading sysfs */ + if (dc->fb->info->mode == NULL) + return 0; return dc->fb->info->mode->refresh; } @@ -532,13 +525,11 @@ void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info, /* Prepare a mode db */ for (i = 0; i < specs->modedb_len; i++) { if (info->fbops->fb_check_var) { - struct fb_videomode m; - /* Call mode filter to check mode */ fb_videomode_to_var(&var, &specs->modedb[i]); if (!(info->fbops->fb_check_var(&var, info))) { - fb_var_to_videomode(&m, &var); - fb_add_videomode(&m, + fb_var_to_videomode(&specs->modedb[i], &var); + fb_add_videomode(&specs->modedb[i], &fb_info->info->modelist); /* EDID stds recommend first detailed mode to be applied as default,but if first mode @@ -584,6 +575,106 @@ void tegra_fb_update_monspecs(struct tegra_fb_info *fb_info, mutex_unlock(&fb_info->info->lock); } +struct tegra_dc_out_pin dc_out_pins[4]; + + +static int parse_opt(struct tegra_dc_out *out, char *this_opt) +{ + if (!strncmp(this_opt, "hsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } else if (!strncmp(this_opt, "vsync:", 6)) { + if (simple_strtoul(this_opt+6, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } else if (!strncmp(this_opt, "outputen:", 9)) { + if (simple_strtoul(this_opt+9, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } else if (!strncmp(this_opt, "pixclockpol:", 12)) { + if (simple_strtoul(this_opt+12, NULL, 0) == 0) { + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].pol = + TEGRA_DC_OUT_PIN_POL_LOW; + } else { + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].pol = + TEGRA_DC_OUT_PIN_POL_HIGH; + } + return 0; + } + + return -1; +} + +static void tegra_dc_copy_pin_modes(struct tegra_dc_out *out) +{ + int i; + struct tegra_dc_out_pin *def = out->out_pins; + int n_out_pins_default = out->n_out_pins; + + /* Allocate memory for dynamic output pin configuration... */ + out->n_out_pins = 4; + out->out_pins = kmalloc(sizeof(struct tegra_dc_out_pin) * out->n_out_pins, + GFP_KERNEL); + + /* ...set fallback values, we use the pin enum as array index... */ + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].name = TEGRA_DC_OUT_PIN_DATA_ENABLE; + out->out_pins[TEGRA_DC_OUT_PIN_DATA_ENABLE].pol = TEGRA_DC_OUT_PIN_POL_HIGH; + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].name = TEGRA_DC_OUT_PIN_H_SYNC; + out->out_pins[TEGRA_DC_OUT_PIN_H_SYNC].pol = TEGRA_DC_OUT_PIN_POL_LOW; + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].name = TEGRA_DC_OUT_PIN_V_SYNC; + out->out_pins[TEGRA_DC_OUT_PIN_V_SYNC].pol = TEGRA_DC_OUT_PIN_POL_LOW; + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].name = TEGRA_DC_OUT_PIN_PIXEL_CLOCK; + out->out_pins[TEGRA_DC_OUT_PIN_PIXEL_CLOCK].pol = TEGRA_DC_OUT_PIN_POL_LOW; + + /* ... and copy the static default config from platform data */ + for (i = 0; i < n_out_pins_default; i++) + out->out_pins[def[i].name].pol = def[i].pol; +} + +static int tegra_parse_options(struct tegra_dc_out *out, struct fb_info *info, + char *option) +{ + char *this_opt; + + /* This off option works perfectly for framebuffer + * device, however the tegra binary driver somehow + * has troubles to handle a missing fb0 + * (then, dc1 gets remapped to fb0, which seems + * to be an issue for the binary driver)... + */ + if (!strcmp(option, "off")) + return -ENODEV; + + while ((this_opt = strsep(&option, ",")) != NULL) { + /* Parse driver specific arguments for RGB output */ + if (out->type == TEGRA_DC_OUT_RGB) { + if (parse_opt(out, this_opt) == 0) + continue; + } + + /* No valid driver specific argument, has to be mode */ + if (!tegra_fb_find_mode(&info->var, info, this_opt, 16)) + return -EINVAL; + } + return 0; +} + struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, struct tegra_dc *dc, struct tegra_fb_data *fb_data, @@ -597,6 +688,9 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, unsigned long fb_phys = 0; int ret = 0; unsigned stride; + char *param_option = NULL; + char *option = NULL; + char driver[10]; win = tegra_dc_get_window(dc, fb_data->win); if (!win) { @@ -615,8 +709,6 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, tegra_fb->win = win; tegra_fb->ndev = ndev; tegra_fb->fb_mem = fb_mem; - tegra_fb->xres = fb_data->xres; - tegra_fb->yres = fb_data->yres; if (fb_mem) { fb_size = resource_size(fb_mem); @@ -653,6 +745,7 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, info->var.xres_virtual = fb_data->xres; info->var.yres_virtual = fb_data->yres * 2; info->var.bits_per_pixel = fb_data->bits_per_pixel; + info->var.activate = FB_ACTIVATE_VBL; info->var.height = tegra_dc_get_out_height(dc); info->var.width = tegra_dc_get_out_width(dc); @@ -665,11 +758,11 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, info->var.vsync_len = 0; info->var.vmode = FB_VMODE_NONINTERLACED; + /* window settings */ win->x.full = dfixed_const(0); win->y.full = dfixed_const(0); win->w.full = dfixed_const(fb_data->xres); win->h.full = dfixed_const(fb_data->yres); - /* TODO: set to output res dc */ win->out_x = 0; win->out_y = 0; win->out_w = fb_data->xres; @@ -683,6 +776,32 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, win->stride_uv = 0; win->flags = TEGRA_WIN_FLAG_ENABLED; + /* Set/copy default pin modes, if output is RGB... */ + if (dc->out->type == TEGRA_DC_OUT_RGB) + tegra_dc_copy_pin_modes(dc->out); + + /* try to use kernel cmd line specified mode */ + sprintf(driver, "tegrafb%d", ndev->id); + fb_get_options(driver, ¶m_option); + if (param_option != NULL) { + option = param_option; + dev_info(&ndev->dev, "use cmd options for %s: %s\n", + driver, option); + } else { + option = dc->out->default_mode; + dev_info(&ndev->dev, "use default mode for %s: %s\n", + driver, option); + } + + if (option != NULL) { + ret = tegra_parse_options(dc->out, info, option); + if (ret < 0) + goto err_iounmap_fb; + } + + /* Activate current settings (tegra_fb_find_mode has call + * tegra_fb_check_var already) + */ if (fb_mem) tegra_fb_set_par(info); @@ -701,26 +820,6 @@ struct tegra_fb_info *tegra_fb_register(struct nvhost_device *ndev, tegra_dc_sync_windows(&tegra_fb->win, 1); } - if (dc->mode.pclk > 1000) { - struct tegra_dc_mode *mode = &dc->mode; - struct fb_videomode vmode; - - if (dc->out->flags & TEGRA_DC_OUT_ONE_SHOT_MODE) - info->var.pixclock = KHZ2PICOS(mode->rated_pclk / 1000); - else - info->var.pixclock = KHZ2PICOS(mode->pclk / 1000); - info->var.left_margin = mode->h_back_porch; - info->var.right_margin = mode->h_front_porch; - info->var.upper_margin = mode->v_back_porch; - info->var.lower_margin = mode->v_front_porch; - info->var.hsync_len = mode->h_sync_width; - info->var.vsync_len = mode->v_sync_width; - - /* Keep info->var consistent with info->modelist. */ - fb_var_to_videomode(&vmode, &info->var); - fb_add_videomode(&vmode, &info->modelist); - } - return tegra_fb; err_iounmap_fb: |