summaryrefslogtreecommitdiff
path: root/drivers/video/tegra/fb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/video/tegra/fb.c')
-rw-r--r--drivers/video/tegra/fb.c343
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, &param_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: