diff mbox

gma600: Enable HDMI support

Message ID 20120912202227.10823.43034.stgit@localhost.localdomain (mailing list archive)
State New, archived
Headers show

Commit Message

Alan Cox Sept. 12, 2012, 8:22 p.m. UTC
From: Alan Cox <alan@linux.intel.com>

There are still some mysteries left, in particular how (and in
fact if) the EDID is supposed to work on the HDMI port. However
the basic stuff now works and I can plug my Q550 into an HDMI
display and get the expected results.

Signed-off-by: Alan Cox <alan@linux.intel.com>
---

 drivers/gpu/drm/gma500/oaktrail.h        |    6 
 drivers/gpu/drm/gma500/oaktrail_crtc.c   |    8 +
 drivers/gpu/drm/gma500/oaktrail_device.c |    2 
 drivers/gpu/drm/gma500/oaktrail_hdmi.c   |  366 +++++++++++++++++++++++++++++-
 4 files changed, 366 insertions(+), 16 deletions(-)

Comments

Dave Airlie Sept. 13, 2012, 1:38 a.m. UTC | #1
> There are still some mysteries left, in particular how (and in
> fact if) the EDID is supposed to work on the HDMI port. However
> the basic stuff now works and I can plug my Q550 into an HDMI
> display and get the expected results.

Assumning this is for -next, and its got whitespace damage,
(checkpatch and git complain :-)

Dave.

>
> Signed-off-by: Alan Cox <alan@linux.intel.com>
> ---
>
>  drivers/gpu/drm/gma500/oaktrail.h        |    6
>  drivers/gpu/drm/gma500/oaktrail_crtc.c   |    8 +
>  drivers/gpu/drm/gma500/oaktrail_device.c |    2
>  drivers/gpu/drm/gma500/oaktrail_hdmi.c   |  366 +++++++++++++++++++++++++++++-
>  4 files changed, 366 insertions(+), 16 deletions(-)
>
> diff --git a/drivers/gpu/drm/gma500/oaktrail.h b/drivers/gpu/drm/gma500/oaktrail.h
> index f2f9f38..30adbbe 100644
> --- a/drivers/gpu/drm/gma500/oaktrail.h
> +++ b/drivers/gpu/drm/gma500/oaktrail.h
> @@ -249,3 +249,9 @@ extern void oaktrail_hdmi_i2c_exit(struct pci_dev *dev);
>  extern void oaktrail_hdmi_save(struct drm_device *dev);
>  extern void oaktrail_hdmi_restore(struct drm_device *dev);
>  extern void oaktrail_hdmi_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev);
> +extern int oaktrail_crtc_hdmi_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);
> +extern void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode);
> +
> +
> diff --git a/drivers/gpu/drm/gma500/oaktrail_crtc.c b/drivers/gpu/drm/gma500/oaktrail_crtc.c
> index cdafd2a..4ec2962 100644
> --- a/drivers/gpu/drm/gma500/oaktrail_crtc.c
> +++ b/drivers/gpu/drm/gma500/oaktrail_crtc.c
> @@ -168,6 +168,11 @@ static void oaktrail_crtc_dpms(struct drm_crtc *crtc, int mode)
>         const struct psb_offset *map = &dev_priv->regmap[pipe];
>         u32 temp;
>
> +       if (pipe == 1) {
> +               oaktrail_crtc_hdmi_dpms(crtc, mode);
> +               return;
> +       }
> +
>         if (!gma_power_begin(dev, true))
>                 return;
>
> @@ -302,6 +307,9 @@ static int oaktrail_crtc_mode_set(struct drm_crtc *crtc,
>         uint64_t scalingType = DRM_MODE_SCALE_FULLSCREEN;
>         struct drm_connector *connector;
>
> +       if (pipe == 1)
> +               return oaktrail_crtc_hdmi_mode_set(crtc, mode, adjusted_mode, x, y, old_fb);
> +
>         if (!gma_power_begin(dev, true))
>                 return 0;
>
> diff --git a/drivers/gpu/drm/gma500/oaktrail_device.c b/drivers/gpu/drm/gma500/oaktrail_device.c
> index cf49ba5..a177082 100644
> --- a/drivers/gpu/drm/gma500/oaktrail_device.c
> +++ b/drivers/gpu/drm/gma500/oaktrail_device.c
> @@ -544,7 +544,7 @@ const struct psb_ops oaktrail_chip_ops = {
>         .accel_2d = 1,
>         .pipes = 2,
>         .crtcs = 2,
> -       .hdmi_mask = (1 << 0),
> +       .hdmi_mask = (1 << 1),
>         .lvds_mask = (1 << 0),
>         .cursor_needs_phys = 0,
>         .sgx_offset = MRST_SGX_OFFSET,
> diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi.c b/drivers/gpu/drm/gma500/oaktrail_hdmi.c
> index 2eb3dc4..b0c83fa 100644
> --- a/drivers/gpu/drm/gma500/oaktrail_hdmi.c
> +++ b/drivers/gpu/drm/gma500/oaktrail_hdmi.c
> @@ -155,6 +155,346 @@ static void oaktrail_hdmi_audio_disable(struct drm_device *dev)
>         HDMI_READ(HDMI_HCR);
>  }
>
> +static void wait_for_vblank(struct drm_device *dev)
> +{
> +       /* Wait for 20ms, i.e. one cycle at 50hz. */
> +       mdelay(20);
> +}
> +
> +static unsigned int htotal_calculate(struct drm_display_mode *mode)
> +{
> +       u32 htotal, new_crtc_htotal;
> +
> +       htotal = (mode->crtc_hdisplay - 1) | ((mode->crtc_htotal - 1) << 16);
> +
> +       /*
> +        * 1024 x 768  new_crtc_htotal = 0x1024;
> +        * 1280 x 1024 new_crtc_htotal = 0x0c34;
> +        */
> +       new_crtc_htotal = (mode->crtc_htotal - 1) * 200 * 1000 / mode->clock;
> +
> +       DRM_DEBUG_KMS("new crtc htotal 0x%4x\n", new_crtc_htotal);
> +       return ((mode->crtc_hdisplay - 1) | (new_crtc_htotal << 16));
> +}
> +
> +static void oaktrail_hdmi_find_dpll(struct drm_crtc *crtc, int target,
> +                               int refclk, struct oaktrail_hdmi_clock *best_clock)
> +{
> +       int np_min, np_max, nr_min, nr_max;
> +       int np, nr, nf;
> +
> +       np_min = DIV_ROUND_UP(oaktrail_hdmi_limit.vco.min, target * 10);
> +       np_max = oaktrail_hdmi_limit.vco.max / (target * 10);
> +       if (np_min < oaktrail_hdmi_limit.np.min)
> +               np_min = oaktrail_hdmi_limit.np.min;
> +       if (np_max > oaktrail_hdmi_limit.np.max)
> +               np_max = oaktrail_hdmi_limit.np.max;
> +
> +       nr_min = DIV_ROUND_UP((refclk * 1000), (target * 10 * np_max));
> +       nr_max = DIV_ROUND_UP((refclk * 1000), (target * 10 * np_min));
> +       if (nr_min < oaktrail_hdmi_limit.nr.min)
> +               nr_min = oaktrail_hdmi_limit.nr.min;
> +       if (nr_max > oaktrail_hdmi_limit.nr.max)
> +               nr_max = oaktrail_hdmi_limit.nr.max;
> +
> +       np = DIV_ROUND_UP((refclk * 1000), (target * 10 * nr_max));
> +       nr = DIV_ROUND_UP((refclk * 1000), (target * 10 * np));
> +       nf = DIV_ROUND_CLOSEST((target * 10 * np * nr), refclk);
> +       DRM_DEBUG_KMS("np, nr, nf %d %d %d\n", np, nr, nf);
> +
> +       /*
> +        * 1024 x 768  np = 1; nr = 0x26; nf = 0x0fd8000;
> +        * 1280 x 1024 np = 1; nr = 0x17; nf = 0x1034000;
> +        */
> +       best_clock->np = np;
> +       best_clock->nr = nr - 1;
> +       best_clock->nf = (nf << 14);
> +}
> +
> +static void scu_busy_loop(void __iomem *scu_base)
> +{
> +       u32 status = 0;
> +       u32 loop_count = 0;
> +
> +       status = readl(scu_base + 0x04);
> +       while (status & 1) {
> +               udelay(1); /* scu processing time is in few u secods */
> +               status = readl(scu_base + 0x04);
> +               loop_count++;
> +               /* break if scu doesn't reset busy bit after huge retry */
> +               if (loop_count > 1000) {
> +                       DRM_DEBUG_KMS("SCU IPC timed out");
> +                       return;
> +               }
> +       }
> +}
> +
> +/*
> + *     You don't want to know, you really really don't want to know....
> + *
> + *     This is magic. However it's safe magic because of the way the platform
> + *     works and it is necessary magic.
> + */
> +static void oaktrail_hdmi_reset(struct drm_device *dev)
> +{
> +       void __iomem *base;
> +        unsigned long scu_ipc_mmio = 0xff11c000UL;
> +       int scu_len = 1024;
> +
> +       base = ioremap((resource_size_t)scu_ipc_mmio, scu_len);
> +       if (base == NULL) {
> +               DRM_ERROR("failed to map scu mmio \n");
> +               return;
> +       }
> +
> +       /* scu ipc: assert hdmi controller reset */
> +       writel(0xff11d118, base + 0x0c);
> +       writel(0x7fffffdf, base + 0x80);
> +       writel(0x42005, base + 0x0);
> +       scu_busy_loop(base);
> +
> +       /* scu ipc: de-assert hdmi controller reset */
> +       writel(0xff11d118, base + 0x0c);
> +       writel(0x7fffffff, base + 0x80);
> +       writel(0x42005, base + 0x0);
> +       scu_busy_loop(base);
> +
> +       iounmap(base);
> +}
> +
> +int oaktrail_crtc_hdmi_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 drm_psb_private *dev_priv = dev->dev_private;
> +       struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
> +       int pipe = 1;
> +       int htot_reg = (pipe == 0) ? HTOTAL_A : HTOTAL_B;
> +       int hblank_reg = (pipe == 0) ? HBLANK_A : HBLANK_B;
> +       int hsync_reg = (pipe == 0) ? HSYNC_A : HSYNC_B;
> +       int vtot_reg = (pipe == 0) ? VTOTAL_A : VTOTAL_B;
> +       int vblank_reg = (pipe == 0) ? VBLANK_A : VBLANK_B;
> +       int vsync_reg = (pipe == 0) ? VSYNC_A : VSYNC_B;
> +       int dspsize_reg = (pipe == 0) ? DSPASIZE : DSPBSIZE;
> +       int dsppos_reg = (pipe == 0) ? DSPAPOS : DSPBPOS;
> +       int pipesrc_reg = (pipe == 0) ? PIPEASRC : PIPEBSRC;
> +       int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF;
> +       int refclk;
> +       struct oaktrail_hdmi_clock clock;
> +       u32 dspcntr, pipeconf, dpll, temp;
> +       int dspcntr_reg = DSPBCNTR;
> +
> +       if (!gma_power_begin(dev, true))
> +               return 0;
> +
> +       /* Disable the VGA plane that we never use */
> +       REG_WRITE(VGACNTRL, VGA_DISP_DISABLE);
> +
> +       /* Disable dpll if necessary */
> +       dpll = REG_READ(DPLL_CTRL);
> +       if ((dpll & DPLL_PWRDN) == 0) {
> +               REG_WRITE(DPLL_CTRL, dpll | (DPLL_PWRDN | DPLL_RESET));
> +               REG_WRITE(DPLL_DIV_CTRL, 0x00000000);
> +               REG_WRITE(DPLL_STATUS, 0x1);
> +       }
> +       udelay(150);
> +
> +       /* Reset controller */
> +       oaktrail_hdmi_reset(dev);
> +
> +        /* program and enable dpll */
> +        refclk = 25000;
> +        oaktrail_hdmi_find_dpll(crtc, adjusted_mode->clock, refclk, &clock);
> +
> +       /* Set the DPLL */
> +       dpll = REG_READ(DPLL_CTRL);
> +       dpll &= ~DPLL_PDIV_MASK;
> +       dpll &= ~(DPLL_PWRDN | DPLL_RESET);
> +       REG_WRITE(DPLL_CTRL, 0x00000008);
> +       REG_WRITE(DPLL_DIV_CTRL, ((clock.nf << 6) | clock.nr));
> +       REG_WRITE(DPLL_ADJUST, ((clock.nf >> 14) - 1));
> +       REG_WRITE(DPLL_CTRL, (dpll | (clock.np << DPLL_PDIV_SHIFT) | DPLL_ENSTAT | DPLL_DITHEN));
> +       REG_WRITE(DPLL_UPDATE, 0x80000000);
> +       REG_WRITE(DPLL_CLK_ENABLE, 0x80050102);
> +       udelay(150);
> +
> +       /* configure HDMI */
> +       HDMI_WRITE(0x1004, 0x1fd);
> +       HDMI_WRITE(0x2000, 0x1);
> +       HDMI_WRITE(0x2008, 0x0);
> +       HDMI_WRITE(0x3130, 0x8);
> +       HDMI_WRITE(0x101c, 0x1800810);
> +
> +       temp = htotal_calculate(adjusted_mode);
> +       REG_WRITE(htot_reg, temp);
> +       REG_WRITE(hblank_reg, (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16));
> +       REG_WRITE(hsync_reg, (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16));
> +       REG_WRITE(vtot_reg, (adjusted_mode->crtc_vdisplay - 1) | ((adjusted_mode->crtc_vtotal - 1) << 16));
> +       REG_WRITE(vblank_reg, (adjusted_mode->crtc_vblank_start - 1) | ((adjusted_mode->crtc_vblank_end - 1) << 16));
> +       REG_WRITE(vsync_reg, (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16));
> +       REG_WRITE(pipesrc_reg, ((mode->crtc_hdisplay - 1) << 16) |  (mode->crtc_vdisplay - 1));
> +
> +       REG_WRITE(PCH_HTOTAL_B, (adjusted_mode->crtc_hdisplay - 1) | ((adjusted_mode->crtc_htotal - 1) << 16));
> +       REG_WRITE(PCH_HBLANK_B, (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16));
> +       REG_WRITE(PCH_HSYNC_B, (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16));
> +       REG_WRITE(PCH_VTOTAL_B, (adjusted_mode->crtc_vdisplay - 1) | ((adjusted_mode->crtc_vtotal - 1) << 16));
> +       REG_WRITE(PCH_VBLANK_B, (adjusted_mode->crtc_vblank_start - 1) | ((adjusted_mode->crtc_vblank_end - 1) << 16));
> +       REG_WRITE(PCH_VSYNC_B, (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16));
> +       REG_WRITE(PCH_PIPEBSRC, ((mode->crtc_hdisplay - 1) << 16) |  (mode->crtc_vdisplay - 1));
> +
> +       temp = adjusted_mode->crtc_hblank_end - adjusted_mode->crtc_hblank_start;
> +       HDMI_WRITE(HDMI_HBLANK_A, ((adjusted_mode->crtc_hdisplay - 1) << 16) |  temp);
> +
> +       REG_WRITE(dspsize_reg, ((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1));
> +       REG_WRITE(dsppos_reg, 0);
> +
> +       /* Flush the plane changes */
> +       {
> +               struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
> +
> +               crtc_funcs->mode_set_base(crtc, x, y, old_fb);
> +       }
> +
> +       /* Set up the display plane register */
> +       dspcntr = REG_READ(dspcntr_reg);
> +       dspcntr |= DISPPLANE_GAMMA_ENABLE;
> +       dspcntr |= DISPPLANE_SEL_PIPE_B;
> +       dspcntr |= DISPLAY_PLANE_ENABLE;
> +
> +       /* setup pipeconf */
> +       pipeconf = REG_READ(pipeconf_reg);
> +       pipeconf |= PIPEACONF_ENABLE;
> +
> +       REG_WRITE(pipeconf_reg, pipeconf);
> +       REG_READ(pipeconf_reg);
> +
> +       REG_WRITE(PCH_PIPEBCONF, pipeconf);
> +       REG_READ(PCH_PIPEBCONF);
> +       wait_for_vblank(dev);
> +
> +       REG_WRITE(dspcntr_reg, dspcntr);
> +       wait_for_vblank(dev);
> +
> +       gma_power_end(dev);
> +
> +       return 0;
> +}
> +
> +void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode)
> +{
> +       struct drm_device *dev = crtc->dev;
> +       u32 temp;
> +
> +       DRM_DEBUG_KMS("%s %d\n", __func__, mode);
> +
> +       switch (mode) {
> +       case DRM_MODE_DPMS_OFF:
> +               REG_WRITE(VGACNTRL, 0x80000000);
> +
> +               /* Disable plane */
> +               temp = REG_READ(DSPBCNTR);
> +               if ((temp & DISPLAY_PLANE_ENABLE) != 0) {
> +                       REG_WRITE(DSPBCNTR, temp & ~DISPLAY_PLANE_ENABLE);
> +                       REG_READ(DSPBCNTR);
> +                       /* Flush the plane changes */
> +                       REG_WRITE(DSPBSURF, REG_READ(DSPBSURF));
> +                       REG_READ(DSPBSURF);
> +               }
> +
> +               /* Disable pipe B */
> +               temp = REG_READ(PIPEBCONF);
> +               if ((temp & PIPEACONF_ENABLE) != 0) {
> +                       REG_WRITE(PIPEBCONF, temp & ~PIPEACONF_ENABLE);
> +                       REG_READ(PIPEBCONF);
> +               }
> +
> +               /* Disable LNW Pipes, etc */
> +               temp = REG_READ(PCH_PIPEBCONF);
> +               if ((temp & PIPEACONF_ENABLE) != 0) {
> +                       REG_WRITE(PCH_PIPEBCONF, temp & ~PIPEACONF_ENABLE);
> +                       REG_READ(PCH_PIPEBCONF);
> +               }
> +
> +               /* wait for pipe off */
> +               udelay(150);
> +
> +               /* Disable dpll */
> +               temp = REG_READ(DPLL_CTRL);
> +               if ((temp & DPLL_PWRDN) == 0) {
> +                       REG_WRITE(DPLL_CTRL, temp | (DPLL_PWRDN | DPLL_RESET));
> +                       REG_WRITE(DPLL_STATUS, 0x1);
> +               }
> +
> +               /* wait for dpll off */
> +               udelay(150);
> +
> +               break;
> +       case DRM_MODE_DPMS_ON:
> +       case DRM_MODE_DPMS_STANDBY:
> +       case DRM_MODE_DPMS_SUSPEND:
> +               /* Enable dpll */
> +               temp = REG_READ(DPLL_CTRL);
> +               if ((temp & DPLL_PWRDN) != 0) {
> +                       REG_WRITE(DPLL_CTRL, temp & ~(DPLL_PWRDN | DPLL_RESET));
> +                       temp = REG_READ(DPLL_CLK_ENABLE);
> +                       REG_WRITE(DPLL_CLK_ENABLE, temp | DPLL_EN_DISP | DPLL_SEL_HDMI | DPLL_EN_HDMI);
> +                       REG_READ(DPLL_CLK_ENABLE);
> +               }
> +               /* wait for dpll warm up */
> +               udelay(150);
> +
> +               /* Enable pipe B */
> +               temp = REG_READ(PIPEBCONF);
> +               if ((temp & PIPEACONF_ENABLE) == 0) {
> +                       REG_WRITE(PIPEBCONF, temp | PIPEACONF_ENABLE);
> +                       REG_READ(PIPEBCONF);
> +               }
> +
> +               /* Enable LNW Pipe B */
> +               temp = REG_READ(PCH_PIPEBCONF);
> +               if ((temp & PIPEACONF_ENABLE) == 0) {
> +                       REG_WRITE(PCH_PIPEBCONF, temp | PIPEACONF_ENABLE);
> +                       REG_READ(PCH_PIPEBCONF);
> +               }
> +
> +               wait_for_vblank(dev);
> +
> +               /* Enable plane */
> +               temp = REG_READ(DSPBCNTR);
> +               if ((temp & DISPLAY_PLANE_ENABLE) == 0) {
> +                       REG_WRITE(DSPBCNTR, temp | DISPLAY_PLANE_ENABLE);
> +                       /* Flush the plane changes */
> +                       REG_WRITE(DSPBSURF, REG_READ(DSPBSURF));
> +                       REG_READ(DSPBSURF);
> +               }
> +
> +               psb_intel_crtc_load_lut(crtc);
> +       }
> +
> +       /* DSPARB */
> +       REG_WRITE(DSPARB, 0x00003fbf);
> +
> +       /* FW1 */
> +       REG_WRITE(0x70034, 0x3f880a0a);
> +
> +       /* FW2 */
> +       REG_WRITE(0x70038, 0x0b060808);
> +
> +       /* FW4 */
> +       REG_WRITE(0x70050, 0x08030404);
> +
> +       /* FW5 */
> +       REG_WRITE(0x70054, 0x04040404);
> +
> +       /* LNC Chicken Bits - Squawk! */
> +       REG_WRITE(0x70400, 0x4000);
> +
> +       return;
> +}
> +
>  static void oaktrail_hdmi_dpms(struct drm_encoder *encoder, int mode)
>  {
>         static int dpms_mode = -1;
> @@ -233,13 +573,15 @@ static const unsigned char raw_edid[] = {
>
>  static int oaktrail_hdmi_get_modes(struct drm_connector *connector)
>  {
> -       struct drm_device *dev = connector->dev;
> -       struct drm_psb_private *dev_priv = dev->dev_private;
>         struct i2c_adapter *i2c_adap;
>         struct edid *edid;
> -       struct drm_display_mode *mode, *t;
> -       int i = 0, ret = 0;
> +       int ret = 0;
>
> +       /*
> +        *      FIXME: We need to figure this lot out. In theory we can
> +        *      read the EDID somehow but I've yet to find working reference
> +        *      code.
> +        */
>         i2c_adap = i2c_get_adapter(3);
>         if (i2c_adap == NULL) {
>                 DRM_ERROR("No ddc adapter available!\n");
> @@ -254,17 +596,7 @@ static int oaktrail_hdmi_get_modes(struct drm_connector *connector)
>                 ret = drm_add_edid_modes(connector, edid);
>                 connector->display_info.raw_edid = NULL;
>         }
> -
> -       /*
> -        * prune modes that require frame buffer bigger than stolen mem
> -        */
> -       list_for_each_entry_safe(mode, t, &connector->probed_modes, head) {
> -               if ((mode->hdisplay * mode->vdisplay * 4) >= dev_priv->vram_stolen_size) {
> -                       i++;
> -                       drm_mode_remove(connector, mode);
> -               }
> -       }
> -       return ret - i;
> +       return ret;
>  }
>
>  static void oaktrail_hdmi_mode_set(struct drm_encoder *encoder,
> @@ -350,6 +682,7 @@ void oaktrail_hdmi_init(struct drm_device *dev,
>         connector->interlace_allowed = false;
>         connector->doublescan_allowed = false;
>         drm_sysfs_connector_add(connector);
> +       dev_info(dev->dev, "HDMI initialised.\n");
>
>         return;
>
> @@ -404,6 +737,9 @@ void oaktrail_hdmi_setup(struct drm_device *dev)
>
>         dev_priv->hdmi_priv = hdmi_dev;
>         oaktrail_hdmi_audio_disable(dev);
> +
> +       dev_info(dev->dev, "HDMI hardware present.\n");
> +
>         return;
>
>  free:
>
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/dri-devel
Alan Cox Sept. 13, 2012, 12:30 p.m. UTC | #2
On Thu, 13 Sep 2012 11:38:20 +1000
Dave Airlie <airlied@gmail.com> wrote:

> > There are still some mysteries left, in particular how (and in
> > fact if) the EDID is supposed to work on the HDMI port. However
> > the basic stuff now works and I can plug my Q550 into an HDMI
> > display and get the expected results.
> 
> Assumning this is for -next, and its got whitespace damage,
> (checkpatch and git complain :-)

It is indeed for -next.

Whitespace damage of what kind, messed up space/tabbing or 'doesn't apply
eaten by mail system' ?

If it's the former then I'll send you a couple of follow up patches to
clean up the driver further fairly soon.

Alan
Dave Airlie Sept. 13, 2012, 6:50 p.m. UTC | #3
On Thu, Sep 13, 2012 at 10:30 PM, Alan Cox <alan@lxorguk.ukuu.org.uk> wrote:
> On Thu, 13 Sep 2012 11:38:20 +1000
> Dave Airlie <airlied@gmail.com> wrote:
>
>> > There are still some mysteries left, in particular how (and in
>> > fact if) the EDID is supposed to work on the HDMI port. However
>> > the basic stuff now works and I can plug my Q550 into an HDMI
>> > display and get the expected results.
>>
>> Assumning this is for -next, and its got whitespace damage,
>> (checkpatch and git complain :-)
>
> It is indeed for -next.
>
> Whitespace damage of what kind, messed up space/tabbing or 'doesn't apply
> eaten by mail system' ?

just messed up spaces, trailing spaces/tabs, i.e. git am complains.

Dave.
diff mbox

Patch

diff --git a/drivers/gpu/drm/gma500/oaktrail.h b/drivers/gpu/drm/gma500/oaktrail.h
index f2f9f38..30adbbe 100644
--- a/drivers/gpu/drm/gma500/oaktrail.h
+++ b/drivers/gpu/drm/gma500/oaktrail.h
@@ -249,3 +249,9 @@  extern void oaktrail_hdmi_i2c_exit(struct pci_dev *dev);
 extern void oaktrail_hdmi_save(struct drm_device *dev);
 extern void oaktrail_hdmi_restore(struct drm_device *dev);
 extern void oaktrail_hdmi_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev);
+extern int oaktrail_crtc_hdmi_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);
+extern void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode);
+
+
diff --git a/drivers/gpu/drm/gma500/oaktrail_crtc.c b/drivers/gpu/drm/gma500/oaktrail_crtc.c
index cdafd2a..4ec2962 100644
--- a/drivers/gpu/drm/gma500/oaktrail_crtc.c
+++ b/drivers/gpu/drm/gma500/oaktrail_crtc.c
@@ -168,6 +168,11 @@  static void oaktrail_crtc_dpms(struct drm_crtc *crtc, int mode)
 	const struct psb_offset *map = &dev_priv->regmap[pipe];
 	u32 temp;
 
+	if (pipe == 1) {
+		oaktrail_crtc_hdmi_dpms(crtc, mode);
+		return;
+	}
+
 	if (!gma_power_begin(dev, true))
 		return;
 
@@ -302,6 +307,9 @@  static int oaktrail_crtc_mode_set(struct drm_crtc *crtc,
 	uint64_t scalingType = DRM_MODE_SCALE_FULLSCREEN;
 	struct drm_connector *connector;
 
+	if (pipe == 1)
+		return oaktrail_crtc_hdmi_mode_set(crtc, mode, adjusted_mode, x, y, old_fb);
+
 	if (!gma_power_begin(dev, true))
 		return 0;
 
diff --git a/drivers/gpu/drm/gma500/oaktrail_device.c b/drivers/gpu/drm/gma500/oaktrail_device.c
index cf49ba5..a177082 100644
--- a/drivers/gpu/drm/gma500/oaktrail_device.c
+++ b/drivers/gpu/drm/gma500/oaktrail_device.c
@@ -544,7 +544,7 @@  const struct psb_ops oaktrail_chip_ops = {
 	.accel_2d = 1,
 	.pipes = 2,
 	.crtcs = 2,
-	.hdmi_mask = (1 << 0),
+	.hdmi_mask = (1 << 1),
 	.lvds_mask = (1 << 0),
 	.cursor_needs_phys = 0,
 	.sgx_offset = MRST_SGX_OFFSET,
diff --git a/drivers/gpu/drm/gma500/oaktrail_hdmi.c b/drivers/gpu/drm/gma500/oaktrail_hdmi.c
index 2eb3dc4..b0c83fa 100644
--- a/drivers/gpu/drm/gma500/oaktrail_hdmi.c
+++ b/drivers/gpu/drm/gma500/oaktrail_hdmi.c
@@ -155,6 +155,346 @@  static void oaktrail_hdmi_audio_disable(struct drm_device *dev)
 	HDMI_READ(HDMI_HCR);
 }
 
+static void wait_for_vblank(struct drm_device *dev)
+{
+	/* Wait for 20ms, i.e. one cycle at 50hz. */
+	mdelay(20);
+}
+
+static unsigned int htotal_calculate(struct drm_display_mode *mode)
+{
+	u32 htotal, new_crtc_htotal;
+
+	htotal = (mode->crtc_hdisplay - 1) | ((mode->crtc_htotal - 1) << 16);
+
+	/* 
+	 * 1024 x 768  new_crtc_htotal = 0x1024; 
+	 * 1280 x 1024 new_crtc_htotal = 0x0c34;
+	 */
+	new_crtc_htotal = (mode->crtc_htotal - 1) * 200 * 1000 / mode->clock;
+
+	DRM_DEBUG_KMS("new crtc htotal 0x%4x\n", new_crtc_htotal);
+	return ((mode->crtc_hdisplay - 1) | (new_crtc_htotal << 16));
+}
+
+static void oaktrail_hdmi_find_dpll(struct drm_crtc *crtc, int target,
+				int refclk, struct oaktrail_hdmi_clock *best_clock)
+{
+	int np_min, np_max, nr_min, nr_max;
+	int np, nr, nf;
+
+	np_min = DIV_ROUND_UP(oaktrail_hdmi_limit.vco.min, target * 10);
+	np_max = oaktrail_hdmi_limit.vco.max / (target * 10);
+	if (np_min < oaktrail_hdmi_limit.np.min)
+		np_min = oaktrail_hdmi_limit.np.min;
+	if (np_max > oaktrail_hdmi_limit.np.max)
+		np_max = oaktrail_hdmi_limit.np.max;
+
+	nr_min = DIV_ROUND_UP((refclk * 1000), (target * 10 * np_max));
+	nr_max = DIV_ROUND_UP((refclk * 1000), (target * 10 * np_min));
+	if (nr_min < oaktrail_hdmi_limit.nr.min)
+		nr_min = oaktrail_hdmi_limit.nr.min;
+	if (nr_max > oaktrail_hdmi_limit.nr.max)
+		nr_max = oaktrail_hdmi_limit.nr.max;
+
+	np = DIV_ROUND_UP((refclk * 1000), (target * 10 * nr_max));
+	nr = DIV_ROUND_UP((refclk * 1000), (target * 10 * np));
+	nf = DIV_ROUND_CLOSEST((target * 10 * np * nr), refclk);
+	DRM_DEBUG_KMS("np, nr, nf %d %d %d\n", np, nr, nf);
+
+	/* 
+	 * 1024 x 768  np = 1; nr = 0x26; nf = 0x0fd8000;
+	 * 1280 x 1024 np = 1; nr = 0x17; nf = 0x1034000;
+	 */
+	best_clock->np = np;
+	best_clock->nr = nr - 1;
+	best_clock->nf = (nf << 14);
+}
+
+static void scu_busy_loop(void __iomem *scu_base)
+{
+	u32 status = 0;
+	u32 loop_count = 0;
+
+	status = readl(scu_base + 0x04);
+	while (status & 1) {
+		udelay(1); /* scu processing time is in few u secods */
+		status = readl(scu_base + 0x04);
+		loop_count++;
+		/* break if scu doesn't reset busy bit after huge retry */
+		if (loop_count > 1000) {
+			DRM_DEBUG_KMS("SCU IPC timed out");
+			return;
+		}
+	}
+}
+
+/*
+ *	You don't want to know, you really really don't want to know....
+ *
+ *	This is magic. However it's safe magic because of the way the platform
+ *	works and it is necessary magic.
+ */
+static void oaktrail_hdmi_reset(struct drm_device *dev)
+{
+	void __iomem *base;
+        unsigned long scu_ipc_mmio = 0xff11c000UL;
+	int scu_len = 1024;
+
+	base = ioremap((resource_size_t)scu_ipc_mmio, scu_len);
+	if (base == NULL) {
+		DRM_ERROR("failed to map scu mmio \n");
+		return;
+	}
+
+	/* scu ipc: assert hdmi controller reset */
+	writel(0xff11d118, base + 0x0c);
+	writel(0x7fffffdf, base + 0x80);
+	writel(0x42005, base + 0x0);
+	scu_busy_loop(base);
+
+	/* scu ipc: de-assert hdmi controller reset */
+	writel(0xff11d118, base + 0x0c);
+	writel(0x7fffffff, base + 0x80);
+	writel(0x42005, base + 0x0);
+	scu_busy_loop(base);
+
+	iounmap(base);
+}
+
+int oaktrail_crtc_hdmi_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 drm_psb_private *dev_priv = dev->dev_private;
+	struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
+	int pipe = 1;
+	int htot_reg = (pipe == 0) ? HTOTAL_A : HTOTAL_B;
+	int hblank_reg = (pipe == 0) ? HBLANK_A : HBLANK_B;
+	int hsync_reg = (pipe == 0) ? HSYNC_A : HSYNC_B;
+	int vtot_reg = (pipe == 0) ? VTOTAL_A : VTOTAL_B;
+	int vblank_reg = (pipe == 0) ? VBLANK_A : VBLANK_B;
+	int vsync_reg = (pipe == 0) ? VSYNC_A : VSYNC_B;
+	int dspsize_reg = (pipe == 0) ? DSPASIZE : DSPBSIZE;
+	int dsppos_reg = (pipe == 0) ? DSPAPOS : DSPBPOS;
+	int pipesrc_reg = (pipe == 0) ? PIPEASRC : PIPEBSRC;
+	int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF;
+	int refclk;
+	struct oaktrail_hdmi_clock clock;
+	u32 dspcntr, pipeconf, dpll, temp;
+	int dspcntr_reg = DSPBCNTR;
+
+	if (!gma_power_begin(dev, true))
+		return 0;
+
+	/* Disable the VGA plane that we never use */
+	REG_WRITE(VGACNTRL, VGA_DISP_DISABLE);
+
+	/* Disable dpll if necessary */
+	dpll = REG_READ(DPLL_CTRL);
+	if ((dpll & DPLL_PWRDN) == 0) {
+		REG_WRITE(DPLL_CTRL, dpll | (DPLL_PWRDN | DPLL_RESET));
+		REG_WRITE(DPLL_DIV_CTRL, 0x00000000);
+		REG_WRITE(DPLL_STATUS, 0x1);
+	}
+	udelay(150);
+
+	/* Reset controller */
+	oaktrail_hdmi_reset(dev);
+
+        /* program and enable dpll */
+        refclk = 25000;
+        oaktrail_hdmi_find_dpll(crtc, adjusted_mode->clock, refclk, &clock);
+
+	/* Set the DPLL */
+	dpll = REG_READ(DPLL_CTRL);
+	dpll &= ~DPLL_PDIV_MASK;
+	dpll &= ~(DPLL_PWRDN | DPLL_RESET);
+	REG_WRITE(DPLL_CTRL, 0x00000008);
+	REG_WRITE(DPLL_DIV_CTRL, ((clock.nf << 6) | clock.nr));
+	REG_WRITE(DPLL_ADJUST, ((clock.nf >> 14) - 1));
+	REG_WRITE(DPLL_CTRL, (dpll | (clock.np << DPLL_PDIV_SHIFT) | DPLL_ENSTAT | DPLL_DITHEN));
+	REG_WRITE(DPLL_UPDATE, 0x80000000);
+	REG_WRITE(DPLL_CLK_ENABLE, 0x80050102);
+	udelay(150);
+
+	/* configure HDMI */
+	HDMI_WRITE(0x1004, 0x1fd);
+	HDMI_WRITE(0x2000, 0x1);
+	HDMI_WRITE(0x2008, 0x0);
+	HDMI_WRITE(0x3130, 0x8);
+	HDMI_WRITE(0x101c, 0x1800810);
+
+	temp = htotal_calculate(adjusted_mode);
+	REG_WRITE(htot_reg, temp); 
+	REG_WRITE(hblank_reg, (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16));
+	REG_WRITE(hsync_reg, (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16));
+	REG_WRITE(vtot_reg, (adjusted_mode->crtc_vdisplay - 1) | ((adjusted_mode->crtc_vtotal - 1) << 16));
+	REG_WRITE(vblank_reg, (adjusted_mode->crtc_vblank_start - 1) | ((adjusted_mode->crtc_vblank_end - 1) << 16));
+	REG_WRITE(vsync_reg, (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16));
+	REG_WRITE(pipesrc_reg, ((mode->crtc_hdisplay - 1) << 16) |  (mode->crtc_vdisplay - 1));
+
+	REG_WRITE(PCH_HTOTAL_B, (adjusted_mode->crtc_hdisplay - 1) | ((adjusted_mode->crtc_htotal - 1) << 16));
+	REG_WRITE(PCH_HBLANK_B, (adjusted_mode->crtc_hblank_start - 1) | ((adjusted_mode->crtc_hblank_end - 1) << 16));
+	REG_WRITE(PCH_HSYNC_B, (adjusted_mode->crtc_hsync_start - 1) | ((adjusted_mode->crtc_hsync_end - 1) << 16));
+	REG_WRITE(PCH_VTOTAL_B, (adjusted_mode->crtc_vdisplay - 1) | ((adjusted_mode->crtc_vtotal - 1) << 16));
+	REG_WRITE(PCH_VBLANK_B, (adjusted_mode->crtc_vblank_start - 1) | ((adjusted_mode->crtc_vblank_end - 1) << 16));
+	REG_WRITE(PCH_VSYNC_B, (adjusted_mode->crtc_vsync_start - 1) | ((adjusted_mode->crtc_vsync_end - 1) << 16));
+	REG_WRITE(PCH_PIPEBSRC, ((mode->crtc_hdisplay - 1) << 16) |  (mode->crtc_vdisplay - 1));
+
+	temp = adjusted_mode->crtc_hblank_end - adjusted_mode->crtc_hblank_start;
+	HDMI_WRITE(HDMI_HBLANK_A, ((adjusted_mode->crtc_hdisplay - 1) << 16) |  temp);
+
+	REG_WRITE(dspsize_reg, ((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1));
+	REG_WRITE(dsppos_reg, 0);
+
+	/* Flush the plane changes */
+	{
+		struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
+		
+		crtc_funcs->mode_set_base(crtc, x, y, old_fb);
+	}
+
+	/* Set up the display plane register */
+	dspcntr = REG_READ(dspcntr_reg);
+	dspcntr |= DISPPLANE_GAMMA_ENABLE;
+	dspcntr |= DISPPLANE_SEL_PIPE_B;
+	dspcntr |= DISPLAY_PLANE_ENABLE;
+
+	/* setup pipeconf */
+	pipeconf = REG_READ(pipeconf_reg);
+	pipeconf |= PIPEACONF_ENABLE;
+
+	REG_WRITE(pipeconf_reg, pipeconf);
+	REG_READ(pipeconf_reg);
+
+	REG_WRITE(PCH_PIPEBCONF, pipeconf);
+	REG_READ(PCH_PIPEBCONF);
+	wait_for_vblank(dev);
+
+	REG_WRITE(dspcntr_reg, dspcntr);
+	wait_for_vblank(dev);
+
+	gma_power_end(dev);
+
+	return 0;
+}
+
+void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode)
+{
+	struct drm_device *dev = crtc->dev;
+	u32 temp;
+
+	DRM_DEBUG_KMS("%s %d\n", __func__, mode);
+
+	switch (mode) {
+	case DRM_MODE_DPMS_OFF:
+		REG_WRITE(VGACNTRL, 0x80000000);
+
+		/* Disable plane */
+		temp = REG_READ(DSPBCNTR);
+		if ((temp & DISPLAY_PLANE_ENABLE) != 0) {
+			REG_WRITE(DSPBCNTR, temp & ~DISPLAY_PLANE_ENABLE);
+			REG_READ(DSPBCNTR);
+			/* Flush the plane changes */
+			REG_WRITE(DSPBSURF, REG_READ(DSPBSURF));
+			REG_READ(DSPBSURF);
+		}
+
+		/* Disable pipe B */
+		temp = REG_READ(PIPEBCONF);
+		if ((temp & PIPEACONF_ENABLE) != 0) {
+			REG_WRITE(PIPEBCONF, temp & ~PIPEACONF_ENABLE);
+			REG_READ(PIPEBCONF);
+		}
+
+		/* Disable LNW Pipes, etc */
+		temp = REG_READ(PCH_PIPEBCONF);
+		if ((temp & PIPEACONF_ENABLE) != 0) {
+			REG_WRITE(PCH_PIPEBCONF, temp & ~PIPEACONF_ENABLE);
+			REG_READ(PCH_PIPEBCONF);
+		}
+
+		/* wait for pipe off */
+		udelay(150);
+
+		/* Disable dpll */
+		temp = REG_READ(DPLL_CTRL);
+		if ((temp & DPLL_PWRDN) == 0) {
+			REG_WRITE(DPLL_CTRL, temp | (DPLL_PWRDN | DPLL_RESET));
+			REG_WRITE(DPLL_STATUS, 0x1);
+		}
+
+		/* wait for dpll off */
+		udelay(150);
+
+		break;
+	case DRM_MODE_DPMS_ON:
+	case DRM_MODE_DPMS_STANDBY:
+	case DRM_MODE_DPMS_SUSPEND:
+		/* Enable dpll */
+		temp = REG_READ(DPLL_CTRL);
+		if ((temp & DPLL_PWRDN) != 0) {
+			REG_WRITE(DPLL_CTRL, temp & ~(DPLL_PWRDN | DPLL_RESET));
+			temp = REG_READ(DPLL_CLK_ENABLE);
+			REG_WRITE(DPLL_CLK_ENABLE, temp | DPLL_EN_DISP | DPLL_SEL_HDMI | DPLL_EN_HDMI);
+			REG_READ(DPLL_CLK_ENABLE);
+		}
+		/* wait for dpll warm up */
+		udelay(150);
+
+		/* Enable pipe B */
+		temp = REG_READ(PIPEBCONF);
+		if ((temp & PIPEACONF_ENABLE) == 0) {
+			REG_WRITE(PIPEBCONF, temp | PIPEACONF_ENABLE);
+			REG_READ(PIPEBCONF);
+		}
+
+		/* Enable LNW Pipe B */
+		temp = REG_READ(PCH_PIPEBCONF);
+		if ((temp & PIPEACONF_ENABLE) == 0) {
+			REG_WRITE(PCH_PIPEBCONF, temp | PIPEACONF_ENABLE);
+			REG_READ(PCH_PIPEBCONF);
+		}
+
+		wait_for_vblank(dev);
+
+		/* Enable plane */
+		temp = REG_READ(DSPBCNTR);
+		if ((temp & DISPLAY_PLANE_ENABLE) == 0) {
+			REG_WRITE(DSPBCNTR, temp | DISPLAY_PLANE_ENABLE);
+			/* Flush the plane changes */
+			REG_WRITE(DSPBSURF, REG_READ(DSPBSURF));
+			REG_READ(DSPBSURF);
+		}
+
+		psb_intel_crtc_load_lut(crtc);
+	}
+
+	/* DSPARB */
+	REG_WRITE(DSPARB, 0x00003fbf);
+
+	/* FW1 */
+	REG_WRITE(0x70034, 0x3f880a0a);
+
+	/* FW2 */
+	REG_WRITE(0x70038, 0x0b060808);
+
+	/* FW4 */
+	REG_WRITE(0x70050, 0x08030404);
+
+	/* FW5 */
+	REG_WRITE(0x70054, 0x04040404);
+
+	/* LNC Chicken Bits - Squawk! */
+	REG_WRITE(0x70400, 0x4000);
+
+	return;
+}
+
 static void oaktrail_hdmi_dpms(struct drm_encoder *encoder, int mode)
 {
 	static int dpms_mode = -1;
@@ -233,13 +573,15 @@  static const unsigned char raw_edid[] = {
 
 static int oaktrail_hdmi_get_modes(struct drm_connector *connector)
 {
-	struct drm_device *dev = connector->dev;
-	struct drm_psb_private *dev_priv = dev->dev_private;
 	struct i2c_adapter *i2c_adap;
 	struct edid *edid;
-	struct drm_display_mode *mode, *t;
-	int i = 0, ret = 0;
+	int ret = 0;
 
+	/*
+	 *	FIXME: We need to figure this lot out. In theory we can
+	 *	read the EDID somehow but I've yet to find working reference
+	 *	code.
+	 */
 	i2c_adap = i2c_get_adapter(3);
 	if (i2c_adap == NULL) {
 		DRM_ERROR("No ddc adapter available!\n");
@@ -254,17 +596,7 @@  static int oaktrail_hdmi_get_modes(struct drm_connector *connector)
 		ret = drm_add_edid_modes(connector, edid);
 		connector->display_info.raw_edid = NULL;
 	}
-
-	/*
-	 * prune modes that require frame buffer bigger than stolen mem
-	 */
-	list_for_each_entry_safe(mode, t, &connector->probed_modes, head) {
-		if ((mode->hdisplay * mode->vdisplay * 4) >= dev_priv->vram_stolen_size) {
-			i++;
-			drm_mode_remove(connector, mode);
-		}
-	}
-	return ret - i;
+	return ret;
 }
 
 static void oaktrail_hdmi_mode_set(struct drm_encoder *encoder,
@@ -350,6 +682,7 @@  void oaktrail_hdmi_init(struct drm_device *dev,
 	connector->interlace_allowed = false;
 	connector->doublescan_allowed = false;
 	drm_sysfs_connector_add(connector);
+	dev_info(dev->dev, "HDMI initialised.\n");
 
 	return;
 
@@ -404,6 +737,9 @@  void oaktrail_hdmi_setup(struct drm_device *dev)
 
 	dev_priv->hdmi_priv = hdmi_dev;
 	oaktrail_hdmi_audio_disable(dev);
+
+	dev_info(dev->dev, "HDMI hardware present.\n");
+
 	return;
 
 free: