| /************************************************************************** | |
| * 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 "psb_drv.h" | |
| #include "mid_bios.h" | |
| #include "mdfld_output.h" | |
| #include "mdfld_dsi_output.h" | |
| #include "tc35876x-dsi-lvds.h" | |
| #include <asm/intel_scu_ipc.h> | |
| #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 BRIGHTNESS_MIN_LEVEL 1 | |
| #define BRIGHTNESS_MAX_LEVEL 100 | |
| #define BRIGHTNESS_MASK 0xFF | |
| #define BLC_POLARITY_NORMAL 0 | |
| #define BLC_POLARITY_INVERSE 1 | |
| #define BLC_ADJUSTMENT_MAX 100 | |
| #define MDFLD_BLC_PWM_PRECISION_FACTOR 10 | |
| #define MDFLD_BLC_MAX_PWM_REG_FREQ 0xFFFE | |
| #define MDFLD_BLC_MIN_PWM_REG_FREQ 0x2 | |
| #define MDFLD_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) | |
| #define MDFLD_BACKLIGHT_PWM_CTL_SHIFT (16) | |
| static struct backlight_device *mdfld_backlight_device; | |
| int mdfld_set_brightness(struct backlight_device *bd) | |
| { | |
| struct drm_device *dev = | |
| (struct drm_device *)bl_get_data(mdfld_backlight_device); | |
| struct drm_psb_private *dev_priv = dev->dev_private; | |
| int level = bd->props.brightness; | |
| DRM_DEBUG_DRIVER("backlight level set to %d\n", level); | |
| /* Perform value bounds checking */ | |
| if (level < BRIGHTNESS_MIN_LEVEL) | |
| level = BRIGHTNESS_MIN_LEVEL; | |
| if (gma_power_begin(dev, false)) { | |
| u32 adjusted_level = 0; | |
| /* | |
| * Adjust the backlight level with the percent in | |
| * dev_priv->blc_adj2 | |
| */ | |
| adjusted_level = level * dev_priv->blc_adj2; | |
| adjusted_level = adjusted_level / BLC_ADJUSTMENT_MAX; | |
| dev_priv->brightness_adjusted = adjusted_level; | |
| if (mdfld_get_panel_type(dev, 0) == TC35876X) { | |
| if (dev_priv->dpi_panel_on[0] || | |
| dev_priv->dpi_panel_on[2]) | |
| tc35876x_brightness_control(dev, | |
| dev_priv->brightness_adjusted); | |
| } else { | |
| if (dev_priv->dpi_panel_on[0]) | |
| mdfld_dsi_brightness_control(dev, 0, | |
| dev_priv->brightness_adjusted); | |
| } | |
| if (dev_priv->dpi_panel_on[2]) | |
| mdfld_dsi_brightness_control(dev, 2, | |
| dev_priv->brightness_adjusted); | |
| gma_power_end(dev); | |
| } | |
| /* cache the brightness for later use */ | |
| dev_priv->brightness = level; | |
| return 0; | |
| } | |
| static int mdfld_get_brightness(struct backlight_device *bd) | |
| { | |
| struct drm_device *dev = | |
| (struct drm_device *)bl_get_data(mdfld_backlight_device); | |
| struct drm_psb_private *dev_priv = dev->dev_private; | |
| DRM_DEBUG_DRIVER("brightness = 0x%x \n", dev_priv->brightness); | |
| /* return locally cached var instead of HW read (due to DPST etc.) */ | |
| return dev_priv->brightness; | |
| } | |
| static const struct backlight_ops mdfld_ops = { | |
| .get_brightness = mdfld_get_brightness, | |
| .update_status = mdfld_set_brightness, | |
| }; | |
| static int device_backlight_init(struct drm_device *dev) | |
| { | |
| struct drm_psb_private *dev_priv = (struct drm_psb_private *) | |
| dev->dev_private; | |
| dev_priv->blc_adj1 = BLC_ADJUSTMENT_MAX; | |
| dev_priv->blc_adj2 = BLC_ADJUSTMENT_MAX; | |
| return 0; | |
| } | |
| static int mdfld_backlight_init(struct drm_device *dev) | |
| { | |
| struct backlight_properties props; | |
| int ret = 0; | |
| memset(&props, 0, sizeof(struct backlight_properties)); | |
| props.max_brightness = BRIGHTNESS_MAX_LEVEL; | |
| props.type = BACKLIGHT_PLATFORM; | |
| mdfld_backlight_device = backlight_device_register("mdfld-bl", | |
| NULL, (void *)dev, &mdfld_ops, &props); | |
| if (IS_ERR(mdfld_backlight_device)) | |
| return PTR_ERR(mdfld_backlight_device); | |
| ret = device_backlight_init(dev); | |
| if (ret) | |
| return ret; | |
| mdfld_backlight_device->props.brightness = BRIGHTNESS_MAX_LEVEL; | |
| mdfld_backlight_device->props.max_brightness = BRIGHTNESS_MAX_LEVEL; | |
| backlight_update_status(mdfld_backlight_device); | |
| return 0; | |
| } | |
| #endif | |
| struct backlight_device *mdfld_get_backlight_device(void) | |
| { | |
| #ifdef CONFIG_BACKLIGHT_CLASS_DEVICE | |
| return mdfld_backlight_device; | |
| #else | |
| return NULL; | |
| #endif | |
| } | |
| /* | |
| * mdfld_save_display_registers | |
| * | |
| * Description: We are going to suspend so save current display | |
| * register state. | |
| * | |
| * Notes: FIXME_JLIU7 need to add the support for DPI MIPI & HDMI audio | |
| */ | |
| static int mdfld_save_display_registers(struct drm_device *dev, int pipenum) | |
| { | |
| struct drm_psb_private *dev_priv = dev->dev_private; | |
| struct medfield_state *regs = &dev_priv->regs.mdfld; | |
| struct psb_pipe *pipe = &dev_priv->regs.pipe[pipenum]; | |
| const struct psb_offset *map = &dev_priv->regmap[pipenum]; | |
| int i; | |
| u32 *mipi_val; | |
| /* register */ | |
| u32 mipi_reg = MIPI; | |
| switch (pipenum) { | |
| case 0: | |
| mipi_val = ®s->saveMIPI; | |
| break; | |
| case 1: | |
| mipi_val = ®s->saveMIPI; | |
| break; | |
| case 2: | |
| /* register */ | |
| mipi_reg = MIPI_C; | |
| /* pointer to values */ | |
| mipi_val = ®s->saveMIPI_C; | |
| break; | |
| default: | |
| DRM_ERROR("%s, invalid pipe number.\n", __func__); | |
| return -EINVAL; | |
| } | |
| /* Pipe & plane A info */ | |
| pipe->dpll = PSB_RVDC32(map->dpll); | |
| pipe->fp0 = PSB_RVDC32(map->fp0); | |
| pipe->conf = PSB_RVDC32(map->conf); | |
| pipe->htotal = PSB_RVDC32(map->htotal); | |
| pipe->hblank = PSB_RVDC32(map->hblank); | |
| pipe->hsync = PSB_RVDC32(map->hsync); | |
| pipe->vtotal = PSB_RVDC32(map->vtotal); | |
| pipe->vblank = PSB_RVDC32(map->vblank); | |
| pipe->vsync = PSB_RVDC32(map->vsync); | |
| pipe->src = PSB_RVDC32(map->src); | |
| pipe->stride = PSB_RVDC32(map->stride); | |
| pipe->linoff = PSB_RVDC32(map->linoff); | |
| pipe->tileoff = PSB_RVDC32(map->tileoff); | |
| pipe->size = PSB_RVDC32(map->size); | |
| pipe->pos = PSB_RVDC32(map->pos); | |
| pipe->surf = PSB_RVDC32(map->surf); | |
| pipe->cntr = PSB_RVDC32(map->cntr); | |
| pipe->status = PSB_RVDC32(map->status); | |
| /*save palette (gamma) */ | |
| for (i = 0; i < 256; i++) | |
| pipe->palette[i] = PSB_RVDC32(map->palette + (i << 2)); | |
| if (pipenum == 1) { | |
| regs->savePFIT_CONTROL = PSB_RVDC32(PFIT_CONTROL); | |
| regs->savePFIT_PGM_RATIOS = PSB_RVDC32(PFIT_PGM_RATIOS); | |
| regs->saveHDMIPHYMISCCTL = PSB_RVDC32(HDMIPHYMISCCTL); | |
| regs->saveHDMIB_CONTROL = PSB_RVDC32(HDMIB_CONTROL); | |
| return 0; | |
| } | |
| *mipi_val = PSB_RVDC32(mipi_reg); | |
| return 0; | |
| } | |
| /* | |
| * mdfld_restore_display_registers | |
| * | |
| * Description: We are going to resume so restore display register state. | |
| * | |
| * Notes: FIXME_JLIU7 need to add the support for DPI MIPI & HDMI audio | |
| */ | |
| static int mdfld_restore_display_registers(struct drm_device *dev, int pipenum) | |
| { | |
| /* To get panel out of ULPS mode. */ | |
| u32 temp = 0; | |
| u32 device_ready_reg = DEVICE_READY_REG; | |
| struct drm_psb_private *dev_priv = dev->dev_private; | |
| struct mdfld_dsi_config *dsi_config = NULL; | |
| struct medfield_state *regs = &dev_priv->regs.mdfld; | |
| struct psb_pipe *pipe = &dev_priv->regs.pipe[pipenum]; | |
| const struct psb_offset *map = &dev_priv->regmap[pipenum]; | |
| u32 i; | |
| u32 dpll; | |
| u32 timeout = 0; | |
| /* register */ | |
| u32 mipi_reg = MIPI; | |
| /* values */ | |
| u32 dpll_val = pipe->dpll; | |
| u32 mipi_val = regs->saveMIPI; | |
| switch (pipenum) { | |
| case 0: | |
| dpll_val &= ~DPLL_VCO_ENABLE; | |
| dsi_config = dev_priv->dsi_configs[0]; | |
| break; | |
| case 1: | |
| dpll_val &= ~DPLL_VCO_ENABLE; | |
| break; | |
| case 2: | |
| mipi_reg = MIPI_C; | |
| mipi_val = regs->saveMIPI_C; | |
| dsi_config = dev_priv->dsi_configs[1]; | |
| break; | |
| default: | |
| DRM_ERROR("%s, invalid pipe number.\n", __func__); | |
| return -EINVAL; | |
| } | |
| /*make sure VGA plane is off. it initializes to on after reset!*/ | |
| PSB_WVDC32(0x80000000, VGACNTRL); | |
| if (pipenum == 1) { | |
| PSB_WVDC32(dpll_val & ~DPLL_VCO_ENABLE, map->dpll); | |
| PSB_RVDC32(map->dpll); | |
| PSB_WVDC32(pipe->fp0, map->fp0); | |
| } else { | |
| dpll = PSB_RVDC32(map->dpll); | |
| if (!(dpll & DPLL_VCO_ENABLE)) { | |
| /* When ungating power of DPLL, needs to wait 0.5us | |
| before enable the VCO */ | |
| if (dpll & MDFLD_PWR_GATE_EN) { | |
| dpll &= ~MDFLD_PWR_GATE_EN; | |
| PSB_WVDC32(dpll, map->dpll); | |
| /* FIXME_MDFLD PO - change 500 to 1 after PO */ | |
| udelay(500); | |
| } | |
| PSB_WVDC32(pipe->fp0, map->fp0); | |
| PSB_WVDC32(dpll_val, map->dpll); | |
| /* FIXME_MDFLD PO - change 500 to 1 after PO */ | |
| udelay(500); | |
| dpll_val |= DPLL_VCO_ENABLE; | |
| PSB_WVDC32(dpll_val, map->dpll); | |
| PSB_RVDC32(map->dpll); | |
| /* wait for DSI PLL to lock */ | |
| while (timeout < 20000 && | |
| !(PSB_RVDC32(map->conf) & PIPECONF_DSIPLL_LOCK)) { | |
| udelay(150); | |
| timeout++; | |
| } | |
| if (timeout == 20000) { | |
| DRM_ERROR("%s, can't lock DSIPLL.\n", | |
| __func__); | |
| return -EINVAL; | |
| } | |
| } | |
| } | |
| /* Restore mode */ | |
| PSB_WVDC32(pipe->htotal, map->htotal); | |
| PSB_WVDC32(pipe->hblank, map->hblank); | |
| PSB_WVDC32(pipe->hsync, map->hsync); | |
| PSB_WVDC32(pipe->vtotal, map->vtotal); | |
| PSB_WVDC32(pipe->vblank, map->vblank); | |
| PSB_WVDC32(pipe->vsync, map->vsync); | |
| PSB_WVDC32(pipe->src, map->src); | |
| PSB_WVDC32(pipe->status, map->status); | |
| /*set up the plane*/ | |
| PSB_WVDC32(pipe->stride, map->stride); | |
| PSB_WVDC32(pipe->linoff, map->linoff); | |
| PSB_WVDC32(pipe->tileoff, map->tileoff); | |
| PSB_WVDC32(pipe->size, map->size); | |
| PSB_WVDC32(pipe->pos, map->pos); | |
| PSB_WVDC32(pipe->surf, map->surf); | |
| if (pipenum == 1) { | |
| /* restore palette (gamma) */ | |
| /*DRM_UDELAY(50000); */ | |
| for (i = 0; i < 256; i++) | |
| PSB_WVDC32(pipe->palette[i], map->palette + (i << 2)); | |
| PSB_WVDC32(regs->savePFIT_CONTROL, PFIT_CONTROL); | |
| PSB_WVDC32(regs->savePFIT_PGM_RATIOS, PFIT_PGM_RATIOS); | |
| /*TODO: resume HDMI port */ | |
| /*TODO: resume pipe*/ | |
| /*enable the plane*/ | |
| PSB_WVDC32(pipe->cntr & ~DISPLAY_PLANE_ENABLE, map->cntr); | |
| return 0; | |
| } | |
| /*set up pipe related registers*/ | |
| PSB_WVDC32(mipi_val, mipi_reg); | |
| /*setup MIPI adapter + MIPI IP registers*/ | |
| if (dsi_config) | |
| mdfld_dsi_controller_init(dsi_config, pipenum); | |
| if (in_atomic() || in_interrupt()) | |
| mdelay(20); | |
| else | |
| msleep(20); | |
| /*enable the plane*/ | |
| PSB_WVDC32(pipe->cntr, map->cntr); | |
| if (in_atomic() || in_interrupt()) | |
| mdelay(20); | |
| else | |
| msleep(20); | |
| /* LP Hold Release */ | |
| temp = REG_READ(mipi_reg); | |
| temp |= LP_OUTPUT_HOLD_RELEASE; | |
| REG_WRITE(mipi_reg, temp); | |
| mdelay(1); | |
| /* Set DSI host to exit from Utra Low Power State */ | |
| temp = REG_READ(device_ready_reg); | |
| temp &= ~ULPS_MASK; | |
| temp |= 0x3; | |
| temp |= EXIT_ULPS_DEV_READY; | |
| REG_WRITE(device_ready_reg, temp); | |
| mdelay(1); | |
| temp = REG_READ(device_ready_reg); | |
| temp &= ~ULPS_MASK; | |
| temp |= EXITING_ULPS; | |
| REG_WRITE(device_ready_reg, temp); | |
| mdelay(1); | |
| /*enable the pipe*/ | |
| PSB_WVDC32(pipe->conf, map->conf); | |
| /* restore palette (gamma) */ | |
| /*DRM_UDELAY(50000); */ | |
| for (i = 0; i < 256; i++) | |
| PSB_WVDC32(pipe->palette[i], map->palette + (i << 2)); | |
| return 0; | |
| } | |
| static int mdfld_save_registers(struct drm_device *dev) | |
| { | |
| /* mdfld_save_cursor_overlay_registers(dev); */ | |
| mdfld_save_display_registers(dev, 0); | |
| mdfld_save_display_registers(dev, 2); | |
| mdfld_disable_crtc(dev, 0); | |
| mdfld_disable_crtc(dev, 2); | |
| return 0; | |
| } | |
| static int mdfld_restore_registers(struct drm_device *dev) | |
| { | |
| mdfld_restore_display_registers(dev, 2); | |
| mdfld_restore_display_registers(dev, 0); | |
| /* mdfld_restore_cursor_overlay_registers(dev); */ | |
| return 0; | |
| } | |
| static int mdfld_power_down(struct drm_device *dev) | |
| { | |
| /* FIXME */ | |
| return 0; | |
| } | |
| static int mdfld_power_up(struct drm_device *dev) | |
| { | |
| /* FIXME */ | |
| return 0; | |
| } | |
| /* Medfield */ | |
| static const struct psb_offset mdfld_regmap[3] = { | |
| { | |
| .fp0 = MRST_FPA0, | |
| .fp1 = MRST_FPA1, | |
| .cntr = DSPACNTR, | |
| .conf = PIPEACONF, | |
| .src = PIPEASRC, | |
| .dpll = MRST_DPLL_A, | |
| .htotal = HTOTAL_A, | |
| .hblank = HBLANK_A, | |
| .hsync = HSYNC_A, | |
| .vtotal = VTOTAL_A, | |
| .vblank = VBLANK_A, | |
| .vsync = VSYNC_A, | |
| .stride = DSPASTRIDE, | |
| .size = DSPASIZE, | |
| .pos = DSPAPOS, | |
| .surf = DSPASURF, | |
| .addr = MRST_DSPABASE, | |
| .status = PIPEASTAT, | |
| .linoff = DSPALINOFF, | |
| .tileoff = DSPATILEOFF, | |
| .palette = PALETTE_A, | |
| }, | |
| { | |
| .fp0 = MDFLD_DPLL_DIV0, | |
| .cntr = DSPBCNTR, | |
| .conf = PIPEBCONF, | |
| .src = PIPEBSRC, | |
| .dpll = MDFLD_DPLL_B, | |
| .htotal = HTOTAL_B, | |
| .hblank = HBLANK_B, | |
| .hsync = HSYNC_B, | |
| .vtotal = VTOTAL_B, | |
| .vblank = VBLANK_B, | |
| .vsync = VSYNC_B, | |
| .stride = DSPBSTRIDE, | |
| .size = DSPBSIZE, | |
| .pos = DSPBPOS, | |
| .surf = DSPBSURF, | |
| .addr = MRST_DSPBBASE, | |
| .status = PIPEBSTAT, | |
| .linoff = DSPBLINOFF, | |
| .tileoff = DSPBTILEOFF, | |
| .palette = PALETTE_B, | |
| }, | |
| { | |
| .fp0 = MRST_FPA0, /* This is what the old code did ?? */ | |
| .cntr = DSPCCNTR, | |
| .conf = PIPECCONF, | |
| .src = PIPECSRC, | |
| /* No DPLL_C */ | |
| .dpll = MRST_DPLL_A, | |
| .htotal = HTOTAL_C, | |
| .hblank = HBLANK_C, | |
| .hsync = HSYNC_C, | |
| .vtotal = VTOTAL_C, | |
| .vblank = VBLANK_C, | |
| .vsync = VSYNC_C, | |
| .stride = DSPCSTRIDE, | |
| .size = DSPBSIZE, | |
| .pos = DSPCPOS, | |
| .surf = DSPCSURF, | |
| .addr = MDFLD_DSPCBASE, | |
| .status = PIPECSTAT, | |
| .linoff = DSPCLINOFF, | |
| .tileoff = DSPCTILEOFF, | |
| .palette = PALETTE_C, | |
| }, | |
| }; | |
| static int mdfld_chip_setup(struct drm_device *dev) | |
| { | |
| struct drm_psb_private *dev_priv = dev->dev_private; | |
| if (pci_enable_msi(dev->pdev)) | |
| dev_warn(dev->dev, "Enabling MSI failed!\n"); | |
| dev_priv->regmap = mdfld_regmap; | |
| return mid_chip_setup(dev); | |
| } | |
| const struct psb_ops mdfld_chip_ops = { | |
| .name = "mdfld", | |
| .accel_2d = 0, | |
| .pipes = 3, | |
| .crtcs = 3, | |
| .lvds_mask = (1 << 1), | |
| .hdmi_mask = (1 << 1), | |
| .cursor_needs_phys = 0, | |
| .sgx_offset = MRST_SGX_OFFSET, | |
| .chip_setup = mdfld_chip_setup, | |
| .crtc_helper = &mdfld_helper_funcs, | |
| .crtc_funcs = &psb_intel_crtc_funcs, | |
| .output_init = mdfld_output_init, | |
| #ifdef CONFIG_BACKLIGHT_CLASS_DEVICE | |
| .backlight_init = mdfld_backlight_init, | |
| #endif | |
| .save_regs = mdfld_save_registers, | |
| .restore_regs = mdfld_restore_registers, | |
| .power_down = mdfld_power_down, | |
| .power_up = mdfld_power_up, | |
| }; |