Commit 1b082ccf authored by Alan Cox's avatar Alan Cox Committed by Dave Airlie

gma500: Add Oaktrail support

Oaktrail (GMA600) is found on some tablet/slate PC type systems. It's a bit
different to the GMA500 but similar enough it makes sense to plug it into
the same driver.
Signed-off-by: default avatarAlan Cox <alan@linux.intel.com>
Signed-off-by: default avatarDave Airlie <airlied@redhat.com>
parent 89c78134
/**************************************************************************
* Copyright (c) 2007-2011, Intel Corporation.
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
**************************************************************************/
/* MID device specific descriptors */
struct oaktrail_vbt {
s8 signature[4]; /*4 bytes,"$GCT" */
u8 revision;
u8 size;
u8 checksum;
void *oaktrail_gct;
} __packed;
struct oaktrail_timing_info {
u16 pixel_clock;
u8 hactive_lo;
u8 hblank_lo;
u8 hblank_hi:4;
u8 hactive_hi:4;
u8 vactive_lo;
u8 vblank_lo;
u8 vblank_hi:4;
u8 vactive_hi:4;
u8 hsync_offset_lo;
u8 hsync_pulse_width_lo;
u8 vsync_pulse_width_lo:4;
u8 vsync_offset_lo:4;
u8 vsync_pulse_width_hi:2;
u8 vsync_offset_hi:2;
u8 hsync_pulse_width_hi:2;
u8 hsync_offset_hi:2;
u8 width_mm_lo;
u8 height_mm_lo;
u8 height_mm_hi:4;
u8 width_mm_hi:4;
u8 hborder;
u8 vborder;
u8 unknown0:1;
u8 hsync_positive:1;
u8 vsync_positive:1;
u8 separate_sync:2;
u8 stereo:1;
u8 unknown6:1;
u8 interlaced:1;
} __packed;
struct gct_r10_timing_info {
u16 pixel_clock;
u32 hactive_lo:8;
u32 hactive_hi:4;
u32 hblank_lo:8;
u32 hblank_hi:4;
u32 hsync_offset_lo:8;
u16 hsync_offset_hi:2;
u16 hsync_pulse_width_lo:8;
u16 hsync_pulse_width_hi:2;
u16 hsync_positive:1;
u16 rsvd_1:3;
u8 vactive_lo:8;
u16 vactive_hi:4;
u16 vblank_lo:8;
u16 vblank_hi:4;
u16 vsync_offset_lo:4;
u16 vsync_offset_hi:2;
u16 vsync_pulse_width_lo:4;
u16 vsync_pulse_width_hi:2;
u16 vsync_positive:1;
u16 rsvd_2:3;
} __packed;
struct oaktrail_panel_descriptor_v1 {
u32 Panel_Port_Control; /* 1 dword, Register 0x61180 if LVDS */
/* 0x61190 if MIPI */
u32 Panel_Power_On_Sequencing;/*1 dword,Register 0x61208,*/
u32 Panel_Power_Off_Sequencing;/*1 dword,Register 0x6120C,*/
u32 Panel_Power_Cycle_Delay_and_Reference_Divisor;/* 1 dword */
/* Register 0x61210 */
struct oaktrail_timing_info DTD;/*18 bytes, Standard definition */
u16 Panel_Backlight_Inverter_Descriptor;/* 16 bits, as follows */
/* Bit 0, Frequency, 15 bits,0 - 32767Hz */
/* Bit 15, Polarity, 1 bit, 0: Normal, 1: Inverted */
u16 Panel_MIPI_Display_Descriptor;
/*16 bits, Defined as follows: */
/* if MIPI, 0x0000 if LVDS */
/* Bit 0, Type, 2 bits, */
/* 0: Type-1, */
/* 1: Type-2, */
/* 2: Type-3, */
/* 3: Type-4 */
/* Bit 2, Pixel Format, 4 bits */
/* Bit0: 16bpp (not supported in LNC), */
/* Bit1: 18bpp loosely packed, */
/* Bit2: 18bpp packed, */
/* Bit3: 24bpp */
/* Bit 6, Reserved, 2 bits, 00b */
/* Bit 8, Minimum Supported Frame Rate, 6 bits, 0 - 63Hz */
/* Bit 14, Reserved, 2 bits, 00b */
} __packed;
struct oaktrail_panel_descriptor_v2 {
u32 Panel_Port_Control; /* 1 dword, Register 0x61180 if LVDS */
/* 0x61190 if MIPI */
u32 Panel_Power_On_Sequencing;/*1 dword,Register 0x61208,*/
u32 Panel_Power_Off_Sequencing;/*1 dword,Register 0x6120C,*/
u8 Panel_Power_Cycle_Delay_and_Reference_Divisor;/* 1 byte */
/* Register 0x61210 */
struct oaktrail_timing_info DTD;/*18 bytes, Standard definition */
u16 Panel_Backlight_Inverter_Descriptor;/*16 bits, as follows*/
/*Bit 0, Frequency, 16 bits, 0 - 32767Hz*/
u8 Panel_Initial_Brightness;/* [7:0] 0 - 100% */
/*Bit 7, Polarity, 1 bit,0: Normal, 1: Inverted*/
u16 Panel_MIPI_Display_Descriptor;
/*16 bits, Defined as follows: */
/* if MIPI, 0x0000 if LVDS */
/* Bit 0, Type, 2 bits, */
/* 0: Type-1, */
/* 1: Type-2, */
/* 2: Type-3, */
/* 3: Type-4 */
/* Bit 2, Pixel Format, 4 bits */
/* Bit0: 16bpp (not supported in LNC), */
/* Bit1: 18bpp loosely packed, */
/* Bit2: 18bpp packed, */
/* Bit3: 24bpp */
/* Bit 6, Reserved, 2 bits, 00b */
/* Bit 8, Minimum Supported Frame Rate, 6 bits, 0 - 63Hz */
/* Bit 14, Reserved, 2 bits, 00b */
} __packed;
union oaktrail_panel_rx {
struct {
u16 NumberOfLanes:2; /*Num of Lanes, 2 bits,0 = 1 lane,*/
/* 1 = 2 lanes, 2 = 3 lanes, 3 = 4 lanes. */
u16 MaxLaneFreq:3; /* 0: 100MHz, 1: 200MHz, 2: 300MHz, */
/*3: 400MHz, 4: 500MHz, 5: 600MHz, 6: 700MHz, 7: 800MHz.*/
u16 SupportedVideoTransferMode:2; /*0: Non-burst only */
/* 1: Burst and non-burst */
/* 2/3: Reserved */
u16 HSClkBehavior:1; /*0: Continuous, 1: Non-continuous*/
u16 DuoDisplaySupport:1; /*1 bit,0: No, 1: Yes*/
u16 ECC_ChecksumCapabilities:1;/*1 bit,0: No, 1: Yes*/
u16 BidirectionalCommunication:1;/*1 bit,0: No, 1: Yes */
u16 Rsvd:5;/*5 bits,00000b */
} panelrx;
u16 panel_receiver;
} __packed;
struct oaktrail_gct_v1 {
union { /*8 bits,Defined as follows: */
struct {
u8 PanelType:4; /*4 bits, Bit field for panels*/
/* 0 - 3: 0 = LVDS, 1 = MIPI*/
/*2 bits,Specifies which of the*/
u8 BootPanelIndex:2;
/* 4 panels to use by default*/
u8 BootMIPI_DSI_RxIndex:2;/*Specifies which of*/
/* the 4 MIPI DSI receivers to use*/
} PD;
u8 PanelDescriptor;
};
struct oaktrail_panel_descriptor_v1 panel[4];/*panel descrs,38 bytes each*/
union oaktrail_panel_rx panelrx[4]; /* panel receivers*/
} __packed;
struct oaktrail_gct_v2 {
union { /*8 bits,Defined as follows: */
struct {
u8 PanelType:4; /*4 bits, Bit field for panels*/
/* 0 - 3: 0 = LVDS, 1 = MIPI*/
/*2 bits,Specifies which of the*/
u8 BootPanelIndex:2;
/* 4 panels to use by default*/
u8 BootMIPI_DSI_RxIndex:2;/*Specifies which of*/
/* the 4 MIPI DSI receivers to use*/
} PD;
u8 PanelDescriptor;
};
struct oaktrail_panel_descriptor_v2 panel[4];/*panel descrs,38 bytes each*/
union oaktrail_panel_rx panelrx[4]; /* panel receivers*/
} __packed;
struct oaktrail_gct_data {
u8 bpi; /* boot panel index, number of panel used during boot */
u8 pt; /* panel type, 4 bit field, 0=lvds, 1=mipi */
struct oaktrail_timing_info DTD; /* timing info for the selected panel */
u32 Panel_Port_Control;
u32 PP_On_Sequencing;/*1 dword,Register 0x61208,*/
u32 PP_Off_Sequencing;/*1 dword,Register 0x6120C,*/
u32 PP_Cycle_Delay;
u16 Panel_Backlight_Inverter_Descriptor;
u16 Panel_MIPI_Display_Descriptor;
} __packed;
#define MODE_SETTING_IN_CRTC 0x1
#define MODE_SETTING_IN_ENCODER 0x2
#define MODE_SETTING_ON_GOING 0x3
#define MODE_SETTING_IN_DSR 0x4
#define MODE_SETTING_ENCODER_DONE 0x8
#define GCT_R10_HEADER_SIZE 16
#define GCT_R10_DISPLAY_DESC_SIZE 28
/*
* Moorestown HDMI interfaces
*/
struct oaktrail_hdmi_dev {
struct pci_dev *dev;
void __iomem *regs;
unsigned int mmio, mmio_len;
int dpms_mode;
struct hdmi_i2c_dev *i2c_dev;
/* register state */
u32 saveDPLL_CTRL;
u32 saveDPLL_DIV_CTRL;
u32 saveDPLL_ADJUST;
u32 saveDPLL_UPDATE;
u32 saveDPLL_CLK_ENABLE;
u32 savePCH_HTOTAL_B;
u32 savePCH_HBLANK_B;
u32 savePCH_HSYNC_B;
u32 savePCH_VTOTAL_B;
u32 savePCH_VBLANK_B;
u32 savePCH_VSYNC_B;
u32 savePCH_PIPEBCONF;
u32 savePCH_PIPEBSRC;
};
extern void oaktrail_hdmi_setup(struct drm_device *dev);
extern void oaktrail_hdmi_teardown(struct drm_device *dev);
extern int oaktrail_hdmi_i2c_init(struct pci_dev *dev);
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);
/*
* Copyright © 2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <linux/i2c.h>
#include <linux/pm_runtime.h>
#include <drm/drmP.h>
#include "framebuffer.h"
#include "psb_drv.h"
#include "psb_intel_drv.h"
#include "psb_intel_reg.h"
#include "psb_intel_display.h"
#include "power.h"
struct psb_intel_range_t {
int min, max;
};
struct oaktrail_limit_t {
struct psb_intel_range_t dot, m, p1;
};
struct oaktrail_clock_t {
/* derived values */
int dot;
int m;
int p1;
};
#define MRST_LIMIT_LVDS_100L 0
#define MRST_LIMIT_LVDS_83 1
#define MRST_LIMIT_LVDS_100 2
#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 const struct oaktrail_limit_t oaktrail_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},
},
{ /* 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},
},
{ /* 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},
},
};
#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 oaktrail_limit_t *oaktrail_limit(struct drm_crtc *crtc)
{
const struct oaktrail_limit_t *limit = NULL;
struct drm_device *dev = crtc->dev;
struct drm_psb_private *dev_priv = dev->dev_private;
if (psb_intel_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)
|| psb_intel_pipe_has_type(crtc, INTEL_OUTPUT_MIPI)) {
switch (dev_priv->core_freq) {
case 100:
limit = &oaktrail_limits[MRST_LIMIT_LVDS_100L];
break;
case 166:
limit = &oaktrail_limits[MRST_LIMIT_LVDS_83];
break;
case 200:
limit = &oaktrail_limits[MRST_LIMIT_LVDS_100];
break;
}
} else {
limit = NULL;
dev_err(dev->dev, "oaktrail_limit Wrong display type.\n");
}
return limit;
}
/** Derive the pixel clock for the given refclk and divisors for 8xx chips. */
static void oaktrail_clock(int refclk, struct oaktrail_clock_t *clock)
{
clock->dot = (refclk * clock->m) / (14 * clock->p1);
}
void mrstPrintPll(char *prefix, struct oaktrail_clock_t *clock)
{
pr_debug("%s: dotclock = %d, m = %d, p1 = %d.\n",
prefix, clock->dot, clock->m, clock->p1);
}
/**
* 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
mrstFindBestPLL(struct drm_crtc *crtc, int target, int refclk,
struct oaktrail_clock_t *best_clock)
{
struct oaktrail_clock_t clock;
const struct oaktrail_limit_t *limit = oaktrail_limit(crtc);
int err = target;
memset(best_clock, 0, sizeof(*best_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;
oaktrail_clock(refclk, &clock);
this_err = abs(clock.dot - target);
if (this_err < err) {
*best_clock = clock;
err = this_err;
}
}
}
dev_dbg(crtc->dev->dev, "mrstFindBestPLL err = %d.\n", 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 psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc);
int pipe = psb_intel_crtc->pipe;
int dpll_reg = (pipe == 0) ? MRST_DPLL_A : DPLL_B;
int dspcntr_reg = (pipe == 0) ? DSPACNTR : DSPBCNTR;
int dspbase_reg = (pipe == 0) ? MRST_DSPABASE : DSPBBASE;
int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF;
u32 temp;
bool enabled;
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:
/* Enable the DPLL */
temp = REG_READ(dpll_reg);
if ((temp & DPLL_VCO_ENABLE) == 0) {
REG_WRITE(dpll_reg, temp);
REG_READ(dpll_reg);
/* Wait for the clocks to stabilize. */
udelay(150);
REG_WRITE(dpll_reg, temp | DPLL_VCO_ENABLE);
REG_READ(dpll_reg);
/* Wait for the clocks to stabilize. */
udelay(150);
REG_WRITE(dpll_reg, temp | DPLL_VCO_ENABLE);
REG_READ(dpll_reg);
/* Wait for the clocks to stabilize. */
udelay(150);
}
/* Enable the pipe */
temp = REG_READ(pipeconf_reg);
if ((temp & PIPEACONF_ENABLE) == 0)
REG_WRITE(pipeconf_reg, temp | PIPEACONF_ENABLE);
/* Enable the plane */
temp = REG_READ(dspcntr_reg);
if ((temp & DISPLAY_PLANE_ENABLE) == 0) {
REG_WRITE(dspcntr_reg,
temp | DISPLAY_PLANE_ENABLE);
/* Flush the plane changes */
REG_WRITE(dspbase_reg, REG_READ(dspbase_reg));
}
psb_intel_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 */
/* Disable the VGA plane that we never use */
REG_WRITE(VGACNTRL, VGA_DISP_DISABLE);
/* Disable display plane */
temp = REG_READ(dspcntr_reg);
if ((temp & DISPLAY_PLANE_ENABLE) != 0) {
REG_WRITE(dspcntr_reg,
temp & ~DISPLAY_PLANE_ENABLE);
/* Flush the plane changes */
REG_WRITE(dspbase_reg, REG_READ(dspbase_reg));
REG_READ(dspbase_reg);
}
/* Next, disable display pipes */
temp = REG_READ(pipeconf_reg);
if ((temp & PIPEACONF_ENABLE) != 0) {
REG_WRITE(pipeconf_reg, temp & ~PIPEACONF_ENABLE);
REG_READ(pipeconf_reg);
}
/* Wait for for the pipe disable to take effect. */
psb_intel_wait_for_vblank(dev);
temp = REG_READ(dpll_reg);
if ((temp & DPLL_VCO_ENABLE) != 0) {
REG_WRITE(dpll_reg, temp & ~DPLL_VCO_ENABLE);
REG_READ(dpll_reg);
}
/* Wait for the clocks to turn off. */
udelay(150);
break;
}
enabled = crtc->enabled && mode != DRM_MODE_DPMS_OFF;
/*Set FIFO Watermarks*/
REG_WRITE(DSPARB, 0x3FFF);
REG_WRITE(DSPFW1, 0x3F88080A);
REG_WRITE(DSPFW2, 0x0b060808);
REG_WRITE(DSPFW3, 0x0);
REG_WRITE(DSPFW4, 0x08030404);
REG_WRITE(DSPFW5, 0x04040404);
REG_WRITE(DSPFW6, 0x78);
REG_WRITE(0x70400, REG_READ(0x70400) | 0x4000);
/* Must write Bit 14 of the Chicken Bit Register */
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 psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc);
struct drm_psb_private *dev_priv = dev->dev_private;
int pipe = psb_intel_crtc->pipe;
int fp_reg = (pipe == 0) ? MRST_FPA0 : FPB0;
int dpll_reg = (pipe == 0) ? MRST_DPLL_A : DPLL_B;
int dspcntr_reg = (pipe == 0) ? DSPACNTR : DSPBCNTR;
int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF;
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 pipesrc_reg = (pipe == 0) ? PIPEASRC : PIPEBSRC;
int refclk = 0;
struct oaktrail_clock_t clock;
u32 dpll = 0, fp = 0, dspcntr, pipeconf;
bool ok, is_sdvo = false;
bool is_crt = false, is_lvds = false, is_tv = false;
bool is_mipi = false;
struct drm_mode_config *mode_config = &dev->mode_config;
struct psb_intel_output *psb_intel_output = NULL;
uint64_t scalingType = DRM_MODE_SCALE_FULLSCREEN;
struct drm_encoder *encoder;
if (!gma_power_begin(dev, true))
return 0;
memcpy(&psb_intel_crtc->saved_mode,
mode,
sizeof(struct drm_display_mode));
memcpy(&psb_intel_crtc->saved_adjusted_mode,
adjusted_mode,
sizeof(struct drm_display_mode));
list_for_each_entry(encoder, &mode_config->encoder_list, head) {
if (encoder->crtc != crtc)
continue;
psb_intel_output = enc_to_psb_intel_output(encoder);
switch (psb_intel_output->type) {
case INTEL_OUTPUT_LVDS:
is_lvds = true;
break;
case INTEL_OUTPUT_SDVO:
is_sdvo = true;
break;
case INTEL_OUTPUT_TVOUT:
is_tv = true;
break;
case INTEL_OUTPUT_ANALOG:
is_crt = true;
break;
case INTEL_OUTPUT_MIPI:
is_mipi = true;
break;
}
}
/* Disable the VGA plane that we never use */
REG_WRITE(VGACNTRL, VGA_DISP_DISABLE);
/* Disable the panel fitter if it was on our pipe */
if (oaktrail_panel_fitter_pipe(dev) == pipe)
REG_WRITE(PFIT_CONTROL, 0);
REG_WRITE(pipesrc_reg,
((mode->crtc_hdisplay - 1) << 16) |
(mode->crtc_vdisplay - 1));
if (psb_intel_output)
drm_connector_property_get_value(&psb_intel_output->base,
dev->mode_config.scaling_mode_property, &scalingType);
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;
REG_WRITE(htot_reg, (mode->crtc_hdisplay - 1) |
((adjusted_mode->crtc_htotal - 1) << 16));
REG_WRITE(vtot_reg, (mode->crtc_vdisplay - 1) |
((adjusted_mode->crtc_vtotal - 1) << 16));
REG_WRITE(hblank_reg,
(adjusted_mode->crtc_hblank_start - offsetX - 1) |
((adjusted_mode->crtc_hblank_end - offsetX - 1) << 16));
REG_WRITE(hsync_reg,
(adjusted_mode->crtc_hsync_start - offsetX - 1) |
((adjusted_mode->crtc_hsync_end - offsetX - 1) << 16));
REG_WRITE(vblank_reg,
(adjusted_mode->crtc_vblank_start - offsetY - 1) |
((adjusted_mode->crtc_vblank_end - offsetY - 1) << 16));
REG_WRITE(vsync_reg,
(adjusted_mode->crtc_vsync_start - offsetY - 1) |
((adjusted_mode->crtc_vsync_end - offsetY - 1) << 16));
} else {
REG_WRITE(htot_reg, (adjusted_mode->crtc_hdisplay - 1) |
((adjusted_mode->crtc_htotal - 1) << 16));
REG_WRITE(vtot_reg, (adjusted_mode->crtc_vdisplay - 1) |
((adjusted_mode->crtc_vtotal - 1) << 16));
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(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));
}
/* Flush the plane changes */
{
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(pipeconf_reg);
/* Set up the display plane register */
dspcntr = REG_READ(dspcntr_reg);
dspcntr |= DISPPLANE_GAMMA_ENABLE;
if (pipe == 0)
dspcntr |= DISPPLANE_SEL_PIPE_A;
else
dspcntr |= DISPPLANE_SEL_PIPE_B;
dev_priv->dspcntr = dspcntr |= DISPLAY_PLANE_ENABLE;
dev_priv->pipeconf = pipeconf |= PIPEACONF_ENABLE;
if (is_mipi)
goto oaktrail_crtc_mode_set_exit;
refclk = dev_priv->core_freq * 1000;
dpll = 0; /*BIT16 = 0 for 100MHz reference */
ok = mrstFindBestPLL(crtc, adjusted_mode->clock, refclk, &clock);
if (!ok) {
dev_dbg(dev->dev, "mrstFindBestPLL fail in oaktrail_crtc_mode_set.\n");
} else {
dev_dbg(dev->dev, "oaktrail_crtc_mode_set pixel clock = %d,"
"m = %x, p1 = %x.\n", clock.dot, clock.m,
clock.p1);
}
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 */
dpll |= (1 << (clock.p1 - 2)) << 17;
dpll |= DPLL_VCO_ENABLE;
mrstPrintPll("chosen", &clock);
if (dpll & DPLL_VCO_ENABLE) {
REG_WRITE(fp_reg, fp);
REG_WRITE(dpll_reg, dpll & ~DPLL_VCO_ENABLE);
REG_READ(dpll_reg);
/* Check the DPLLA lock bit PIPEACONF[29] */
udelay(150);
}
REG_WRITE(fp_reg, fp);
REG_WRITE(dpll_reg, dpll);
REG_READ(dpll_reg);
/* Wait for the clocks to stabilize. */
udelay(150);
/* write it again -- the BIOS does, after all */
REG_WRITE(dpll_reg, dpll);
REG_READ(dpll_reg);
/* Wait for the clocks to stabilize. */
udelay(150);
REG_WRITE(pipeconf_reg, pipeconf);
REG_READ(pipeconf_reg);
psb_intel_wait_for_vblank(dev);
REG_WRITE(dspcntr_reg, dspcntr);
psb_intel_wait_for_vblank(dev);
oaktrail_crtc_mode_set_exit:
gma_power_end(dev);
return 0;
}
static bool oaktrail_crtc_mode_fixup(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
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_i915_master_private *master_priv; */
struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc);
struct psb_framebuffer *psbfb = to_psb_fb(crtc->fb);
int pipe = psb_intel_crtc->pipe;
unsigned long start, offset;
/* FIXME: check if we need this surely MRST is pipe 0 only */
int dspbase = (pipe == 0 ? DSPALINOFF : DSPBBASE);
int dspsurf = (pipe == 0 ? DSPASURF : DSPBSURF);
int dspstride = (pipe == 0) ? DSPASTRIDE : DSPBSTRIDE;
int dspcntr_reg = (pipe == 0) ? DSPACNTR : DSPBCNTR;
u32 dspcntr;
int ret = 0;
/* no fb bound */
if (!crtc->fb) {
dev_dbg(dev->dev, "No FB bound\n");
return 0;
}
if (!gma_power_begin(dev, true))
return 0;
start = psbfb->gtt->offset;
offset = y * crtc->fb->pitch + x * (crtc->fb->bits_per_pixel / 8);
REG_WRITE(dspstride, crtc->fb->pitch);
dspcntr = REG_READ(dspcntr_reg);
dspcntr &= ~DISPPLANE_PIXFORMAT_MASK;
switch (crtc->fb->bits_per_pixel) {
case 8:
dspcntr |= DISPPLANE_8BPP;
break;
case 16:
if (crtc->fb->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(dspcntr_reg, dspcntr);
if (0 /* FIXMEAC - check what PSB needs */) {
REG_WRITE(dspbase, offset);
REG_READ(dspbase);
REG_WRITE(dspsurf, start);
REG_READ(dspsurf);
} else {
REG_WRITE(dspbase, start + offset);
REG_READ(dspbase);
}
pipe_set_base_exit:
gma_power_end(dev);
return ret;
}
static void oaktrail_crtc_prepare(struct drm_crtc *crtc)
{
struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
}
static void oaktrail_crtc_commit(struct drm_crtc *crtc)
{
struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON);
}
const struct drm_crtc_helper_funcs oaktrail_helper_funcs = {
.dpms = oaktrail_crtc_dpms,
.mode_fixup = oaktrail_crtc_mode_fixup,
.mode_set = oaktrail_crtc_mode_set,
.mode_set_base = oaktrail_pipe_set_base,
.prepare = oaktrail_crtc_prepare,
.commit = oaktrail_crtc_commit,
};
/**************************************************************************
* Copyright (c) 2011, Intel Corporation.
* All Rights Reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
**************************************************************************/
#include <linux/backlight.h>
#include <linux/module.h>
#include <linux/dmi.h>
#include <drm/drmP.h>
#include <drm/drm.h>
#include "psb_drm.h"
#include "psb_drv.h"
#include "psb_reg.h"
#include "psb_intel_reg.h"
#include <asm/mrst.h>
#include <asm/intel_scu_ipc.h>
#include "mid_bios.h"
static int oaktrail_output_init(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
if (dev_priv->iLVDS_enable)
oaktrail_lvds_init(dev, &dev_priv->mode_dev);
else
dev_err(dev->dev, "DSI is not supported\n");
if (dev_priv->hdmi_priv)
oaktrail_hdmi_init(dev, &dev_priv->mode_dev);
return 0;
}
/*
* Provide the low level interfaces for the Moorestown backlight
*/
#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE
#define MRST_BLC_MAX_PWM_REG_FREQ 0xFFFF
#define BLC_PWM_PRECISION_FACTOR 100 /* 10000000 */
#define BLC_PWM_FREQ_CALC_CONSTANT 32
#define MHz 1000000
#define BLC_ADJUSTMENT_MAX 100
static struct backlight_device *oaktrail_backlight_device;
static int oaktrail_brightness;
static int oaktrail_set_brightness(struct backlight_device *bd)
{
struct drm_device *dev = bl_get_data(oaktrail_backlight_device);
struct drm_psb_private *dev_priv = dev->dev_private;
int level = bd->props.brightness;
u32 blc_pwm_ctl;
u32 max_pwm_blc;
/* Percentage 1-100% being valid */
if (level < 1)
level = 1;
if (gma_power_begin(dev, 0)) {
/* Calculate and set the brightness value */
max_pwm_blc = REG_READ(BLC_PWM_CTL) >> 16;
blc_pwm_ctl = level * max_pwm_blc / 100;
/* Adjust the backlight level with the percent in
* dev_priv->blc_adj1;
*/
blc_pwm_ctl = blc_pwm_ctl * dev_priv->blc_adj1;
blc_pwm_ctl = blc_pwm_ctl / 100;
/* Adjust the backlight level with the percent in
* dev_priv->blc_adj2;
*/
blc_pwm_ctl = blc_pwm_ctl * dev_priv->blc_adj2;
blc_pwm_ctl = blc_pwm_ctl / 100;
/* force PWM bit on */
REG_WRITE(BLC_PWM_CTL2, (0x80000000 | REG_READ(BLC_PWM_CTL2)));
REG_WRITE(BLC_PWM_CTL, (max_pwm_blc << 16) | blc_pwm_ctl);
gma_power_end(dev);
}
oaktrail_brightness = level;
return 0;
}
static int oaktrail_get_brightness(struct backlight_device *bd)
{
/* return locally cached var instead of HW read (due to DPST etc.) */
/* FIXME: ideally return actual value in case firmware fiddled with
it */
return oaktrail_brightness;
}
static int device_backlight_init(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
unsigned long core_clock;
u16 bl_max_freq;
uint32_t value;
uint32_t blc_pwm_precision_factor;
dev_priv->blc_adj1 = BLC_ADJUSTMENT_MAX;
dev_priv->blc_adj2 = BLC_ADJUSTMENT_MAX;
bl_max_freq = 256;
/* this needs to be set elsewhere */
blc_pwm_precision_factor = BLC_PWM_PRECISION_FACTOR;
core_clock = dev_priv->core_freq;
value = (core_clock * MHz) / BLC_PWM_FREQ_CALC_CONSTANT;
value *= blc_pwm_precision_factor;
value /= bl_max_freq;
value /= blc_pwm_precision_factor;
if (value > (unsigned long long)MRST_BLC_MAX_PWM_REG_FREQ)
return -ERANGE;
if (gma_power_begin(dev, false)) {
REG_WRITE(BLC_PWM_CTL2, (0x80000000 | REG_READ(BLC_PWM_CTL2)));
REG_WRITE(BLC_PWM_CTL, value | (value << 16));
gma_power_end(dev);
}
return 0;
}
static const struct backlight_ops oaktrail_ops = {
.get_brightness = oaktrail_get_brightness,
.update_status = oaktrail_set_brightness,
};
int oaktrail_backlight_init(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
int ret;
struct backlight_properties props;
memset(&props, 0, sizeof(struct backlight_properties));
props.max_brightness = 100;
props.type = BACKLIGHT_PLATFORM;
oaktrail_backlight_device = backlight_device_register("oaktrail-bl",
NULL, (void *)dev, &oaktrail_ops, &props);
if (IS_ERR(oaktrail_backlight_device))
return PTR_ERR(oaktrail_backlight_device);
ret = device_backlight_init(dev);
if (ret < 0) {
backlight_device_unregister(oaktrail_backlight_device);
return ret;
}
oaktrail_backlight_device->props.brightness = 100;
oaktrail_backlight_device->props.max_brightness = 100;
backlight_update_status(oaktrail_backlight_device);
dev_priv->backlight_device = oaktrail_backlight_device;
return 0;
}
#endif
/*
* Provide the Moorestown specific chip logic and low level methods
* for power management
*/
static void oaktrail_init_pm(struct drm_device *dev)
{
}
/**
* oaktrail_save_display_registers - save registers lost on suspend
* @dev: our DRM device
*
* Save the state we need in order to be able to restore the interface
* upon resume from suspend
*/
static int oaktrail_save_display_registers(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
int i;
u32 pp_stat;
/* Display arbitration control + watermarks */
dev_priv->saveDSPARB = PSB_RVDC32(DSPARB);
dev_priv->saveDSPFW1 = PSB_RVDC32(DSPFW1);
dev_priv->saveDSPFW2 = PSB_RVDC32(DSPFW2);
dev_priv->saveDSPFW3 = PSB_RVDC32(DSPFW3);
dev_priv->saveDSPFW4 = PSB_RVDC32(DSPFW4);
dev_priv->saveDSPFW5 = PSB_RVDC32(DSPFW5);
dev_priv->saveDSPFW6 = PSB_RVDC32(DSPFW6);
dev_priv->saveCHICKENBIT = PSB_RVDC32(DSPCHICKENBIT);
/* Pipe & plane A info */
dev_priv->savePIPEACONF = PSB_RVDC32(PIPEACONF);
dev_priv->savePIPEASRC = PSB_RVDC32(PIPEASRC);
dev_priv->saveFPA0 = PSB_RVDC32(MRST_FPA0);
dev_priv->saveFPA1 = PSB_RVDC32(MRST_FPA1);
dev_priv->saveDPLL_A = PSB_RVDC32(MRST_DPLL_A);
dev_priv->saveHTOTAL_A = PSB_RVDC32(HTOTAL_A);
dev_priv->saveHBLANK_A = PSB_RVDC32(HBLANK_A);
dev_priv->saveHSYNC_A = PSB_RVDC32(HSYNC_A);
dev_priv->saveVTOTAL_A = PSB_RVDC32(VTOTAL_A);
dev_priv->saveVBLANK_A = PSB_RVDC32(VBLANK_A);
dev_priv->saveVSYNC_A = PSB_RVDC32(VSYNC_A);
dev_priv->saveBCLRPAT_A = PSB_RVDC32(BCLRPAT_A);
dev_priv->saveDSPACNTR = PSB_RVDC32(DSPACNTR);
dev_priv->saveDSPASTRIDE = PSB_RVDC32(DSPASTRIDE);
dev_priv->saveDSPAADDR = PSB_RVDC32(DSPABASE);
dev_priv->saveDSPASURF = PSB_RVDC32(DSPASURF);
dev_priv->saveDSPALINOFF = PSB_RVDC32(DSPALINOFF);
dev_priv->saveDSPATILEOFF = PSB_RVDC32(DSPATILEOFF);
/* Save cursor regs */
dev_priv->saveDSPACURSOR_CTRL = PSB_RVDC32(CURACNTR);
dev_priv->saveDSPACURSOR_BASE = PSB_RVDC32(CURABASE);
dev_priv->saveDSPACURSOR_POS = PSB_RVDC32(CURAPOS);
/* Save palette (gamma) */
for (i = 0; i < 256; i++)
dev_priv->save_palette_a[i] = PSB_RVDC32(PALETTE_A + (i << 2));
if (dev_priv->hdmi_priv)
oaktrail_hdmi_save(dev);
/* Save performance state */
dev_priv->savePERF_MODE = PSB_RVDC32(MRST_PERF_MODE);
/* LVDS state */
dev_priv->savePP_CONTROL = PSB_RVDC32(PP_CONTROL);
dev_priv->savePFIT_PGM_RATIOS = PSB_RVDC32(PFIT_PGM_RATIOS);
dev_priv->savePFIT_AUTO_RATIOS = PSB_RVDC32(PFIT_AUTO_RATIOS);
dev_priv->saveBLC_PWM_CTL = PSB_RVDC32(BLC_PWM_CTL);
dev_priv->saveBLC_PWM_CTL2 = PSB_RVDC32(BLC_PWM_CTL2);
dev_priv->saveLVDS = PSB_RVDC32(LVDS);
dev_priv->savePFIT_CONTROL = PSB_RVDC32(PFIT_CONTROL);
dev_priv->savePP_ON_DELAYS = PSB_RVDC32(LVDSPP_ON);
dev_priv->savePP_OFF_DELAYS = PSB_RVDC32(LVDSPP_OFF);
dev_priv->savePP_DIVISOR = PSB_RVDC32(PP_CYCLE);
/* HW overlay */
dev_priv->saveOV_OVADD = PSB_RVDC32(OV_OVADD);
dev_priv->saveOV_OGAMC0 = PSB_RVDC32(OV_OGAMC0);
dev_priv->saveOV_OGAMC1 = PSB_RVDC32(OV_OGAMC1);
dev_priv->saveOV_OGAMC2 = PSB_RVDC32(OV_OGAMC2);
dev_priv->saveOV_OGAMC3 = PSB_RVDC32(OV_OGAMC3);
dev_priv->saveOV_OGAMC4 = PSB_RVDC32(OV_OGAMC4);
dev_priv->saveOV_OGAMC5 = PSB_RVDC32(OV_OGAMC5);
/* DPST registers */
dev_priv->saveHISTOGRAM_INT_CONTROL_REG =
PSB_RVDC32(HISTOGRAM_INT_CONTROL);
dev_priv->saveHISTOGRAM_LOGIC_CONTROL_REG =
PSB_RVDC32(HISTOGRAM_LOGIC_CONTROL);
dev_priv->savePWM_CONTROL_LOGIC = PSB_RVDC32(PWM_CONTROL_LOGIC);
if (dev_priv->iLVDS_enable) {
/* Shut down the panel */
PSB_WVDC32(0, PP_CONTROL);
do {
pp_stat = PSB_RVDC32(PP_STATUS);
} while (pp_stat & 0x80000000);
/* Turn off the plane */
PSB_WVDC32(0x58000000, DSPACNTR);
/* Trigger the plane disable */
PSB_WVDC32(0, DSPASURF);
/* Wait ~4 ticks */
msleep(4);
/* Turn off pipe */
PSB_WVDC32(0x0, PIPEACONF);
/* Wait ~8 ticks */
msleep(8);
/* Turn off PLLs */
PSB_WVDC32(0, MRST_DPLL_A);
}
return 0;
}
/**
* oaktrail_restore_display_registers - restore lost register state
* @dev: our DRM device
*
* Restore register state that was lost during suspend and resume.
*/
static int oaktrail_restore_display_registers(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
u32 pp_stat;
int i;
/* Display arbitration + watermarks */
PSB_WVDC32(dev_priv->saveDSPARB, DSPARB);
PSB_WVDC32(dev_priv->saveDSPFW1, DSPFW1);
PSB_WVDC32(dev_priv->saveDSPFW2, DSPFW2);
PSB_WVDC32(dev_priv->saveDSPFW3, DSPFW3);
PSB_WVDC32(dev_priv->saveDSPFW4, DSPFW4);
PSB_WVDC32(dev_priv->saveDSPFW5, DSPFW5);
PSB_WVDC32(dev_priv->saveDSPFW6, DSPFW6);
PSB_WVDC32(dev_priv->saveCHICKENBIT, DSPCHICKENBIT);
/* Make sure VGA plane is off. it initializes to on after reset!*/
PSB_WVDC32(0x80000000, VGACNTRL);
/* set the plls */
PSB_WVDC32(dev_priv->saveFPA0, MRST_FPA0);
PSB_WVDC32(dev_priv->saveFPA1, MRST_FPA1);
/* Actually enable it */
PSB_WVDC32(dev_priv->saveDPLL_A, MRST_DPLL_A);
DRM_UDELAY(150);
/* Restore mode */
PSB_WVDC32(dev_priv->saveHTOTAL_A, HTOTAL_A);
PSB_WVDC32(dev_priv->saveHBLANK_A, HBLANK_A);
PSB_WVDC32(dev_priv->saveHSYNC_A, HSYNC_A);
PSB_WVDC32(dev_priv->saveVTOTAL_A, VTOTAL_A);
PSB_WVDC32(dev_priv->saveVBLANK_A, VBLANK_A);
PSB_WVDC32(dev_priv->saveVSYNC_A, VSYNC_A);
PSB_WVDC32(dev_priv->savePIPEASRC, PIPEASRC);
PSB_WVDC32(dev_priv->saveBCLRPAT_A, BCLRPAT_A);
/* Restore performance mode*/
PSB_WVDC32(dev_priv->savePERF_MODE, MRST_PERF_MODE);
/* Enable the pipe*/
if (dev_priv->iLVDS_enable)
PSB_WVDC32(dev_priv->savePIPEACONF, PIPEACONF);
/* Set up the plane*/
PSB_WVDC32(dev_priv->saveDSPALINOFF, DSPALINOFF);
PSB_WVDC32(dev_priv->saveDSPASTRIDE, DSPASTRIDE);
PSB_WVDC32(dev_priv->saveDSPATILEOFF, DSPATILEOFF);
/* Enable the plane */
PSB_WVDC32(dev_priv->saveDSPACNTR, DSPACNTR);
PSB_WVDC32(dev_priv->saveDSPASURF, DSPASURF);
/* Enable Cursor A */
PSB_WVDC32(dev_priv->saveDSPACURSOR_CTRL, CURACNTR);
PSB_WVDC32(dev_priv->saveDSPACURSOR_POS, CURAPOS);
PSB_WVDC32(dev_priv->saveDSPACURSOR_BASE, CURABASE);
/* Restore palette (gamma) */
for (i = 0; i < 256; i++)
PSB_WVDC32(dev_priv->save_palette_a[i], PALETTE_A + (i << 2));
if (dev_priv->hdmi_priv)
oaktrail_hdmi_restore(dev);
if (dev_priv->iLVDS_enable) {
PSB_WVDC32(dev_priv->saveBLC_PWM_CTL2, BLC_PWM_CTL2);
PSB_WVDC32(dev_priv->saveLVDS, LVDS); /*port 61180h*/
PSB_WVDC32(dev_priv->savePFIT_CONTROL, PFIT_CONTROL);
PSB_WVDC32(dev_priv->savePFIT_PGM_RATIOS, PFIT_PGM_RATIOS);
PSB_WVDC32(dev_priv->savePFIT_AUTO_RATIOS, PFIT_AUTO_RATIOS);
PSB_WVDC32(dev_priv->saveBLC_PWM_CTL, BLC_PWM_CTL);
PSB_WVDC32(dev_priv->savePP_ON_DELAYS, LVDSPP_ON);
PSB_WVDC32(dev_priv->savePP_OFF_DELAYS, LVDSPP_OFF);
PSB_WVDC32(dev_priv->savePP_DIVISOR, PP_CYCLE);
PSB_WVDC32(dev_priv->savePP_CONTROL, PP_CONTROL);
}
/* Wait for cycle delay */
do {
pp_stat = PSB_RVDC32(PP_STATUS);
} while (pp_stat & 0x08000000);
/* Wait for panel power up */
do {
pp_stat = PSB_RVDC32(PP_STATUS);
} while (pp_stat & 0x10000000);
/* Restore HW overlay */
PSB_WVDC32(dev_priv->saveOV_OVADD, OV_OVADD);
PSB_WVDC32(dev_priv->saveOV_OGAMC0, OV_OGAMC0);
PSB_WVDC32(dev_priv->saveOV_OGAMC1, OV_OGAMC1);
PSB_WVDC32(dev_priv->saveOV_OGAMC2, OV_OGAMC2);
PSB_WVDC32(dev_priv->saveOV_OGAMC3, OV_OGAMC3);
PSB_WVDC32(dev_priv->saveOV_OGAMC4, OV_OGAMC4);
PSB_WVDC32(dev_priv->saveOV_OGAMC5, OV_OGAMC5);
/* DPST registers */
PSB_WVDC32(dev_priv->saveHISTOGRAM_INT_CONTROL_REG,
HISTOGRAM_INT_CONTROL);
PSB_WVDC32(dev_priv->saveHISTOGRAM_LOGIC_CONTROL_REG,
HISTOGRAM_LOGIC_CONTROL);
PSB_WVDC32(dev_priv->savePWM_CONTROL_LOGIC, PWM_CONTROL_LOGIC);
return 0;
}
/**
* oaktrail_power_down - power down the display island
* @dev: our DRM device
*
* Power down the display interface of our device
*/
static int oaktrail_power_down(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
u32 pwr_mask ;
u32 pwr_sts;
pwr_mask = PSB_PWRGT_DISPLAY_MASK;
outl(pwr_mask, dev_priv->ospm_base + PSB_PM_SSC);
while (true) {
pwr_sts = inl(dev_priv->ospm_base + PSB_PM_SSS);
if ((pwr_sts & pwr_mask) == pwr_mask)
break;
else
udelay(10);
}
return 0;
}
/*
* oaktrail_power_up
*
* Restore power to the specified island(s) (powergating)
*/
static int oaktrail_power_up(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
u32 pwr_mask = PSB_PWRGT_DISPLAY_MASK;
u32 pwr_sts, pwr_cnt;
pwr_cnt = inl(dev_priv->ospm_base + PSB_PM_SSC);
pwr_cnt &= ~pwr_mask;
outl(pwr_cnt, (dev_priv->ospm_base + PSB_PM_SSC));
while (true) {
pwr_sts = inl(dev_priv->ospm_base + PSB_PM_SSS);
if ((pwr_sts & pwr_mask) == 0)
break;
else
udelay(10);
}
return 0;
}
static void oaktrail_teardown(struct drm_device *dev)
{
oaktrail_hdmi_teardown(dev);
}
const struct psb_ops oaktrail_chip_ops = {
.name = "Oaktrail",
.accel_2d = 1,
.pipes = 2,
.crtcs = 2,
.sgx_offset = MRST_SGX_OFFSET,
.chip_setup = mid_chip_setup,
.chip_teardown = oaktrail_teardown,
.crtc_helper = &oaktrail_helper_funcs,
.crtc_funcs = &psb_intel_crtc_funcs,
.output_init = oaktrail_output_init,
#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE
.backlight_init = oaktrail_backlight_init,
#endif
.init_pm = oaktrail_init_pm,
.save_regs = oaktrail_save_display_registers,
.restore_regs = oaktrail_restore_display_registers,
.power_down = oaktrail_power_down,
.power_up = oaktrail_power_up,
.i2c_bus = 1,
};
/*
* Copyright © 2010 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Li Peng <peng.li@intel.com>
*/
#include <drm/drmP.h>
#include <drm/drm.h>
#include "psb_intel_drv.h"
#include "psb_intel_reg.h"
#include "psb_drv.h"
#define HDMI_READ(reg) readl(hdmi_dev->regs + (reg))
#define HDMI_WRITE(reg, val) writel(val, hdmi_dev->regs + (reg))
#define HDMI_HCR 0x1000
#define HCR_ENABLE_HDCP (1 << 5)
#define HCR_ENABLE_AUDIO (1 << 2)
#define HCR_ENABLE_PIXEL (1 << 1)
#define HCR_ENABLE_TMDS (1 << 0)
#define HDMI_HICR 0x1004
#define HDMI_HSR 0x1008
#define HDMI_HISR 0x100C
#define HDMI_DETECT_HDP (1 << 0)
#define HDMI_VIDEO_REG 0x3000
#define HDMI_UNIT_EN (1 << 7)
#define HDMI_MODE_OUTPUT (1 << 0)
#define HDMI_HBLANK_A 0x3100
#define HDMI_AUDIO_CTRL 0x4000
#define HDMI_ENABLE_AUDIO (1 << 0)
#define PCH_HTOTAL_B 0x3100
#define PCH_HBLANK_B 0x3104
#define PCH_HSYNC_B 0x3108
#define PCH_VTOTAL_B 0x310C
#define PCH_VBLANK_B 0x3110
#define PCH_VSYNC_B 0x3114
#define PCH_PIPEBSRC 0x311C
#define PCH_PIPEB_DSL 0x3800
#define PCH_PIPEB_SLC 0x3804
#define PCH_PIPEBCONF 0x3808
#define PCH_PIPEBSTAT 0x3824
#define CDVO_DFT 0x5000
#define CDVO_SLEWRATE 0x5004
#define CDVO_STRENGTH 0x5008
#define CDVO_RCOMP 0x500C
#define DPLL_CTRL 0x6000
#define DPLL_PDIV_SHIFT 16
#define DPLL_PDIV_MASK (0xf << 16)
#define DPLL_PWRDN (1 << 4)
#define DPLL_RESET (1 << 3)
#define DPLL_FASTEN (1 << 2)
#define DPLL_ENSTAT (1 << 1)
#define DPLL_DITHEN (1 << 0)
#define DPLL_DIV_CTRL 0x6004
#define DPLL_CLKF_MASK 0xffffffc0
#define DPLL_CLKR_MASK (0x3f)
#define DPLL_CLK_ENABLE 0x6008
#define DPLL_EN_DISP (1 << 31)
#define DPLL_SEL_HDMI (1 << 8)
#define DPLL_EN_HDMI (1 << 1)
#define DPLL_EN_VGA (1 << 0)
#define DPLL_ADJUST 0x600C
#define DPLL_STATUS 0x6010
#define DPLL_UPDATE 0x6014
#define DPLL_DFT 0x6020
struct intel_range {
int min, max;
};
struct oaktrail_hdmi_limit {
struct intel_range vco, np, nr, nf;
};
struct oaktrail_hdmi_clock {
int np;
int nr;
int nf;
int dot;
};
#define VCO_MIN 320000
#define VCO_MAX 1650000
#define NP_MIN 1
#define NP_MAX 15
#define NR_MIN 1
#define NR_MAX 64
#define NF_MIN 2
#define NF_MAX 4095
static const struct oaktrail_hdmi_limit oaktrail_hdmi_limit = {
.vco = { .min = VCO_MIN, .max = VCO_MAX },
.np = { .min = NP_MIN, .max = NP_MAX },
.nr = { .min = NR_MIN, .max = NR_MAX },
.nf = { .min = NF_MIN, .max = NF_MAX },
};
static void wait_for_vblank(struct drm_device *dev)
{
/* FIXME: Can we do this as a sleep ? */
/* Wait for 20ms, i.e. one cycle at 50hz. */
mdelay(20);
}
static void scu_busy_loop(void *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;
}
}
}
static void oaktrail_hdmi_reset(struct drm_device *dev)
{
void *base;
/* FIXME: at least make these defines */
unsigned int scu_ipc_mmio = 0xff11c000;
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);
}
static void oaktrail_hdmi_audio_enable(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
HDMI_WRITE(HDMI_HCR, 0x67);
HDMI_READ(HDMI_HCR);
HDMI_WRITE(0x51a8, 0x10);
HDMI_READ(0x51a8);
HDMI_WRITE(HDMI_AUDIO_CTRL, 0x1);
HDMI_READ(HDMI_AUDIO_CTRL);
}
static void oaktrail_hdmi_audio_disable(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
HDMI_WRITE(0x51a8, 0x0);
HDMI_READ(0x51a8);
HDMI_WRITE(HDMI_AUDIO_CTRL, 0x0);
HDMI_READ(HDMI_AUDIO_CTRL);
HDMI_WRITE(HDMI_HCR, 0x47);
HDMI_READ(HDMI_HCR);
}
void oaktrail_crtc_hdmi_dpms(struct drm_crtc *crtc, int mode)
{
struct drm_device *dev = crtc->dev;
u32 temp;
switch (mode) {
case DRM_MODE_DPMS_OFF:
/* Disable VGACNTRL */
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 */
REG_WRITE(0x70400, 0x4000);
}
static void oaktrail_hdmi_dpms(struct drm_encoder *encoder, int mode)
{
static int dpms_mode = -1;
struct drm_device *dev = encoder->dev;
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
u32 temp;
if (dpms_mode == mode)
return;
if (mode != DRM_MODE_DPMS_ON)
temp = 0x0;
else
temp = 0x99;
dpms_mode = mode;
HDMI_WRITE(HDMI_VIDEO_REG, temp);
}
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;
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);
}
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;
/* Disable the VGA plane that we never use */
REG_WRITE(VGACNTRL, VGA_DISP_DISABLE);
/* XXX: Disable the panel fitter if it was on our pipe */
/* 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: FIXME - can we sort out the ioremap mess ? */
iounmap(hdmi_dev->regs);
oaktrail_hdmi_reset(dev);
/* program and enable dpll */
refclk = 25000;
oaktrail_hdmi_find_dpll(crtc, adjusted_mode->clock, refclk, &clock);
/* Setting 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);
hdmi_dev->regs = ioremap(hdmi_dev->mmio, hdmi_dev->mmio_len);
if (hdmi_dev->regs == NULL) {
DRM_ERROR("failed to do hdmi mmio mapping\n");
return -ENOMEM;
}
/* 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);
return 0;
}
static int oaktrail_hdmi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
if (mode->clock > 165000)
return MODE_CLOCK_HIGH;
if (mode->clock < 20000)
return MODE_CLOCK_LOW;
if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
return MODE_NO_DBLESCAN;
return MODE_OK;
}
static bool oaktrail_hdmi_mode_fixup(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
return true;
}
static enum drm_connector_status
oaktrail_hdmi_detect(struct drm_connector *connector, bool force)
{
enum drm_connector_status status;
struct drm_device *dev = connector->dev;
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
u32 temp;
temp = HDMI_READ(HDMI_HSR);
DRM_DEBUG_KMS("HDMI_HSR %x\n", temp);
if ((temp & HDMI_DETECT_HDP) != 0)
status = connector_status_connected;
else
status = connector_status_disconnected;
return status;
}
static const unsigned char raw_edid[] = {
0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x10, 0xac, 0x2f, 0xa0,
0x53, 0x55, 0x33, 0x30, 0x16, 0x13, 0x01, 0x03, 0x0e, 0x3a, 0x24, 0x78,
0xea, 0xe9, 0xf5, 0xac, 0x51, 0x30, 0xb4, 0x25, 0x11, 0x50, 0x54, 0xa5,
0x4b, 0x00, 0x81, 0x80, 0xa9, 0x40, 0x71, 0x4f, 0xb3, 0x00, 0x01, 0x01,
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x28, 0x3c, 0x80, 0xa0, 0x70, 0xb0,
0x23, 0x40, 0x30, 0x20, 0x36, 0x00, 0x46, 0x6c, 0x21, 0x00, 0x00, 0x1a,
0x00, 0x00, 0x00, 0xff, 0x00, 0x47, 0x4e, 0x37, 0x32, 0x31, 0x39, 0x35,
0x52, 0x30, 0x33, 0x55, 0x53, 0x0a, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x44,
0x45, 0x4c, 0x4c, 0x20, 0x32, 0x37, 0x30, 0x39, 0x57, 0x0a, 0x20, 0x20,
0x00, 0x00, 0x00, 0xfd, 0x00, 0x38, 0x4c, 0x1e, 0x53, 0x11, 0x00, 0x0a,
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x8d
};
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;
i2c_adap = i2c_get_adapter(3);
if (i2c_adap == NULL) {
DRM_ERROR("No ddc adapter available!\n");
edid = (struct edid *)raw_edid;
} else {
edid = (struct edid *)raw_edid;
/* FIXME ? edid = drm_get_edid(connector, i2c_adap); */
}
if (edid) {
drm_mode_connector_update_edid_property(connector, edid);
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;
}
static void oaktrail_hdmi_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct drm_device *dev = encoder->dev;
oaktrail_hdmi_audio_enable(dev);
return;
}
static void oaktrail_hdmi_destroy(struct drm_connector *connector)
{
return;
}
static const struct drm_encoder_helper_funcs oaktrail_hdmi_helper_funcs = {
.dpms = oaktrail_hdmi_dpms,
.mode_fixup = oaktrail_hdmi_mode_fixup,
.prepare = psb_intel_encoder_prepare,
.mode_set = oaktrail_hdmi_mode_set,
.commit = psb_intel_encoder_commit,
};
static const struct drm_connector_helper_funcs
oaktrail_hdmi_connector_helper_funcs = {
.get_modes = oaktrail_hdmi_get_modes,
.mode_valid = oaktrail_hdmi_mode_valid,
.best_encoder = psb_intel_best_encoder,
};
static const struct drm_connector_funcs oaktrail_hdmi_connector_funcs = {
.dpms = drm_helper_connector_dpms,
.detect = oaktrail_hdmi_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = oaktrail_hdmi_destroy,
};
static void oaktrail_hdmi_enc_destroy(struct drm_encoder *encoder)
{
drm_encoder_cleanup(encoder);
}
static const struct drm_encoder_funcs oaktrail_hdmi_enc_funcs = {
.destroy = oaktrail_hdmi_enc_destroy,
};
void oaktrail_hdmi_init(struct drm_device *dev,
struct psb_intel_mode_device *mode_dev)
{
struct psb_intel_output *psb_intel_output;
struct drm_connector *connector;
struct drm_encoder *encoder;
psb_intel_output = kzalloc(sizeof(struct psb_intel_output), GFP_KERNEL);
if (!psb_intel_output)
return;
psb_intel_output->mode_dev = mode_dev;
connector = &psb_intel_output->base;
encoder = &psb_intel_output->enc;
drm_connector_init(dev, &psb_intel_output->base,
&oaktrail_hdmi_connector_funcs,
DRM_MODE_CONNECTOR_DVID);
drm_encoder_init(dev, &psb_intel_output->enc,
&oaktrail_hdmi_enc_funcs,
DRM_MODE_ENCODER_TMDS);
drm_mode_connector_attach_encoder(&psb_intel_output->base,
&psb_intel_output->enc);
psb_intel_output->type = INTEL_OUTPUT_HDMI;
drm_encoder_helper_add(encoder, &oaktrail_hdmi_helper_funcs);
drm_connector_helper_add(connector, &oaktrail_hdmi_connector_helper_funcs);
connector->display_info.subpixel_order = SubPixelHorizontalRGB;
connector->interlace_allowed = false;
connector->doublescan_allowed = false;
drm_sysfs_connector_add(connector);
return;
}
static DEFINE_PCI_DEVICE_TABLE(hdmi_ids) = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, 0x080d) },
{}
};
void oaktrail_hdmi_setup(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
struct pci_dev *pdev;
struct oaktrail_hdmi_dev *hdmi_dev;
int ret;
pdev = pci_get_device(PCI_VENDOR_ID_INTEL, 0x080d, NULL);
if (!pdev)
return;
hdmi_dev = kzalloc(sizeof(struct oaktrail_hdmi_dev), GFP_KERNEL);
if (!hdmi_dev) {
dev_err(dev->dev, "failed to allocate memory\n");
goto out;
}
ret = pci_enable_device(pdev);
if (ret) {
dev_err(dev->dev, "failed to enable hdmi controller\n");
goto free;
}
hdmi_dev->mmio = pci_resource_start(pdev, 0);
hdmi_dev->mmio_len = pci_resource_len(pdev, 0);
hdmi_dev->regs = ioremap(hdmi_dev->mmio, hdmi_dev->mmio_len);
if (!hdmi_dev->regs) {
dev_err(dev->dev, "failed to map hdmi mmio\n");
goto free;
}
hdmi_dev->dev = pdev;
pci_set_drvdata(pdev, hdmi_dev);
/* Initialize i2c controller */
ret = oaktrail_hdmi_i2c_init(hdmi_dev->dev);
if (ret)
dev_err(dev->dev, "HDMI I2C initialization failed\n");
dev_priv->hdmi_priv = hdmi_dev;
oaktrail_hdmi_audio_disable(dev);
return;
free:
kfree(hdmi_dev);
out:
return;
}
void oaktrail_hdmi_teardown(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
struct pci_dev *pdev;
if (hdmi_dev) {
pdev = hdmi_dev->dev;
pci_set_drvdata(pdev, NULL);
oaktrail_hdmi_i2c_exit(pdev);
iounmap(hdmi_dev->regs);
kfree(hdmi_dev);
pci_dev_put(pdev);
}
}
/* save HDMI register state */
void oaktrail_hdmi_save(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
int i;
/* dpll */
hdmi_dev->saveDPLL_CTRL = PSB_RVDC32(DPLL_CTRL);
hdmi_dev->saveDPLL_DIV_CTRL = PSB_RVDC32(DPLL_DIV_CTRL);
hdmi_dev->saveDPLL_ADJUST = PSB_RVDC32(DPLL_ADJUST);
hdmi_dev->saveDPLL_UPDATE = PSB_RVDC32(DPLL_UPDATE);
hdmi_dev->saveDPLL_CLK_ENABLE = PSB_RVDC32(DPLL_CLK_ENABLE);
/* pipe B */
dev_priv->savePIPEBCONF = PSB_RVDC32(PIPEBCONF);
dev_priv->savePIPEBSRC = PSB_RVDC32(PIPEBSRC);
dev_priv->saveHTOTAL_B = PSB_RVDC32(HTOTAL_B);
dev_priv->saveHBLANK_B = PSB_RVDC32(HBLANK_B);
dev_priv->saveHSYNC_B = PSB_RVDC32(HSYNC_B);
dev_priv->saveVTOTAL_B = PSB_RVDC32(VTOTAL_B);
dev_priv->saveVBLANK_B = PSB_RVDC32(VBLANK_B);
dev_priv->saveVSYNC_B = PSB_RVDC32(VSYNC_B);
hdmi_dev->savePCH_PIPEBCONF = PSB_RVDC32(PCH_PIPEBCONF);
hdmi_dev->savePCH_PIPEBSRC = PSB_RVDC32(PCH_PIPEBSRC);
hdmi_dev->savePCH_HTOTAL_B = PSB_RVDC32(PCH_HTOTAL_B);
hdmi_dev->savePCH_HBLANK_B = PSB_RVDC32(PCH_HBLANK_B);
hdmi_dev->savePCH_HSYNC_B = PSB_RVDC32(PCH_HSYNC_B);
hdmi_dev->savePCH_VTOTAL_B = PSB_RVDC32(PCH_VTOTAL_B);
hdmi_dev->savePCH_VBLANK_B = PSB_RVDC32(PCH_VBLANK_B);
hdmi_dev->savePCH_VSYNC_B = PSB_RVDC32(PCH_VSYNC_B);
/* plane */
dev_priv->saveDSPBCNTR = PSB_RVDC32(DSPBCNTR);
dev_priv->saveDSPBSTRIDE = PSB_RVDC32(DSPBSTRIDE);
dev_priv->saveDSPBADDR = PSB_RVDC32(DSPBBASE);
dev_priv->saveDSPBSURF = PSB_RVDC32(DSPBSURF);
dev_priv->saveDSPBLINOFF = PSB_RVDC32(DSPBLINOFF);
dev_priv->saveDSPBTILEOFF = PSB_RVDC32(DSPBTILEOFF);
/* cursor B */
dev_priv->saveDSPBCURSOR_CTRL = PSB_RVDC32(CURBCNTR);
dev_priv->saveDSPBCURSOR_BASE = PSB_RVDC32(CURBBASE);
dev_priv->saveDSPBCURSOR_POS = PSB_RVDC32(CURBPOS);
/* save palette */
for (i = 0; i < 256; i++)
dev_priv->save_palette_b[i] = PSB_RVDC32(PALETTE_B + (i << 2));
}
/* restore HDMI register state */
void oaktrail_hdmi_restore(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_hdmi_dev *hdmi_dev = dev_priv->hdmi_priv;
int i;
/* dpll */
PSB_WVDC32(hdmi_dev->saveDPLL_CTRL, DPLL_CTRL);
PSB_WVDC32(hdmi_dev->saveDPLL_DIV_CTRL, DPLL_DIV_CTRL);
PSB_WVDC32(hdmi_dev->saveDPLL_ADJUST, DPLL_ADJUST);
PSB_WVDC32(hdmi_dev->saveDPLL_UPDATE, DPLL_UPDATE);
PSB_WVDC32(hdmi_dev->saveDPLL_CLK_ENABLE, DPLL_CLK_ENABLE);
DRM_UDELAY(150);
/* pipe */
PSB_WVDC32(dev_priv->savePIPEBSRC, PIPEBSRC);
PSB_WVDC32(dev_priv->saveHTOTAL_B, HTOTAL_B);
PSB_WVDC32(dev_priv->saveHBLANK_B, HBLANK_B);
PSB_WVDC32(dev_priv->saveHSYNC_B, HSYNC_B);
PSB_WVDC32(dev_priv->saveVTOTAL_B, VTOTAL_B);
PSB_WVDC32(dev_priv->saveVBLANK_B, VBLANK_B);
PSB_WVDC32(dev_priv->saveVSYNC_B, VSYNC_B);
PSB_WVDC32(hdmi_dev->savePCH_PIPEBSRC, PCH_PIPEBSRC);
PSB_WVDC32(hdmi_dev->savePCH_HTOTAL_B, PCH_HTOTAL_B);
PSB_WVDC32(hdmi_dev->savePCH_HBLANK_B, PCH_HBLANK_B);
PSB_WVDC32(hdmi_dev->savePCH_HSYNC_B, PCH_HSYNC_B);
PSB_WVDC32(hdmi_dev->savePCH_VTOTAL_B, PCH_VTOTAL_B);
PSB_WVDC32(hdmi_dev->savePCH_VBLANK_B, PCH_VBLANK_B);
PSB_WVDC32(hdmi_dev->savePCH_VSYNC_B, PCH_VSYNC_B);
PSB_WVDC32(dev_priv->savePIPEBCONF, PIPEBCONF);
PSB_WVDC32(hdmi_dev->savePCH_PIPEBCONF, PCH_PIPEBCONF);
/* plane */
PSB_WVDC32(dev_priv->saveDSPBLINOFF, DSPBLINOFF);
PSB_WVDC32(dev_priv->saveDSPBSTRIDE, DSPBSTRIDE);
PSB_WVDC32(dev_priv->saveDSPBTILEOFF, DSPBTILEOFF);
PSB_WVDC32(dev_priv->saveDSPBCNTR, DSPBCNTR);
PSB_WVDC32(dev_priv->saveDSPBSURF, DSPBSURF);
/* cursor B */
PSB_WVDC32(dev_priv->saveDSPBCURSOR_CTRL, CURBCNTR);
PSB_WVDC32(dev_priv->saveDSPBCURSOR_POS, CURBPOS);
PSB_WVDC32(dev_priv->saveDSPBCURSOR_BASE, CURBBASE);
/* restore palette */
for (i = 0; i < 256; i++)
PSB_WVDC32(dev_priv->save_palette_b[i], PALETTE_B + (i << 2));
}
/*
* Copyright © 2010 Intel Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Authors:
* Li Peng <peng.li@intel.com>
*/
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include "psb_drv.h"
#define HDMI_READ(reg) readl(hdmi_dev->regs + (reg))
#define HDMI_WRITE(reg, val) writel(val, hdmi_dev->regs + (reg))
#define HDMI_HCR 0x1000
#define HCR_DETECT_HDP (1 << 6)
#define HCR_ENABLE_HDCP (1 << 5)
#define HCR_ENABLE_AUDIO (1 << 2)
#define HCR_ENABLE_PIXEL (1 << 1)
#define HCR_ENABLE_TMDS (1 << 0)
#define HDMI_HICR 0x1004
#define HDMI_INTR_I2C_ERROR (1 << 4)
#define HDMI_INTR_I2C_FULL (1 << 3)
#define HDMI_INTR_I2C_DONE (1 << 2)
#define HDMI_INTR_HPD (1 << 0)
#define HDMI_HSR 0x1008
#define HDMI_HISR 0x100C
#define HDMI_HI2CRDB0 0x1200
#define HDMI_HI2CHCR 0x1240
#define HI2C_HDCP_WRITE (0 << 2)
#define HI2C_HDCP_RI_READ (1 << 2)
#define HI2C_HDCP_READ (2 << 2)
#define HI2C_EDID_READ (3 << 2)
#define HI2C_READ_CONTINUE (1 << 1)
#define HI2C_ENABLE_TRANSACTION (1 << 0)
#define HDMI_ICRH 0x1100
#define HDMI_HI2CTDR0 0x1244
#define HDMI_HI2CTDR1 0x1248
#define I2C_STAT_INIT 0
#define I2C_READ_DONE 1
#define I2C_TRANSACTION_DONE 2
struct hdmi_i2c_dev {
struct i2c_adapter *adap;
struct mutex i2c_lock;
struct completion complete;
int status;
struct i2c_msg *msg;
int buf_offset;
};
static void hdmi_i2c_irq_enable(struct oaktrail_hdmi_dev *hdmi_dev)
{
u32 temp;
temp = HDMI_READ(HDMI_HICR);
temp |= (HDMI_INTR_I2C_ERROR | HDMI_INTR_I2C_FULL | HDMI_INTR_I2C_DONE);
HDMI_WRITE(HDMI_HICR, temp);
HDMI_READ(HDMI_HICR);
}
static void hdmi_i2c_irq_disable(struct oaktrail_hdmi_dev *hdmi_dev)
{
HDMI_WRITE(HDMI_HICR, 0x0);
HDMI_READ(HDMI_HICR);
}
static int xfer_read(struct i2c_adapter *adap, struct i2c_msg *pmsg)
{
struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap);
struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev;
u32 temp;
i2c_dev->status = I2C_STAT_INIT;
i2c_dev->msg = pmsg;
i2c_dev->buf_offset = 0;
INIT_COMPLETION(i2c_dev->complete);
/* Enable I2C transaction */
temp = ((pmsg->len) << 20) | HI2C_EDID_READ | HI2C_ENABLE_TRANSACTION;
HDMI_WRITE(HDMI_HI2CHCR, temp);
HDMI_READ(HDMI_HI2CHCR);
while (i2c_dev->status != I2C_TRANSACTION_DONE)
wait_for_completion_interruptible_timeout(&i2c_dev->complete,
10 * HZ);
return 0;
}
static int xfer_write(struct i2c_adapter *adap, struct i2c_msg *pmsg)
{
/*
* XXX: i2c write seems isn't useful for EDID probe, don't do anything
*/
return 0;
}
static int oaktrail_hdmi_i2c_access(struct i2c_adapter *adap,
struct i2c_msg *pmsg,
int num)
{
struct oaktrail_hdmi_dev *hdmi_dev = i2c_get_adapdata(adap);
struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev;
int i, err = 0;
mutex_lock(&i2c_dev->i2c_lock);
/* Enable i2c unit */
HDMI_WRITE(HDMI_ICRH, 0x00008760);
/* Enable irq */
hdmi_i2c_irq_enable(hdmi_dev);
for (i = 0; i < num; i++) {
if (pmsg->len && pmsg->buf) {
if (pmsg->flags & I2C_M_RD)
err = xfer_read(adap, pmsg);
else
err = xfer_write(adap, pmsg);
}
pmsg++; /* next message */
}
/* Disable irq */
hdmi_i2c_irq_disable(hdmi_dev);
mutex_unlock(&i2c_dev->i2c_lock);
return i;
}
static u32 oaktrail_hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_10BIT_ADDR;
}
static const struct i2c_algorithm oaktrail_hdmi_i2c_algorithm = {
.master_xfer = oaktrail_hdmi_i2c_access,
.functionality = oaktrail_hdmi_i2c_func,
};
static struct i2c_adapter oaktrail_hdmi_i2c_adapter = {
.name = "oaktrail_hdmi_i2c",
.nr = 3,
.owner = THIS_MODULE,
.class = I2C_CLASS_DDC,
.algo = &oaktrail_hdmi_i2c_algorithm,
};
static void hdmi_i2c_read(struct oaktrail_hdmi_dev *hdmi_dev)
{
struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev;
struct i2c_msg *msg = i2c_dev->msg;
u8 *buf = msg->buf;
u32 temp;
int i, offset;
offset = i2c_dev->buf_offset;
for (i = 0; i < 0x10; i++) {
temp = HDMI_READ(HDMI_HI2CRDB0 + (i * 4));
memcpy(buf + (offset + i * 4), &temp, 4);
}
i2c_dev->buf_offset += (0x10 * 4);
/* clearing read buffer full intr */
temp = HDMI_READ(HDMI_HISR);
HDMI_WRITE(HDMI_HISR, temp | HDMI_INTR_I2C_FULL);
HDMI_READ(HDMI_HISR);
/* continue read transaction */
temp = HDMI_READ(HDMI_HI2CHCR);
HDMI_WRITE(HDMI_HI2CHCR, temp | HI2C_READ_CONTINUE);
HDMI_READ(HDMI_HI2CHCR);
i2c_dev->status = I2C_READ_DONE;
return;
}
static void hdmi_i2c_transaction_done(struct oaktrail_hdmi_dev *hdmi_dev)
{
struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev;
u32 temp;
/* clear transaction done intr */
temp = HDMI_READ(HDMI_HISR);
HDMI_WRITE(HDMI_HISR, temp | HDMI_INTR_I2C_DONE);
HDMI_READ(HDMI_HISR);
temp = HDMI_READ(HDMI_HI2CHCR);
HDMI_WRITE(HDMI_HI2CHCR, temp & ~HI2C_ENABLE_TRANSACTION);
HDMI_READ(HDMI_HI2CHCR);
i2c_dev->status = I2C_TRANSACTION_DONE;
return;
}
static irqreturn_t oaktrail_hdmi_i2c_handler(int this_irq, void *dev)
{
struct oaktrail_hdmi_dev *hdmi_dev = dev;
struct hdmi_i2c_dev *i2c_dev = hdmi_dev->i2c_dev;
u32 stat;
stat = HDMI_READ(HDMI_HISR);
if (stat & HDMI_INTR_HPD) {
HDMI_WRITE(HDMI_HISR, stat | HDMI_INTR_HPD);
HDMI_READ(HDMI_HISR);
}
if (stat & HDMI_INTR_I2C_FULL)
hdmi_i2c_read(hdmi_dev);
if (stat & HDMI_INTR_I2C_DONE)
hdmi_i2c_transaction_done(hdmi_dev);
complete(&i2c_dev->complete);
return IRQ_HANDLED;
}
/*
* choose alternate function 2 of GPIO pin 52, 53,
* which is used by HDMI I2C logic
*/
static void oaktrail_hdmi_i2c_gpio_fix(void)
{
void *base;
unsigned int gpio_base = 0xff12c000;
int gpio_len = 0x1000;
u32 temp;
base = ioremap((resource_size_t)gpio_base, gpio_len);
if (base == NULL) {
DRM_ERROR("gpio ioremap fail\n");
return;
}
temp = readl(base + 0x44);
DRM_DEBUG_DRIVER("old gpio val %x\n", temp);
writel((temp | 0x00000a00), (base + 0x44));
temp = readl(base + 0x44);
DRM_DEBUG_DRIVER("new gpio val %x\n", temp);
iounmap(base);
}
int oaktrail_hdmi_i2c_init(struct pci_dev *dev)
{
struct oaktrail_hdmi_dev *hdmi_dev;
struct hdmi_i2c_dev *i2c_dev;
int ret;
hdmi_dev = pci_get_drvdata(dev);
i2c_dev = kzalloc(sizeof(struct hdmi_i2c_dev), GFP_KERNEL);
if (i2c_dev == NULL) {
DRM_ERROR("Can't allocate interface\n");
ret = -ENOMEM;
goto exit;
}
i2c_dev->adap = &oaktrail_hdmi_i2c_adapter;
i2c_dev->status = I2C_STAT_INIT;
init_completion(&i2c_dev->complete);
mutex_init(&i2c_dev->i2c_lock);
i2c_set_adapdata(&oaktrail_hdmi_i2c_adapter, hdmi_dev);
hdmi_dev->i2c_dev = i2c_dev;
/* Enable HDMI I2C function on gpio */
oaktrail_hdmi_i2c_gpio_fix();
/* request irq */
ret = request_irq(dev->irq, oaktrail_hdmi_i2c_handler, IRQF_SHARED,
oaktrail_hdmi_i2c_adapter.name, hdmi_dev);
if (ret) {
DRM_ERROR("Failed to request IRQ for I2C controller\n");
goto err;
}
/* Adapter registration */
ret = i2c_add_numbered_adapter(&oaktrail_hdmi_i2c_adapter);
return ret;
err:
kfree(i2c_dev);
exit:
return ret;
}
void oaktrail_hdmi_i2c_exit(struct pci_dev *dev)
{
struct oaktrail_hdmi_dev *hdmi_dev;
struct hdmi_i2c_dev *i2c_dev;
hdmi_dev = pci_get_drvdata(dev);
if (i2c_del_adapter(&oaktrail_hdmi_i2c_adapter))
DRM_DEBUG_DRIVER("Failed to delete hdmi-i2c adapter\n");
i2c_dev = hdmi_dev->i2c_dev;
kfree(i2c_dev);
free_irq(dev->irq, hdmi_dev);
}
/*
* Copyright © 2006-2009 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
* Authors:
* Eric Anholt <eric@anholt.net>
* Dave Airlie <airlied@linux.ie>
* Jesse Barnes <jesse.barnes@intel.com>
*/
#include <linux/i2c.h>
#include <drm/drmP.h>
#include <asm/mrst.h>
#include "intel_bios.h"
#include "psb_drv.h"
#include "psb_intel_drv.h"
#include "psb_intel_reg.h"
#include "power.h"
#include <linux/pm_runtime.h>
/* The max/min PWM frequency in BPCR[31:17] - */
/* The smallest number is 1 (not 0) that can fit in the
* 15-bit field of the and then*/
/* shifts to the left by one bit to get the actual 16-bit
* value that the 15-bits correspond to.*/
#define MRST_BLC_MAX_PWM_REG_FREQ 0xFFFF
#define BRIGHTNESS_MAX_LEVEL 100
/**
* Sets the power state for the panel.
*/
static void oaktrail_lvds_set_power(struct drm_device *dev,
struct psb_intel_output *output, bool on)
{
u32 pp_status;
struct drm_psb_private *dev_priv = dev->dev_private;
if (!gma_power_begin(dev, true))
return;
if (on) {
REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) |
POWER_TARGET_ON);
do {
pp_status = REG_READ(PP_STATUS);
} while ((pp_status & (PP_ON | PP_READY)) == PP_READY);
dev_priv->is_lvds_on = true;
if (dev_priv->ops->lvds_bl_power)
dev_priv->ops->lvds_bl_power(dev, true);
} else {
if (dev_priv->ops->lvds_bl_power)
dev_priv->ops->lvds_bl_power(dev, false);
REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) &
~POWER_TARGET_ON);
do {
pp_status = REG_READ(PP_STATUS);
} while (pp_status & PP_ON);
dev_priv->is_lvds_on = false;
pm_request_idle(&dev->pdev->dev);
}
gma_power_end(dev);
}
static void oaktrail_lvds_dpms(struct drm_encoder *encoder, int mode)
{
struct drm_device *dev = encoder->dev;
struct psb_intel_output *output = enc_to_psb_intel_output(encoder);
if (mode == DRM_MODE_DPMS_ON)
oaktrail_lvds_set_power(dev, output, true);
else
oaktrail_lvds_set_power(dev, output, false);
/* XXX: We never power down the LVDS pairs. */
}
static void oaktrail_lvds_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct psb_intel_mode_device *mode_dev =
enc_to_psb_intel_output(encoder)->mode_dev;
struct drm_device *dev = encoder->dev;
struct drm_psb_private *dev_priv = dev->dev_private;
u32 lvds_port;
uint64_t v = DRM_MODE_SCALE_FULLSCREEN;
if (!gma_power_begin(dev, true))
return;
/*
* The LVDS pin pair will already have been turned on in the
* psb_intel_crtc_mode_set since it has a large impact on the DPLL
* settings.
*/
lvds_port = (REG_READ(LVDS) &
(~LVDS_PIPEB_SELECT)) |
LVDS_PORT_EN |
LVDS_BORDER_EN;
/* If the firmware says dither on Moorestown, or the BIOS does
on Oaktrail then enable dithering */
if (mode_dev->panel_wants_dither || dev_priv->lvds_dither)
lvds_port |= MRST_PANEL_8TO6_DITHER_ENABLE;
REG_WRITE(LVDS, lvds_port);
drm_connector_property_get_value(
&enc_to_psb_intel_output(encoder)->base,
dev->mode_config.scaling_mode_property,
&v);
if (v == DRM_MODE_SCALE_NO_SCALE)
REG_WRITE(PFIT_CONTROL, 0);
else if (v == DRM_MODE_SCALE_ASPECT) {
if ((mode->vdisplay != adjusted_mode->crtc_vdisplay) ||
(mode->hdisplay != adjusted_mode->crtc_hdisplay)) {
if ((adjusted_mode->crtc_hdisplay * mode->vdisplay) ==
(mode->hdisplay * adjusted_mode->crtc_vdisplay))
REG_WRITE(PFIT_CONTROL, PFIT_ENABLE);
else if ((adjusted_mode->crtc_hdisplay *
mode->vdisplay) > (mode->hdisplay *
adjusted_mode->crtc_vdisplay))
REG_WRITE(PFIT_CONTROL, PFIT_ENABLE |
PFIT_SCALING_MODE_PILLARBOX);
else
REG_WRITE(PFIT_CONTROL, PFIT_ENABLE |
PFIT_SCALING_MODE_LETTERBOX);
} else
REG_WRITE(PFIT_CONTROL, PFIT_ENABLE);
} else /*(v == DRM_MODE_SCALE_FULLSCREEN)*/
REG_WRITE(PFIT_CONTROL, PFIT_ENABLE);
gma_power_end(dev);
}
static void oaktrail_lvds_prepare(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct psb_intel_output *output = enc_to_psb_intel_output(encoder);
struct psb_intel_mode_device *mode_dev = output->mode_dev;
if (!gma_power_begin(dev, true))
return;
mode_dev->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL);
mode_dev->backlight_duty_cycle = (mode_dev->saveBLC_PWM_CTL &
BACKLIGHT_DUTY_CYCLE_MASK);
oaktrail_lvds_set_power(dev, output, false);
gma_power_end(dev);
}
static u32 oaktrail_lvds_get_max_backlight(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = dev->dev_private;
u32 ret;
if (gma_power_begin(dev, false)) {
ret = ((REG_READ(BLC_PWM_CTL) &
BACKLIGHT_MODULATION_FREQ_MASK) >>
BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
gma_power_end(dev);
} else
ret = ((dev_priv->saveBLC_PWM_CTL &
BACKLIGHT_MODULATION_FREQ_MASK) >>
BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
return ret;
}
static void oaktrail_lvds_commit(struct drm_encoder *encoder)
{
struct drm_device *dev = encoder->dev;
struct psb_intel_output *output = enc_to_psb_intel_output(encoder);
struct psb_intel_mode_device *mode_dev = output->mode_dev;
if (mode_dev->backlight_duty_cycle == 0)
mode_dev->backlight_duty_cycle =
oaktrail_lvds_get_max_backlight(dev);
oaktrail_lvds_set_power(dev, output, true);
}
static const struct drm_encoder_helper_funcs oaktrail_lvds_helper_funcs = {
.dpms = oaktrail_lvds_dpms,
.mode_fixup = psb_intel_lvds_mode_fixup,
.prepare = oaktrail_lvds_prepare,
.mode_set = oaktrail_lvds_mode_set,
.commit = oaktrail_lvds_commit,
};
static struct drm_display_mode lvds_configuration_modes[] = {
/* hard coded fixed mode for TPO LTPS LPJ040K001A */
{ DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER, 33264, 800, 836,
846, 1056, 0, 480, 489, 491, 525, 0, 0) },
/* hard coded fixed mode for LVDS 800x480 */
{ DRM_MODE("800x480", DRM_MODE_TYPE_DRIVER, 30994, 800, 801,
802, 1024, 0, 480, 481, 482, 525, 0, 0) },
/* hard coded fixed mode for Samsung 480wsvga LVDS 1024x600@75 */
{ DRM_MODE("1024x600", DRM_MODE_TYPE_DRIVER, 53990, 1024, 1072,
1104, 1184, 0, 600, 603, 604, 608, 0, 0) },
/* hard coded fixed mode for Samsung 480wsvga LVDS 1024x600@75 */
{ DRM_MODE("1024x600", DRM_MODE_TYPE_DRIVER, 53990, 1024, 1104,
1136, 1184, 0, 600, 603, 604, 608, 0, 0) },
/* hard coded fixed mode for Sharp wsvga LVDS 1024x600 */
{ DRM_MODE("1024x600", DRM_MODE_TYPE_DRIVER, 48885, 1024, 1124,
1204, 1312, 0, 600, 607, 610, 621, 0, 0) },
/* hard coded fixed mode for LVDS 1024x768 */
{ DRM_MODE("1024x768", DRM_MODE_TYPE_DRIVER, 65000, 1024, 1048,
1184, 1344, 0, 768, 771, 777, 806, 0, 0) },
/* hard coded fixed mode for LVDS 1366x768 */
{ DRM_MODE("1366x768", DRM_MODE_TYPE_DRIVER, 77500, 1366, 1430,
1558, 1664, 0, 768, 769, 770, 776, 0, 0) },
};
/* Returns the panel fixed mode from configuration. */
static struct drm_display_mode *
oaktrail_lvds_get_configuration_mode(struct drm_device *dev)
{
struct drm_display_mode *mode = NULL;
struct drm_psb_private *dev_priv = dev->dev_private;
struct oaktrail_timing_info *ti = &dev_priv->gct_data.DTD;
if (dev_priv->vbt_data.size != 0x00) { /*if non-zero, then use vbt*/
mode = kzalloc(sizeof(*mode), GFP_KERNEL);
if (!mode)
return NULL;
mode->hdisplay = (ti->hactive_hi << 8) | ti->hactive_lo;
mode->vdisplay = (ti->vactive_hi << 8) | ti->vactive_lo;
mode->hsync_start = mode->hdisplay + \
((ti->hsync_offset_hi << 8) | \
ti->hsync_offset_lo);
mode->hsync_end = mode->hsync_start + \
((ti->hsync_pulse_width_hi << 8) | \
ti->hsync_pulse_width_lo);
mode->htotal = mode->hdisplay + ((ti->hblank_hi << 8) | \
ti->hblank_lo);
mode->vsync_start = \
mode->vdisplay + ((ti->vsync_offset_hi << 4) | \
ti->vsync_offset_lo);
mode->vsync_end = \
mode->vsync_start + ((ti->vsync_pulse_width_hi << 4) | \
ti->vsync_pulse_width_lo);
mode->vtotal = mode->vdisplay + \
((ti->vblank_hi << 8) | ti->vblank_lo);
mode->clock = ti->pixel_clock * 10;
#if 0
printk(KERN_INFO "hdisplay is %d\n", mode->hdisplay);
printk(KERN_INFO "vdisplay is %d\n", mode->vdisplay);
printk(KERN_INFO "HSS is %d\n", mode->hsync_start);
printk(KERN_INFO "HSE is %d\n", mode->hsync_end);
printk(KERN_INFO "htotal is %d\n", mode->htotal);
printk(KERN_INFO "VSS is %d\n", mode->vsync_start);
printk(KERN_INFO "VSE is %d\n", mode->vsync_end);
printk(KERN_INFO "vtotal is %d\n", mode->vtotal);
printk(KERN_INFO "clock is %d\n", mode->clock);
#endif
} else
mode = drm_mode_duplicate(dev, &lvds_configuration_modes[2]);
drm_mode_set_name(mode);
drm_mode_set_crtcinfo(mode, 0);
return mode;
}
/**
* oaktrail_lvds_init - setup LVDS connectors on this device
* @dev: drm device
*
* Create the connector, register the LVDS DDC bus, and try to figure out what
* modes we can display on the LVDS panel (if present).
*/
void oaktrail_lvds_init(struct drm_device *dev,
struct psb_intel_mode_device *mode_dev)
{
struct psb_intel_output *psb_intel_output;
struct drm_connector *connector;
struct drm_encoder *encoder;
struct drm_psb_private *dev_priv =
(struct drm_psb_private *) dev->dev_private;
struct edid *edid;
int ret = 0;
struct i2c_adapter *i2c_adap;
struct drm_display_mode *scan; /* *modes, *bios_mode; */
psb_intel_output = kzalloc(sizeof(struct psb_intel_output), GFP_KERNEL);
if (!psb_intel_output)
return;
psb_intel_output->mode_dev = mode_dev;
connector = &psb_intel_output->base;
encoder = &psb_intel_output->enc;
dev_priv->is_lvds_on = true;
drm_connector_init(dev, &psb_intel_output->base,
&psb_intel_lvds_connector_funcs,
DRM_MODE_CONNECTOR_LVDS);
drm_encoder_init(dev, &psb_intel_output->enc, &psb_intel_lvds_enc_funcs,
DRM_MODE_ENCODER_LVDS);
drm_mode_connector_attach_encoder(&psb_intel_output->base,
&psb_intel_output->enc);
psb_intel_output->type = INTEL_OUTPUT_LVDS;
drm_encoder_helper_add(encoder, &oaktrail_lvds_helper_funcs);
drm_connector_helper_add(connector,
&psb_intel_lvds_connector_helper_funcs);
connector->display_info.subpixel_order = SubPixelHorizontalRGB;
connector->interlace_allowed = false;
connector->doublescan_allowed = false;
drm_connector_attach_property(connector,
dev->mode_config.scaling_mode_property,
DRM_MODE_SCALE_FULLSCREEN);
drm_connector_attach_property(connector,
dev_priv->backlight_property,
BRIGHTNESS_MAX_LEVEL);
mode_dev->panel_wants_dither = false;
if (dev_priv->vbt_data.size != 0x00)
mode_dev->panel_wants_dither = (dev_priv->gct_data.
Panel_Port_Control & MRST_PANEL_8TO6_DITHER_ENABLE);
/*
* LVDS discovery:
* 1) check for EDID on DDC
* 2) check for VBT data
* 3) check to see if LVDS is already on
* if none of the above, no panel
* 4) make sure lid is open
* if closed, act like it's not there for now
*/
i2c_adap = i2c_get_adapter(dev_priv->ops->i2c_bus);
if (i2c_adap == NULL)
dev_err(dev->dev, "No ddc adapter available!\n");
/*
* Attempt to get the fixed panel mode from DDC. Assume that the
* preferred mode is the right one.
*/
if (i2c_adap) {
edid = drm_get_edid(connector, i2c_adap);
if (edid) {
drm_mode_connector_update_edid_property(connector,
edid);
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
}
list_for_each_entry(scan, &connector->probed_modes, head) {
if (scan->type & DRM_MODE_TYPE_PREFERRED) {
mode_dev->panel_fixed_mode =
drm_mode_duplicate(dev, scan);
goto out; /* FIXME: check for quirks */
}
}
}
/*
* If we didn't get EDID, try geting panel timing
* from configuration data
*/
mode_dev->panel_fixed_mode = oaktrail_lvds_get_configuration_mode(dev);
if (mode_dev->panel_fixed_mode) {
mode_dev->panel_fixed_mode->type |= DRM_MODE_TYPE_PREFERRED;
goto out; /* FIXME: check for quirks */
}
/* If we still don't have a mode after all that, give up. */
if (!mode_dev->panel_fixed_mode) {
dev_err(dev->dev, "Found no modes on the lvds, ignoring the LVDS\n");
goto failed_find;
}
out:
drm_sysfs_connector_add(connector);
return;
failed_find:
dev_dbg(dev->dev, "No LVDS modes found, disabling.\n");
if (psb_intel_output->ddc_bus)
psb_intel_i2c_destroy(psb_intel_output->ddc_bus);
/* failed_ddc: */
drm_encoder_cleanup(encoder);
drm_connector_cleanup(connector);
kfree(connector);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment