| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright © 2009 Intel Corporation |
| */ |
| |
| #include <linux/delay.h> |
| #include <linux/i2c.h> |
| #include <linux/pm_runtime.h> |
| |
| #include <drm/drm_fourcc.h> |
| #include <drm/drm_framebuffer.h> |
| #include <drm/drm_modeset_helper_vtables.h> |
| |
| #include "framebuffer.h" |
| #include "gem.h" |
| #include "gma_display.h" |
| #include "power.h" |
| #include "psb_drv.h" |
| #include "psb_intel_drv.h" |
| #include "psb_intel_reg.h" |
| |
| #define MRST_LIMIT_LVDS_100L 0 |
| #define MRST_LIMIT_LVDS_83 1 |
| #define MRST_LIMIT_LVDS_100 2 |
| #define MRST_LIMIT_SDVO 3 |
| |
| #define MRST_DOT_MIN 19750 |
| #define MRST_DOT_MAX 120000 |
| #define MRST_M_MIN_100L 20 |
| #define MRST_M_MIN_100 10 |
| #define MRST_M_MIN_83 12 |
| #define MRST_M_MAX_100L 34 |
| #define MRST_M_MAX_100 17 |
| #define MRST_M_MAX_83 20 |
| #define MRST_P1_MIN 2 |
| #define MRST_P1_MAX_0 7 |
| #define MRST_P1_MAX_1 8 |
| |
| static bool mrst_lvds_find_best_pll(const struct gma_limit_t *limit, |
| struct drm_crtc *crtc, int target, |
| int refclk, struct gma_clock_t *best_clock); |
| |
| static bool mrst_sdvo_find_best_pll(const struct gma_limit_t *limit, |
| struct drm_crtc *crtc, int target, |
| int refclk, struct gma_clock_t *best_clock); |
| |
| static const struct gma_limit_t mrst_limits[] = { |
| { /* MRST_LIMIT_LVDS_100L */ |
| .dot = {.min = MRST_DOT_MIN, .max = MRST_DOT_MAX}, |
| .m = {.min = MRST_M_MIN_100L, .max = MRST_M_MAX_100L}, |
| .p1 = {.min = MRST_P1_MIN, .max = MRST_P1_MAX_1}, |
| .find_pll = mrst_lvds_find_best_pll, |
| }, |
| { /* MRST_LIMIT_LVDS_83L */ |
| .dot = {.min = MRST_DOT_MIN, .max = MRST_DOT_MAX}, |
| .m = {.min = MRST_M_MIN_83, .max = MRST_M_MAX_83}, |
| .p1 = {.min = MRST_P1_MIN, .max = MRST_P1_MAX_0}, |
| .find_pll = mrst_lvds_find_best_pll, |
| }, |
| { /* MRST_LIMIT_LVDS_100 */ |
| .dot = {.min = MRST_DOT_MIN, .max = MRST_DOT_MAX}, |
| .m = {.min = MRST_M_MIN_100, .max = MRST_M_MAX_100}, |
| .p1 = {.min = MRST_P1_MIN, .max = MRST_P1_MAX_1}, |
| .find_pll = mrst_lvds_find_best_pll, |
| }, |
| { /* MRST_LIMIT_SDVO */ |
| .vco = {.min = 1400000, .max = 2800000}, |
| .n = {.min = 3, .max = 7}, |
| .m = {.min = 80, .max = 137}, |
| .p1 = {.min = 1, .max = 2}, |
| .p2 = {.dot_limit = 200000, .p2_slow = 10, .p2_fast = 10}, |
| .find_pll = mrst_sdvo_find_best_pll, |
| }, |
| }; |
| |
| #define MRST_M_MIN 10 |
| static const u32 oaktrail_m_converts[] = { |
| 0x2B, 0x15, 0x2A, 0x35, 0x1A, 0x0D, 0x26, 0x33, 0x19, 0x2C, |
| 0x36, 0x3B, 0x1D, 0x2E, 0x37, 0x1B, 0x2D, 0x16, 0x0B, 0x25, |
| 0x12, 0x09, 0x24, 0x32, 0x39, 0x1c, |
| }; |
| |
| static const struct gma_limit_t *mrst_limit(struct drm_crtc *crtc, |
| int refclk) |
| { |
| const struct gma_limit_t *limit = NULL; |
| struct drm_device *dev = crtc->dev; |
| struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
| |
| if (gma_pipe_has_type(crtc, INTEL_OUTPUT_LVDS) |
| || gma_pipe_has_type(crtc, INTEL_OUTPUT_MIPI)) { |
| switch (dev_priv->core_freq) { |
| case 100: |
| limit = &mrst_limits[MRST_LIMIT_LVDS_100L]; |
| break; |
| case 166: |
| limit = &mrst_limits[MRST_LIMIT_LVDS_83]; |
| break; |
| case 200: |
| limit = &mrst_limits[MRST_LIMIT_LVDS_100]; |
| break; |
| } |
| } else if (gma_pipe_has_type(crtc, INTEL_OUTPUT_SDVO)) { |
| limit = &mrst_limits[MRST_LIMIT_SDVO]; |
| } else { |
| limit = NULL; |
| dev_err(dev->dev, "mrst_limit Wrong display type.\n"); |
| } |
| |
| return limit; |
| } |
| |
| /** Derive the pixel clock for the given refclk and divisors for 8xx chips. */ |
| static void mrst_lvds_clock(int refclk, struct gma_clock_t *clock) |
| { |
| clock->dot = (refclk * clock->m) / (14 * clock->p1); |
| } |
| |
| static void mrst_print_pll(struct gma_clock_t *clock) |
| { |
| DRM_DEBUG_DRIVER("dotclock=%d, m=%d, m1=%d, m2=%d, n=%d, p1=%d, p2=%d\n", |
| clock->dot, clock->m, clock->m1, clock->m2, clock->n, |
| clock->p1, clock->p2); |
| } |
| |
| static bool mrst_sdvo_find_best_pll(const struct gma_limit_t *limit, |
| struct drm_crtc *crtc, int target, |
| int refclk, struct gma_clock_t *best_clock) |
| { |
| struct gma_clock_t clock; |
| u32 target_vco, actual_freq; |
| s32 freq_error, min_error = 100000; |
| |
| memset(best_clock, 0, sizeof(*best_clock)); |
| memset(&clock, 0, sizeof(clock)); |
| |
| for (clock.m = limit->m.min; clock.m <= limit->m.max; clock.m++) { |
| for (clock.n = limit->n.min; clock.n <= limit->n.max; |
| clock.n++) { |
| for (clock.p1 = limit->p1.min; |
| clock.p1 <= limit->p1.max; clock.p1++) { |
| /* p2 value always stored in p2_slow on SDVO */ |
| clock.p = clock.p1 * limit->p2.p2_slow; |
| target_vco = target * clock.p; |
| |
| /* VCO will increase at this point so break */ |
| if (target_vco > limit->vco.max) |
| break; |
| |
| if (target_vco < limit->vco.min) |
| continue; |
| |
| actual_freq = (refclk * clock.m) / |
| (clock.n * clock.p); |
| freq_error = 10000 - |
| ((target * 10000) / actual_freq); |
| |
| if (freq_error < -min_error) { |
| /* freq_error will start to decrease at |
| this point so break */ |
| break; |
| } |
| |
| if (freq_error < 0) |
| freq_error = -freq_error; |
| |
| if (freq_error < min_error) { |
| min_error = freq_error; |
| *best_clock = clock; |
| } |
| } |
| } |
| if (min_error == 0) |
| break; |
| } |
| |
| return min_error == 0; |
| } |
| |
| /* |
| * Returns a set of divisors for the desired target clock with the given refclk, |
| * or FALSE. Divisor values are the actual divisors for |
| */ |
| static bool mrst_lvds_find_best_pll(const struct gma_limit_t *limit, |
| struct drm_crtc *crtc, int target, |
| int refclk, struct gma_clock_t *best_clock) |
| { |
| struct gma_clock_t clock; |
| int err = target; |
| |
| memset(best_clock, 0, sizeof(*best_clock)); |
| memset(&clock, 0, sizeof(clock)); |
| |
| for (clock.m = limit->m.min; clock.m <= limit->m.max; clock.m++) { |
| for (clock.p1 = limit->p1.min; clock.p1 <= limit->p1.max; |
| clock.p1++) { |
| int this_err; |
| |
| mrst_lvds_clock(refclk, &clock); |
| |
| this_err = abs(clock.dot - target); |
| if (this_err < err) { |
| *best_clock = clock; |
| err = this_err; |
| } |
| } |
| } |
| return err != target; |
| } |
| |
| /* |
| * Sets the power management mode of the pipe and plane. |
| * |
| * This code should probably grow support for turning the cursor off and back |
| * on appropriately at the same time as we're turning the pipe off/on. |
| */ |
| static void oaktrail_crtc_dpms(struct drm_crtc *crtc, int mode) |
| { |
| struct drm_device *dev = crtc->dev; |
| struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
| struct gma_crtc *gma_crtc = to_gma_crtc(crtc); |
| int pipe = gma_crtc->pipe; |
| const struct psb_offset *map = &dev_priv->regmap[pipe]; |
| u32 temp; |
| int i; |
| int need_aux = gma_pipe_has_type(crtc, INTEL_OUTPUT_SDVO) ? 1 : 0; |
| |
| if (gma_pipe_has_type(crtc, INTEL_OUTPUT_HDMI)) { |
| oaktrail_crtc_hdmi_dpms(crtc, mode); |
| return; |
| } |
| |
| if (!gma_power_begin(dev, true)) |
| return; |
| |
| /* XXX: When our outputs are all unaware of DPMS modes other than off |
| * and on, we should map those modes to DRM_MODE_DPMS_OFF in the CRTC. |
| */ |
| switch (mode) { |
| case DRM_MODE_DPMS_ON: |
| case DRM_MODE_DPMS_STANDBY: |
| case DRM_MODE_DPMS_SUSPEND: |
| for (i = 0; i <= need_aux; i++) { |
| /* Enable the DPLL */ |
| temp = REG_READ_WITH_AUX(map->dpll, i); |
| if ((temp & DPLL_VCO_ENABLE) == 0) { |
| REG_WRITE_WITH_AUX(map->dpll, temp, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| /* Wait for the clocks to stabilize. */ |
| udelay(150); |
| REG_WRITE_WITH_AUX(map->dpll, |
| temp | DPLL_VCO_ENABLE, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| /* Wait for the clocks to stabilize. */ |
| udelay(150); |
| REG_WRITE_WITH_AUX(map->dpll, |
| temp | DPLL_VCO_ENABLE, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| /* Wait for the clocks to stabilize. */ |
| udelay(150); |
| } |
| |
| /* Enable the pipe */ |
| temp = REG_READ_WITH_AUX(map->conf, i); |
| if ((temp & PIPEACONF_ENABLE) == 0) { |
| REG_WRITE_WITH_AUX(map->conf, |
| temp | PIPEACONF_ENABLE, i); |
| } |
| |
| /* Enable the plane */ |
| temp = REG_READ_WITH_AUX(map->cntr, i); |
| if ((temp & DISPLAY_PLANE_ENABLE) == 0) { |
| REG_WRITE_WITH_AUX(map->cntr, |
| temp | DISPLAY_PLANE_ENABLE, |
| i); |
| /* Flush the plane changes */ |
| REG_WRITE_WITH_AUX(map->base, |
| REG_READ_WITH_AUX(map->base, i), i); |
| } |
| |
| } |
| gma_crtc_load_lut(crtc); |
| |
| /* Give the overlay scaler a chance to enable |
| if it's on this pipe */ |
| /* psb_intel_crtc_dpms_video(crtc, true); TODO */ |
| break; |
| case DRM_MODE_DPMS_OFF: |
| /* Give the overlay scaler a chance to disable |
| * if it's on this pipe */ |
| /* psb_intel_crtc_dpms_video(crtc, FALSE); TODO */ |
| |
| for (i = 0; i <= need_aux; i++) { |
| /* Disable the VGA plane that we never use */ |
| REG_WRITE_WITH_AUX(VGACNTRL, VGA_DISP_DISABLE, i); |
| /* Disable display plane */ |
| temp = REG_READ_WITH_AUX(map->cntr, i); |
| if ((temp & DISPLAY_PLANE_ENABLE) != 0) { |
| REG_WRITE_WITH_AUX(map->cntr, |
| temp & ~DISPLAY_PLANE_ENABLE, i); |
| /* Flush the plane changes */ |
| REG_WRITE_WITH_AUX(map->base, |
| REG_READ(map->base), i); |
| REG_READ_WITH_AUX(map->base, i); |
| } |
| |
| /* Next, disable display pipes */ |
| temp = REG_READ_WITH_AUX(map->conf, i); |
| if ((temp & PIPEACONF_ENABLE) != 0) { |
| REG_WRITE_WITH_AUX(map->conf, |
| temp & ~PIPEACONF_ENABLE, i); |
| REG_READ_WITH_AUX(map->conf, i); |
| } |
| /* Wait for the pipe disable to take effect. */ |
| gma_wait_for_vblank(dev); |
| |
| temp = REG_READ_WITH_AUX(map->dpll, i); |
| if ((temp & DPLL_VCO_ENABLE) != 0) { |
| REG_WRITE_WITH_AUX(map->dpll, |
| temp & ~DPLL_VCO_ENABLE, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| } |
| |
| /* Wait for the clocks to turn off. */ |
| udelay(150); |
| } |
| break; |
| } |
| |
| /* Set FIFO Watermarks (values taken from EMGD) */ |
| REG_WRITE(DSPARB, 0x3f80); |
| REG_WRITE(DSPFW1, 0x3f8f0404); |
| REG_WRITE(DSPFW2, 0x04040f04); |
| REG_WRITE(DSPFW3, 0x0); |
| REG_WRITE(DSPFW4, 0x04040404); |
| REG_WRITE(DSPFW5, 0x04040404); |
| REG_WRITE(DSPFW6, 0x78); |
| REG_WRITE(DSPCHICKENBIT, REG_READ(DSPCHICKENBIT) | 0xc040); |
| |
| gma_power_end(dev); |
| } |
| |
| /* |
| * Return the pipe currently connected to the panel fitter, |
| * or -1 if the panel fitter is not present or not in use |
| */ |
| static int oaktrail_panel_fitter_pipe(struct drm_device *dev) |
| { |
| u32 pfit_control; |
| |
| pfit_control = REG_READ(PFIT_CONTROL); |
| |
| /* See if the panel fitter is in use */ |
| if ((pfit_control & PFIT_ENABLE) == 0) |
| return -1; |
| return (pfit_control >> 29) & 3; |
| } |
| |
| static int oaktrail_crtc_mode_set(struct drm_crtc *crtc, |
| struct drm_display_mode *mode, |
| struct drm_display_mode *adjusted_mode, |
| int x, int y, |
| struct drm_framebuffer *old_fb) |
| { |
| struct drm_device *dev = crtc->dev; |
| struct gma_crtc *gma_crtc = to_gma_crtc(crtc); |
| struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
| int pipe = gma_crtc->pipe; |
| const struct psb_offset *map = &dev_priv->regmap[pipe]; |
| int refclk = 0; |
| struct gma_clock_t clock; |
| const struct gma_limit_t *limit; |
| u32 dpll = 0, fp = 0, dspcntr, pipeconf; |
| bool ok, is_sdvo = false; |
| bool is_lvds = false; |
| bool is_mipi = false; |
| struct gma_encoder *gma_encoder = NULL; |
| uint64_t scalingType = DRM_MODE_SCALE_FULLSCREEN; |
| struct drm_connector_list_iter conn_iter; |
| struct drm_connector *connector; |
| int i; |
| int need_aux = gma_pipe_has_type(crtc, INTEL_OUTPUT_SDVO) ? 1 : 0; |
| |
| if (gma_pipe_has_type(crtc, INTEL_OUTPUT_HDMI)) |
| return oaktrail_crtc_hdmi_mode_set(crtc, mode, adjusted_mode, x, y, old_fb); |
| |
| if (!gma_power_begin(dev, true)) |
| return 0; |
| |
| drm_mode_copy(&gma_crtc->saved_mode, mode); |
| drm_mode_copy(&gma_crtc->saved_adjusted_mode, adjusted_mode); |
| |
| drm_connector_list_iter_begin(dev, &conn_iter); |
| drm_for_each_connector_iter(connector, &conn_iter) { |
| if (!connector->encoder || connector->encoder->crtc != crtc) |
| continue; |
| |
| gma_encoder = gma_attached_encoder(connector); |
| |
| switch (gma_encoder->type) { |
| case INTEL_OUTPUT_LVDS: |
| is_lvds = true; |
| break; |
| case INTEL_OUTPUT_SDVO: |
| is_sdvo = true; |
| break; |
| case INTEL_OUTPUT_MIPI: |
| is_mipi = true; |
| break; |
| } |
| |
| break; |
| } |
| |
| if (gma_encoder) |
| drm_object_property_get_value(&connector->base, |
| dev->mode_config.scaling_mode_property, &scalingType); |
| |
| drm_connector_list_iter_end(&conn_iter); |
| |
| /* Disable the VGA plane that we never use */ |
| for (i = 0; i <= need_aux; i++) |
| REG_WRITE_WITH_AUX(VGACNTRL, VGA_DISP_DISABLE, i); |
| |
| /* Disable the panel fitter if it was on our pipe */ |
| if (oaktrail_panel_fitter_pipe(dev) == pipe) |
| REG_WRITE(PFIT_CONTROL, 0); |
| |
| for (i = 0; i <= need_aux; i++) { |
| REG_WRITE_WITH_AUX(map->src, ((mode->crtc_hdisplay - 1) << 16) | |
| (mode->crtc_vdisplay - 1), i); |
| } |
| |
| if (scalingType == DRM_MODE_SCALE_NO_SCALE) { |
| /* Moorestown doesn't have register support for centering so |
| * we need to mess with the h/vblank and h/vsync start and |
| * ends to get centering */ |
| int offsetX = 0, offsetY = 0; |
| |
| offsetX = (adjusted_mode->crtc_hdisplay - |
| mode->crtc_hdisplay) / 2; |
| offsetY = (adjusted_mode->crtc_vdisplay - |
| mode->crtc_vdisplay) / 2; |
| |
| for (i = 0; i <= need_aux; i++) { |
| REG_WRITE_WITH_AUX(map->htotal, (mode->crtc_hdisplay - 1) | |
| ((adjusted_mode->crtc_htotal - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->vtotal, (mode->crtc_vdisplay - 1) | |
| ((adjusted_mode->crtc_vtotal - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->hblank, |
| (adjusted_mode->crtc_hblank_start - offsetX - 1) | |
| ((adjusted_mode->crtc_hblank_end - offsetX - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->hsync, |
| (adjusted_mode->crtc_hsync_start - offsetX - 1) | |
| ((adjusted_mode->crtc_hsync_end - offsetX - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->vblank, |
| (adjusted_mode->crtc_vblank_start - offsetY - 1) | |
| ((adjusted_mode->crtc_vblank_end - offsetY - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->vsync, |
| (adjusted_mode->crtc_vsync_start - offsetY - 1) | |
| ((adjusted_mode->crtc_vsync_end - offsetY - 1) << 16), i); |
| } |
| } else { |
| for (i = 0; i <= need_aux; i++) { |
| REG_WRITE_WITH_AUX(map->htotal, (adjusted_mode->crtc_hdisplay - 1) | |
| ((adjusted_mode->crtc_htotal - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->vtotal, (adjusted_mode->crtc_vdisplay - 1) | |
| ((adjusted_mode->crtc_vtotal - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->hblank, (adjusted_mode->crtc_hblank_start - 1) | |
| ((adjusted_mode->crtc_hblank_end - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->hsync, (adjusted_mode->crtc_hsync_start - 1) | |
| ((adjusted_mode->crtc_hsync_end - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->vblank, (adjusted_mode->crtc_vblank_start - 1) | |
| ((adjusted_mode->crtc_vblank_end - 1) << 16), i); |
| REG_WRITE_WITH_AUX(map->vsync, (adjusted_mode->crtc_vsync_start - 1) | |
| ((adjusted_mode->crtc_vsync_end - 1) << 16), i); |
| } |
| } |
| |
| /* Flush the plane changes */ |
| { |
| const struct drm_crtc_helper_funcs *crtc_funcs = |
| crtc->helper_private; |
| crtc_funcs->mode_set_base(crtc, x, y, old_fb); |
| } |
| |
| /* setup pipeconf */ |
| pipeconf = REG_READ(map->conf); |
| |
| /* Set up the display plane register */ |
| dspcntr = REG_READ(map->cntr); |
| dspcntr |= DISPPLANE_GAMMA_ENABLE; |
| |
| if (pipe == 0) |
| dspcntr |= DISPPLANE_SEL_PIPE_A; |
| else |
| dspcntr |= DISPPLANE_SEL_PIPE_B; |
| |
| if (is_mipi) |
| goto oaktrail_crtc_mode_set_exit; |
| |
| |
| dpll = 0; /*BIT16 = 0 for 100MHz reference */ |
| |
| refclk = is_sdvo ? 96000 : dev_priv->core_freq * 1000; |
| limit = mrst_limit(crtc, refclk); |
| ok = limit->find_pll(limit, crtc, adjusted_mode->clock, |
| refclk, &clock); |
| |
| if (is_sdvo) { |
| /* Convert calculated values to register values */ |
| clock.p1 = (1L << (clock.p1 - 1)); |
| clock.m -= 2; |
| clock.n = (1L << (clock.n - 1)); |
| } |
| |
| if (!ok) |
| DRM_ERROR("Failed to find proper PLL settings"); |
| |
| mrst_print_pll(&clock); |
| |
| if (is_sdvo) |
| fp = clock.n << 16 | clock.m; |
| else |
| fp = oaktrail_m_converts[(clock.m - MRST_M_MIN)] << 8; |
| |
| dpll |= DPLL_VGA_MODE_DIS; |
| |
| |
| dpll |= DPLL_VCO_ENABLE; |
| |
| if (is_lvds) |
| dpll |= DPLLA_MODE_LVDS; |
| else |
| dpll |= DPLLB_MODE_DAC_SERIAL; |
| |
| if (is_sdvo) { |
| int sdvo_pixel_multiply = |
| adjusted_mode->clock / mode->clock; |
| |
| dpll |= DPLL_DVO_HIGH_SPEED; |
| dpll |= |
| (sdvo_pixel_multiply - |
| 1) << SDVO_MULTIPLIER_SHIFT_HIRES; |
| } |
| |
| |
| /* compute bitmask from p1 value */ |
| if (is_sdvo) |
| dpll |= clock.p1 << 16; // dpll |= (1 << (clock.p1 - 1)) << 16; |
| else |
| dpll |= (1 << (clock.p1 - 2)) << 17; |
| |
| dpll |= DPLL_VCO_ENABLE; |
| |
| if (dpll & DPLL_VCO_ENABLE) { |
| for (i = 0; i <= need_aux; i++) { |
| REG_WRITE_WITH_AUX(map->fp0, fp, i); |
| REG_WRITE_WITH_AUX(map->dpll, dpll & ~DPLL_VCO_ENABLE, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| /* Check the DPLLA lock bit PIPEACONF[29] */ |
| udelay(150); |
| } |
| } |
| |
| for (i = 0; i <= need_aux; i++) { |
| REG_WRITE_WITH_AUX(map->fp0, fp, i); |
| REG_WRITE_WITH_AUX(map->dpll, dpll, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| /* Wait for the clocks to stabilize. */ |
| udelay(150); |
| |
| /* write it again -- the BIOS does, after all */ |
| REG_WRITE_WITH_AUX(map->dpll, dpll, i); |
| REG_READ_WITH_AUX(map->dpll, i); |
| /* Wait for the clocks to stabilize. */ |
| udelay(150); |
| |
| REG_WRITE_WITH_AUX(map->conf, pipeconf, i); |
| REG_READ_WITH_AUX(map->conf, i); |
| gma_wait_for_vblank(dev); |
| |
| REG_WRITE_WITH_AUX(map->cntr, dspcntr, i); |
| gma_wait_for_vblank(dev); |
| } |
| |
| oaktrail_crtc_mode_set_exit: |
| gma_power_end(dev); |
| return 0; |
| } |
| |
| static int oaktrail_pipe_set_base(struct drm_crtc *crtc, |
| int x, int y, struct drm_framebuffer *old_fb) |
| { |
| struct drm_device *dev = crtc->dev; |
| struct drm_psb_private *dev_priv = to_drm_psb_private(dev); |
| struct gma_crtc *gma_crtc = to_gma_crtc(crtc); |
| struct drm_framebuffer *fb = crtc->primary->fb; |
| int pipe = gma_crtc->pipe; |
| const struct psb_offset *map = &dev_priv->regmap[pipe]; |
| unsigned long start, offset; |
| |
| u32 dspcntr; |
| int ret = 0; |
| |
| /* no fb bound */ |
| if (!fb) { |
| dev_dbg(dev->dev, "No FB bound\n"); |
| return 0; |
| } |
| |
| if (!gma_power_begin(dev, true)) |
| return 0; |
| |
| start = to_psb_gem_object(fb->obj[0])->offset; |
| offset = y * fb->pitches[0] + x * fb->format->cpp[0]; |
| |
| REG_WRITE(map->stride, fb->pitches[0]); |
| |
| dspcntr = REG_READ(map->cntr); |
| dspcntr &= ~DISPPLANE_PIXFORMAT_MASK; |
| |
| switch (fb->format->cpp[0] * 8) { |
| case 8: |
| dspcntr |= DISPPLANE_8BPP; |
| break; |
| case 16: |
| if (fb->format->depth == 15) |
| dspcntr |= DISPPLANE_15_16BPP; |
| else |
| dspcntr |= DISPPLANE_16BPP; |
| break; |
| case 24: |
| case 32: |
| dspcntr |= DISPPLANE_32BPP_NO_ALPHA; |
| break; |
| default: |
| dev_err(dev->dev, "Unknown color depth\n"); |
| ret = -EINVAL; |
| goto pipe_set_base_exit; |
| } |
| REG_WRITE(map->cntr, dspcntr); |
| |
| REG_WRITE(map->base, offset); |
| REG_READ(map->base); |
| REG_WRITE(map->surf, start); |
| REG_READ(map->surf); |
| |
| pipe_set_base_exit: |
| gma_power_end(dev); |
| return ret; |
| } |
| |
| const struct drm_crtc_helper_funcs oaktrail_helper_funcs = { |
| .dpms = oaktrail_crtc_dpms, |
| .mode_set = oaktrail_crtc_mode_set, |
| .mode_set_base = oaktrail_pipe_set_base, |
| .prepare = gma_crtc_prepare, |
| .commit = gma_crtc_commit, |
| }; |
| |
| /* Not used yet */ |
| const struct gma_clock_funcs mrst_clock_funcs = { |
| .clock = mrst_lvds_clock, |
| .limit = mrst_limit, |
| .pll_is_valid = gma_pll_is_valid, |
| }; |