diff options
author | Alan Cox <alan@linux.intel.com> | 2011-11-03 22:22:37 +0400 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2011-11-16 15:27:35 +0400 |
commit | 6a227d5fd6c4abe6a9226a40f6981825e9da5fbe (patch) | |
tree | c9bf4f59f45c84f668d15de32ac7d6420e700578 /drivers/gpu/drm/gma500 | |
parent | 1b082ccf5901108d3acd860a73d8c0442556c0bb (diff) | |
download | linux-6a227d5fd6c4abe6a9226a40f6981825e9da5fbe.tar.xz |
gma500: Add support for Cedarview
Again this is similar but has some differences so we have a set of plug in
support. This does make the driver bigger than is needed in some respects
but the tradeoff for maintainability is huge.
Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/gma500')
-rw-r--r-- | drivers/gpu/drm/gma500/cdv_device.c | 351 | ||||
-rw-r--r-- | drivers/gpu/drm/gma500/cdv_device.h | 36 | ||||
-rw-r--r-- | drivers/gpu/drm/gma500/cdv_intel_crt.c | 326 | ||||
-rw-r--r-- | drivers/gpu/drm/gma500/cdv_intel_display.c | 1508 | ||||
-rw-r--r-- | drivers/gpu/drm/gma500/cdv_intel_hdmi.c | 376 | ||||
-rw-r--r-- | drivers/gpu/drm/gma500/cdv_intel_lvds.c | 721 |
6 files changed, 3318 insertions, 0 deletions
diff --git a/drivers/gpu/drm/gma500/cdv_device.c b/drivers/gpu/drm/gma500/cdv_device.c new file mode 100644 index 000000000000..87614e0d396a --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_device.c @@ -0,0 +1,351 @@ +/************************************************************************** + * 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 <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 "intel_bios.h" +#include "cdv_device.h" + +#define VGA_SR_INDEX 0x3c4 +#define VGA_SR_DATA 0x3c5 + +/* FIXME: should check if we are the active VGA device ?? */ +static void cdv_disable_vga(struct drm_device *dev) +{ + u8 sr1; + u32 vga_reg; + + vga_reg = VGACNTRL; + + outb(1, VGA_SR_INDEX); + sr1 = inb(VGA_SR_DATA); + outb(sr1 | 1<<5, VGA_SR_DATA); + udelay(300); + + REG_WRITE(vga_reg, VGA_DISP_DISABLE); + REG_READ(vga_reg); +} + +static int cdv_output_init(struct drm_device *dev) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + cdv_disable_vga(dev); + + cdv_intel_crt_init(dev, &dev_priv->mode_dev); + cdv_intel_lvds_init(dev, &dev_priv->mode_dev); + + /* These bits indicate HDMI not SDVO on CDV, but we don't yet support + the HDMI interface */ + if (REG_READ(SDVOB) & SDVO_DETECTED) + cdv_hdmi_init(dev, &dev_priv->mode_dev, SDVOB); + if (REG_READ(SDVOC) & SDVO_DETECTED) + cdv_hdmi_init(dev, &dev_priv->mode_dev, SDVOC); + return 0; +} + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + +/* + * Poulsbo Backlight Interfaces + */ + +#define BLC_PWM_PRECISION_FACTOR 100 /* 10000000 */ +#define BLC_PWM_FREQ_CALC_CONSTANT 32 +#define MHz 1000000 + +#define PSB_BLC_PWM_PRECISION_FACTOR 10 +#define PSB_BLC_MAX_PWM_REG_FREQ 0xFFFE +#define PSB_BLC_MIN_PWM_REG_FREQ 0x2 + +#define PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) +#define PSB_BACKLIGHT_PWM_CTL_SHIFT (16) + +static int cdv_brightness; +static struct backlight_device *cdv_backlight_device; + +static int cdv_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 cdv_brightness; +} + + +static int cdv_backlight_setup(struct drm_device *dev) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + unsigned long core_clock; + /* u32 bl_max_freq; */ + /* unsigned long value; */ + u16 bl_max_freq; + uint32_t value; + uint32_t blc_pwm_precision_factor; + + /* get bl_max_freq and pol from dev_priv*/ + if (!dev_priv->lvds_bl) { + dev_err(dev->dev, "Has no valid LVDS backlight info\n"); + return -ENOENT; + } + bl_max_freq = dev_priv->lvds_bl->freq; + blc_pwm_precision_factor = PSB_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)PSB_BLC_MAX_PWM_REG_FREQ || + value < (unsigned long long)PSB_BLC_MIN_PWM_REG_FREQ) + return -ERANGE; + else { + /* FIXME */ + } + return 0; +} + +static int cdv_set_brightness(struct backlight_device *bd) +{ + int level = bd->props.brightness; + + /* Percentage 1-100% being valid */ + if (level < 1) + level = 1; + + /*cdv_intel_lvds_set_brightness(dev, level); FIXME */ + cdv_brightness = level; + return 0; +} + +static const struct backlight_ops cdv_ops = { + .get_brightness = cdv_get_brightness, + .update_status = cdv_set_brightness, +}; + +static int cdv_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; + + cdv_backlight_device = backlight_device_register("psb-bl", + NULL, (void *)dev, &cdv_ops, &props); + if (IS_ERR(cdv_backlight_device)) + return PTR_ERR(cdv_backlight_device); + + ret = cdv_backlight_setup(dev); + if (ret < 0) { + backlight_device_unregister(cdv_backlight_device); + cdv_backlight_device = NULL; + return ret; + } + cdv_backlight_device->props.brightness = 100; + cdv_backlight_device->props.max_brightness = 100; + backlight_update_status(cdv_backlight_device); + dev_priv->backlight_device = cdv_backlight_device; + return 0; +} + +#endif + +/* + * Provide the Cedarview specific chip logic and low level methods + * for power management + * + * FIXME: we need to implement the apm/ospm base management bits + * for this and the MID devices. + */ + +static inline u32 CDV_MSG_READ32(uint port, uint offset) +{ + int mcr = (0x10<<24) | (port << 16) | (offset << 8); + uint32_t ret_val = 0; + struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); + pci_write_config_dword(pci_root, 0xD0, mcr); + pci_read_config_dword(pci_root, 0xD4, &ret_val); + pci_dev_put(pci_root); + return ret_val; +} + +static inline void CDV_MSG_WRITE32(uint port, uint offset, u32 value) +{ + int mcr = (0x11<<24) | (port << 16) | (offset << 8) | 0xF0; + struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); + pci_write_config_dword(pci_root, 0xD4, value); + pci_write_config_dword(pci_root, 0xD0, mcr); + pci_dev_put(pci_root); +} + +#define PSB_APM_CMD 0x0 +#define PSB_APM_STS 0x04 +#define PSB_PM_SSC 0x20 +#define PSB_PM_SSS 0x30 +#define PSB_PWRGT_GFX_MASK 0x3 +#define CDV_PWRGT_DISPLAY_CNTR 0x000fc00c +#define CDV_PWRGT_DISPLAY_STS 0x000fc00c + +static void cdv_init_pm(struct drm_device *dev) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + u32 pwr_cnt; + int i; + + dev_priv->apm_base = CDV_MSG_READ32(PSB_PUNIT_PORT, + PSB_APMBA) & 0xFFFF; + dev_priv->ospm_base = CDV_MSG_READ32(PSB_PUNIT_PORT, + PSB_OSPMBA) & 0xFFFF; + + /* Force power on for now */ + pwr_cnt = inl(dev_priv->apm_base + PSB_APM_CMD); + pwr_cnt &= ~PSB_PWRGT_GFX_MASK; + + outl(pwr_cnt, dev_priv->apm_base + PSB_APM_CMD); + for (i = 0; i < 5; i++) { + u32 pwr_sts = inl(dev_priv->apm_base + PSB_APM_STS); + if ((pwr_sts & PSB_PWRGT_GFX_MASK) == 0) + break; + udelay(10); + } + pwr_cnt = inl(dev_priv->ospm_base + PSB_PM_SSC); + pwr_cnt &= ~CDV_PWRGT_DISPLAY_CNTR; + outl(pwr_cnt, dev_priv->ospm_base + PSB_PM_SSC); + for (i = 0; i < 5; i++) { + u32 pwr_sts = inl(dev_priv->ospm_base + PSB_PM_SSS); + if ((pwr_sts & CDV_PWRGT_DISPLAY_STS) == 0) + break; + udelay(10); + } +} + +/** + * cdv_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 + * + * FIXME: review + */ +static int cdv_save_display_registers(struct drm_device *dev) +{ + return 0; +} + +/** + * cdv_restore_display_registers - restore lost register state + * @dev: our DRM device + * + * Restore register state that was lost during suspend and resume. + * + * FIXME: review + */ +static int cdv_restore_display_registers(struct drm_device *dev) +{ + return 0; +} + +static int cdv_power_down(struct drm_device *dev) +{ + return 0; +} + +static int cdv_power_up(struct drm_device *dev) +{ + return 0; +} + +/* FIXME ? - shared with Poulsbo */ +static void cdv_get_core_freq(struct drm_device *dev) +{ + uint32_t clock; + struct pci_dev *pci_root = pci_get_bus_and_slot(0, 0); + struct drm_psb_private *dev_priv = dev->dev_private; + + pci_write_config_dword(pci_root, 0xD0, 0xD0050300); + pci_read_config_dword(pci_root, 0xD4, &clock); + pci_dev_put(pci_root); + + switch (clock & 0x07) { + case 0: + dev_priv->core_freq = 100; + break; + case 1: + dev_priv->core_freq = 133; + break; + case 2: + dev_priv->core_freq = 150; + break; + case 3: + dev_priv->core_freq = 178; + break; + case 4: + dev_priv->core_freq = 200; + break; + case 5: + case 6: + case 7: + dev_priv->core_freq = 266; + default: + dev_priv->core_freq = 0; + } +} + +static int cdv_chip_setup(struct drm_device *dev) +{ + cdv_get_core_freq(dev); + gma_intel_opregion_init(dev); + psb_intel_init_bios(dev); + return 0; +} + +/* CDV is much like Poulsbo but has MID like SGX offsets and PM */ + +const struct psb_ops cdv_chip_ops = { + .name = "Cedartrail", + .accel_2d = 0, + .pipes = 2, + .sgx_offset = MRST_SGX_OFFSET, + .chip_setup = cdv_chip_setup, + + .crtc_helper = &cdv_intel_helper_funcs, + .crtc_funcs = &cdv_intel_crtc_funcs, + + .output_init = cdv_output_init, + +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + .backlight_init = cdv_backlight_init, +#endif + + .init_pm = cdv_init_pm, + .save_regs = cdv_save_display_registers, + .restore_regs = cdv_restore_display_registers, + .power_down = cdv_power_down, + .power_up = cdv_power_up, +}; diff --git a/drivers/gpu/drm/gma500/cdv_device.h b/drivers/gpu/drm/gma500/cdv_device.h new file mode 100644 index 000000000000..2a88b7beb551 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_device.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2011 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. + */ + +extern const struct drm_crtc_helper_funcs cdv_intel_helper_funcs; +extern const struct drm_crtc_funcs cdv_intel_crtc_funcs; +extern void cdv_intel_crt_init(struct drm_device *dev, + struct psb_intel_mode_device *mode_dev); +extern void cdv_intel_lvds_init(struct drm_device *dev, + struct psb_intel_mode_device *mode_dev); +extern void cdv_hdmi_init(struct drm_device *dev, struct psb_intel_mode_device *mode_dev, + int reg); +extern struct drm_display_mode *cdv_intel_crtc_mode_get(struct drm_device *dev, + struct drm_crtc *crtc); + +extern inline void cdv_intel_wait_for_vblank(struct drm_device *dev) +{ + /* Wait for 20ms, i.e. one cycle at 50hz. */ + /* FIXME: msleep ?? */ + mdelay(20); +} + + diff --git a/drivers/gpu/drm/gma500/cdv_intel_crt.c b/drivers/gpu/drm/gma500/cdv_intel_crt.c new file mode 100644 index 000000000000..efda63b97b45 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_crt.c @@ -0,0 +1,326 @@ +/* + * Copyright © 2006-2007 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: + * Eric Anholt <eric@anholt.net> + */ + +#include <linux/i2c.h> +#include <drm/drmP.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> + + +static void cdv_intel_crt_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + u32 temp, reg; + reg = ADPA; + + temp = REG_READ(reg); + temp &= ~(ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE); + temp &= ~ADPA_DAC_ENABLE; + + switch (mode) { + case DRM_MODE_DPMS_ON: + temp |= ADPA_DAC_ENABLE; + break; + case DRM_MODE_DPMS_STANDBY: + temp |= ADPA_DAC_ENABLE | ADPA_HSYNC_CNTL_DISABLE; + break; + case DRM_MODE_DPMS_SUSPEND: + temp |= ADPA_DAC_ENABLE | ADPA_VSYNC_CNTL_DISABLE; + break; + case DRM_MODE_DPMS_OFF: + temp |= ADPA_HSYNC_CNTL_DISABLE | ADPA_VSYNC_CNTL_DISABLE; + break; + } + + REG_WRITE(reg, temp); +} + +static int cdv_intel_crt_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + int max_clock = 0; + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + /* The lowest clock for CDV is 20000KHz */ + if (mode->clock < 20000) + return MODE_CLOCK_LOW; + + /* The max clock for CDV is 355 instead of 400 */ + max_clock = 355000; + if (mode->clock > max_clock) + return MODE_CLOCK_HIGH; + + if (mode->hdisplay > 1680 || mode->vdisplay > 1050) + return MODE_PANEL; + + return MODE_OK; +} + +static bool cdv_intel_crt_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void cdv_intel_crt_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + + struct drm_device *dev = encoder->dev; + struct drm_crtc *crtc = encoder->crtc; + struct psb_intel_crtc *psb_intel_crtc = + to_psb_intel_crtc(crtc); + int dpll_md_reg; + u32 adpa, dpll_md; + u32 adpa_reg; + + if (psb_intel_crtc->pipe == 0) + dpll_md_reg = DPLL_A_MD; + else + dpll_md_reg = DPLL_B_MD; + + adpa_reg = ADPA; + + /* + * Disable separate mode multiplier used when cloning SDVO to CRT + * XXX this needs to be adjusted when we really are cloning + */ + { + dpll_md = REG_READ(dpll_md_reg); + REG_WRITE(dpll_md_reg, + dpll_md & ~DPLL_MD_UDI_MULTIPLIER_MASK); + } + + adpa = 0; + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + adpa |= ADPA_HSYNC_ACTIVE_HIGH; + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + adpa |= ADPA_VSYNC_ACTIVE_HIGH; + + if (psb_intel_crtc->pipe == 0) + adpa |= ADPA_PIPE_A_SELECT; + else + adpa |= ADPA_PIPE_B_SELECT; + + REG_WRITE(adpa_reg, adpa); +} + + +/** + * Uses CRT_HOTPLUG_EN and CRT_HOTPLUG_STAT to detect CRT presence. + * + * \return true if CRT is connected. + * \return false if CRT is disconnected. + */ +static bool cdv_intel_crt_detect_hotplug(struct drm_connector *connector, + bool force) +{ + struct drm_device *dev = connector->dev; + u32 hotplug_en; + int i, tries = 0, ret = false; + u32 adpa_orig; + + /* disable the DAC when doing the hotplug detection */ + + adpa_orig = REG_READ(ADPA); + + REG_WRITE(ADPA, adpa_orig & ~(ADPA_DAC_ENABLE)); + + /* + * On a CDV thep, CRT detect sequence need to be done twice + * to get a reliable result. + */ + tries = 2; + + hotplug_en = REG_READ(PORT_HOTPLUG_EN); + hotplug_en &= ~(CRT_HOTPLUG_DETECT_MASK); + hotplug_en |= CRT_HOTPLUG_FORCE_DETECT; + + hotplug_en |= CRT_HOTPLUG_ACTIVATION_PERIOD_64; + hotplug_en |= CRT_HOTPLUG_VOLTAGE_COMPARE_50; + + for (i = 0; i < tries ; i++) { + unsigned long timeout; + /* turn on the FORCE_DETECT */ + REG_WRITE(PORT_HOTPLUG_EN, hotplug_en); + timeout = jiffies + msecs_to_jiffies(1000); + /* wait for FORCE_DETECT to go off */ + do { + if (!(REG_READ(PORT_HOTPLUG_EN) & + CRT_HOTPLUG_FORCE_DETECT)) + break; + msleep(1); + } while (time_after(timeout, jiffies)); + } + + if ((REG_READ(PORT_HOTPLUG_STAT) & CRT_HOTPLUG_MONITOR_MASK) != + CRT_HOTPLUG_MONITOR_NONE) + ret = true; + + /* Restore the saved ADPA */ + REG_WRITE(ADPA, adpa_orig); + return ret; +} + +static enum drm_connector_status cdv_intel_crt_detect( + struct drm_connector *connector, bool force) +{ + if (cdv_intel_crt_detect_hotplug(connector, force)) + return connector_status_connected; + else + return connector_status_disconnected; +} + +static void cdv_intel_crt_destroy(struct drm_connector *connector) +{ + struct psb_intel_output *intel_output = to_psb_intel_output(connector); + + psb_intel_i2c_destroy(intel_output->ddc_bus); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +static int cdv_intel_crt_get_modes(struct drm_connector *connector) +{ + struct psb_intel_output *intel_output = + to_psb_intel_output(connector); + return psb_intel_ddc_get_modes(intel_output); +} + +static int cdv_intel_crt_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + return 0; +} + +/* + * Routines for controlling stuff on the analog port + */ + +static const struct drm_encoder_helper_funcs cdv_intel_crt_helper_funcs = { + .dpms = cdv_intel_crt_dpms, + .mode_fixup = cdv_intel_crt_mode_fixup, + .prepare = psb_intel_encoder_prepare, + .commit = psb_intel_encoder_commit, + .mode_set = cdv_intel_crt_mode_set, +}; + +static const struct drm_connector_funcs cdv_intel_crt_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = cdv_intel_crt_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = cdv_intel_crt_destroy, + .set_property = cdv_intel_crt_set_property, +}; + +static const struct drm_connector_helper_funcs + cdv_intel_crt_connector_helper_funcs = { + .mode_valid = cdv_intel_crt_mode_valid, + .get_modes = cdv_intel_crt_get_modes, + .best_encoder = psb_intel_best_encoder, +}; + +static void cdv_intel_crt_enc_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs cdv_intel_crt_enc_funcs = { + .destroy = cdv_intel_crt_enc_destroy, +}; + +void cdv_intel_crt_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; + + u32 i2c_reg; + + 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; + drm_connector_init(dev, connector, + &cdv_intel_crt_connector_funcs, DRM_MODE_CONNECTOR_VGA); + + encoder = &psb_intel_output->enc; + drm_encoder_init(dev, encoder, + &cdv_intel_crt_enc_funcs, DRM_MODE_ENCODER_DAC); + + drm_mode_connector_attach_encoder(&psb_intel_output->base, + &psb_intel_output->enc); + + /* Set up the DDC bus. */ + i2c_reg = GPIOA; + /* Remove the following code for CDV */ + /* + if (dev_priv->crt_ddc_bus != 0) + i2c_reg = dev_priv->crt_ddc_bus; + }*/ + psb_intel_output->ddc_bus = psb_intel_i2c_create(dev, + i2c_reg, "CRTDDC_A"); + if (!psb_intel_output->ddc_bus) { + dev_printk(KERN_ERR, &dev->pdev->dev, "DDC bus registration " + "failed.\n"); + goto failed_ddc; + } + + psb_intel_output->type = INTEL_OUTPUT_ANALOG; + /* + psb_intel_output->clone_mask = (1 << INTEL_ANALOG_CLONE_BIT); + psb_intel_output->crtc_mask = (1 << 0) | (1 << 1); + */ + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + drm_encoder_helper_add(encoder, &cdv_intel_crt_helper_funcs); + drm_connector_helper_add(connector, + &cdv_intel_crt_connector_helper_funcs); + + drm_sysfs_connector_add(connector); + + return; +failed_ddc: + drm_encoder_cleanup(&psb_intel_output->enc); + drm_connector_cleanup(&psb_intel_output->base); + kfree(psb_intel_output); + return; +} diff --git a/drivers/gpu/drm/gma500/cdv_intel_display.c b/drivers/gpu/drm/gma500/cdv_intel_display.c new file mode 100644 index 000000000000..7b97c600eff0 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_display.c @@ -0,0 +1,1508 @@ +/* + * Copyright © 2006-2011 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> + */ + +#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" +#include "cdv_device.h" + + +struct cdv_intel_range_t { + int min, max; +}; + +struct cdv_intel_p2_t { + int dot_limit; + int p2_slow, p2_fast; +}; + +struct cdv_intel_clock_t { + /* given values */ + int n; + int m1, m2; + int p1, p2; + /* derived values */ + int dot; + int vco; + int m; + int p; +}; + +#define INTEL_P2_NUM 2 + +struct cdv_intel_limit_t { + struct cdv_intel_range_t dot, vco, n, m, m1, m2, p, p1; + struct cdv_intel_p2_t p2; +}; + +#define CDV_LIMIT_SINGLE_LVDS_96 0 +#define CDV_LIMIT_SINGLE_LVDS_100 1 +#define CDV_LIMIT_DAC_HDMI_27 2 +#define CDV_LIMIT_DAC_HDMI_96 3 + +static const struct cdv_intel_limit_t cdv_intel_limits[] = { + { /* CDV_SIGNLE_LVDS_96MHz */ + .dot = {.min = 20000, .max = 115500}, + .vco = {.min = 1800000, .max = 3600000}, + .n = {.min = 2, .max = 6}, + .m = {.min = 60, .max = 160}, + .m1 = {.min = 0, .max = 0}, + .m2 = {.min = 58, .max = 158}, + .p = {.min = 28, .max = 140}, + .p1 = {.min = 2, .max = 10}, + .p2 = {.dot_limit = 200000, + .p2_slow = 14, .p2_fast = 14}, + }, + { /* CDV_SINGLE_LVDS_100MHz */ + .dot = {.min = 20000, .max = 115500}, + .vco = {.min = 1800000, .max = 3600000}, + .n = {.min = 2, .max = 6}, + .m = {.min = 60, .max = 160}, + .m1 = {.min = 0, .max = 0}, + .m2 = {.min = 58, .max = 158}, + .p = {.min = 28, .max = 140}, + .p1 = {.min = 2, .max = 10}, + /* The single-channel range is 25-112Mhz, and dual-channel + * is 80-224Mhz. Prefer single channel as much as possible. + */ + .p2 = {.dot_limit = 200000, .p2_slow = 14, .p2_fast = 14}, + }, + { /* CDV_DAC_HDMI_27MHz */ + .dot = {.min = 20000, .max = 400000}, + .vco = {.min = 1809000, .max = 3564000}, + .n = {.min = 1, .max = 1}, + .m = {.min = 67, .max = 132}, + .m1 = {.min = 0, .max = 0}, + .m2 = {.min = 65, .max = 130}, + .p = {.min = 5, .max = 90}, + .p1 = {.min = 1, .max = 9}, + .p2 = {.dot_limit = 225000, .p2_slow = 10, .p2_fast = 5}, + }, + { /* CDV_DAC_HDMI_96MHz */ + .dot = {.min = 20000, .max = 400000}, + .vco = {.min = 1800000, .max = 3600000}, + .n = {.min = 2, .max = 6}, + .m = {.min = 60, .max = 160}, + .m1 = {.min = 0, .max = 0}, + .m2 = {.min = 58, .max = 158}, + .p = {.min = 5, .max = 100}, + .p1 = {.min = 1, .max = 10}, + .p2 = {.dot_limit = 225000, .p2_slow = 10, .p2_fast = 5}, + }, +}; + +#define _wait_for(COND, MS, W) ({ \ + unsigned long timeout__ = jiffies + msecs_to_jiffies(MS); \ + int ret__ = 0; \ + while (!(COND)) { \ + if (time_after(jiffies, timeout__)) { \ + ret__ = -ETIMEDOUT; \ + break; \ + } \ + if (W && !in_dbg_master()) \ + msleep(W); \ + } \ + ret__; \ +}) + +#define wait_for(COND, MS) _wait_for(COND, MS, 1) + + +static int cdv_sb_read(struct drm_device *dev, u32 reg, u32 *val) +{ + int ret; + + ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); + if (ret) { + DRM_ERROR("timeout waiting for SB to idle before read\n"); + return ret; + } + + REG_WRITE(SB_ADDR, reg); + REG_WRITE(SB_PCKT, + SET_FIELD(SB_OPCODE_READ, SB_OPCODE) | + SET_FIELD(SB_DEST_DPLL, SB_DEST) | + SET_FIELD(0xf, SB_BYTE_ENABLE)); + + ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); + if (ret) { + DRM_ERROR("timeout waiting for SB to idle after read\n"); + return ret; + } + + *val = REG_READ(SB_DATA); + + return 0; +} + +static int cdv_sb_write(struct drm_device *dev, u32 reg, u32 val) +{ + int ret; + static bool dpio_debug = true; + u32 temp; + + if (dpio_debug) { + if (cdv_sb_read(dev, reg, &temp) == 0) + DRM_DEBUG_KMS("0x%08x: 0x%08x (before)\n", reg, temp); + DRM_DEBUG_KMS("0x%08x: 0x%08x\n", reg, val); + } + + ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); + if (ret) { + DRM_ERROR("timeout waiting for SB to idle before write\n"); + return ret; + } + + REG_WRITE(SB_ADDR, reg); + REG_WRITE(SB_DATA, val); + REG_WRITE(SB_PCKT, + SET_FIELD(SB_OPCODE_WRITE, SB_OPCODE) | + SET_FIELD(SB_DEST_DPLL, SB_DEST) | + SET_FIELD(0xf, SB_BYTE_ENABLE)); + + ret = wait_for((REG_READ(SB_PCKT) & SB_BUSY) == 0, 1000); + if (ret) { + DRM_ERROR("timeout waiting for SB to idle after write\n"); + return ret; + } + + if (dpio_debug) { + if (cdv_sb_read(dev, reg, &temp) == 0) + DRM_DEBUG_KMS("0x%08x: 0x%08x (after)\n", reg, temp); + } + + return 0; +} + +/* Reset the DPIO configuration register. The BIOS does this at every + * mode set. + */ +static void cdv_sb_reset(struct drm_device *dev) +{ + + REG_WRITE(DPIO_CFG, 0); + REG_READ(DPIO_CFG); + REG_WRITE(DPIO_CFG, DPIO_MODE_SELECT_0 | DPIO_CMN_RESET_N); +} + +/* Unlike most Intel display engines, on Cedarview the DPLL registers + * are behind this sideband bus. They must be programmed while the + * DPLL reference clock is on in the DPLL control register, but before + * the DPLL is enabled in the DPLL control register. + */ +static int +cdv_dpll_set_clock_cdv(struct drm_device *dev, struct drm_crtc *crtc, + struct cdv_intel_clock_t *clock) +{ + struct psb_intel_crtc *psb_crtc = + to_psb_intel_crtc(crtc); + int pipe = psb_crtc->pipe; + u32 m, n_vco, p; + int ret = 0; + int dpll_reg = (pipe == 0) ? DPLL_A : DPLL_B; + u32 ref_value; + + cdv_sb_reset(dev); + + if ((REG_READ(dpll_reg) & DPLL_SYNCLOCK_ENABLE) == 0) { + DRM_ERROR("Attempting to set DPLL with refclk disabled\n"); + return -EBUSY; + } + + /* Follow the BIOS and write the REF/SFR Register. Hardcoded value */ + ref_value = 0x68A701; + + cdv_sb_write(dev, SB_REF_SFR(pipe), ref_value); + + /* We don't know what the other fields of these regs are, so + * leave them in place. + */ + ret = cdv_sb_read(dev, SB_M(pipe), &m); + if (ret) + return ret; + m &= ~SB_M_DIVIDER_MASK; + m |= ((clock->m2) << SB_M_DIVIDER_SHIFT); + ret = cdv_sb_write(dev, SB_M(pipe), m); + if (ret) + return ret; + + ret = cdv_sb_read(dev, SB_N_VCO(pipe), &n_vco); + if (ret) + return ret; + + /* Follow the BIOS to program the N_DIVIDER REG */ + n_vco &= 0xFFFF; + n_vco |= 0x107; + n_vco &= ~(SB_N_VCO_SEL_MASK | + SB_N_DIVIDER_MASK | + SB_N_CB_TUNE_MASK); + + n_vco |= ((clock->n) << SB_N_DIVIDER_SHIFT); + + if (clock->vco < 2250000) { + n_vco |= (2 << SB_N_CB_TUNE_SHIFT); + n_vco |= (0 << SB_N_VCO_SEL_SHIFT); + } else if (clock->vco < 2750000) { + n_vco |= (1 << SB_N_CB_TUNE_SHIFT); + n_vco |= (1 << SB_N_VCO_SEL_SHIFT); + } else if (clock->vco < 3300000) { + n_vco |= (0 << SB_N_CB_TUNE_SHIFT); + n_vco |= (2 << SB_N_VCO_SEL_SHIFT); + } else { + n_vco |= (0 << SB_N_CB_TUNE_SHIFT); + n_vco |= (3 << SB_N_VCO_SEL_SHIFT); + } + + ret = cdv_sb_write(dev, SB_N_VCO(pipe), n_vco); + if (ret) + return ret; + + ret = cdv_sb_read(dev, SB_P(pipe), &p); + if (ret) + return ret; + p &= ~(SB_P2_DIVIDER_MASK | SB_P1_DIVIDER_MASK); + p |= SET_FIELD(clock->p1, SB_P1_DIVIDER); + switch (clock->p2) { + case 5: + p |= SET_FIELD(SB_P2_5, SB_P2_DIVIDER); + break; + case 10: + p |= SET_FIELD(SB_P2_10, SB_P2_DIVIDER); + break; + case 14: + p |= SET_FIELD(SB_P2_14, SB_P2_DIVIDER); + break; + case 7: + p |= SET_FIELD(SB_P2_7, SB_P2_DIVIDER); + break; + default: + DRM_ERROR("Bad P2 clock: %d\n", clock->p2); + return -EINVAL; + } + ret = cdv_sb_write(dev, SB_P(pipe), p); + if (ret) + return ret; + + /* always Program the Lane Register for the Pipe A*/ + if (pipe == 0) { + /* Program the Lane0/1 for HDMI B */ + u32 lane_reg, lane_value; + + lane_reg = PSB_LANE0; + cdv_sb_read(dev, lane_reg, &lane_value); + lane_value &= ~(LANE_PLL_MASK); + lane_value |= LANE_PLL_ENABLE; + cdv_sb_write(dev, lane_reg, lane_value); + + lane_reg = PSB_LANE1; + cdv_sb_read(dev, lane_reg, &lane_value); + lane_value &= ~(LANE_PLL_MASK); + lane_value |= LANE_PLL_ENABLE; + cdv_sb_write(dev, lane_reg, lane_value); + + /* Program the Lane2/3 for HDMI C */ + lane_reg = PSB_LANE2; + cdv_sb_read(dev, lane_reg, &lane_value); + lane_value &= ~(LANE_PLL_MASK); + lane_value |= LANE_PLL_ENABLE; + cdv_sb_write(dev, lane_reg, lane_value); + + lane_reg = PSB_LANE3; + cdv_sb_read(dev, lane_reg, &lane_value); + lane_value &= ~(LANE_PLL_MASK); + lane_value |= LANE_PLL_ENABLE; + cdv_sb_write(dev, lane_reg, lane_value); + } + + return 0; +} + +/* + * Returns whether any output on the specified pipe is of the specified type + */ +bool cdv_intel_pipe_has_type(struct drm_crtc *crtc, int type) +{ + struct drm_device *dev = crtc->dev; + struct drm_mode_config *mode_config = &dev->mode_config; + struct drm_connector *l_entry; + + list_for_each_entry(l_entry, &mode_config->connector_list, head) { + if (l_entry->encoder && l_entry->encoder->crtc == crtc) { + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(l_entry); + if (psb_intel_output->type == type) + return true; + } + } + return false; +} + +static const struct cdv_intel_limit_t *cdv_intel_limit(struct drm_crtc *crtc, + int refclk) +{ + const struct cdv_intel_limit_t *limit; + if (cdv_intel_pipe_has_type(crtc, INTEL_OUTPUT_LVDS)) { + /* + * Now only single-channel LVDS is supported on CDV. If it is + * incorrect, please add the dual-channel LVDS. + */ + if (refclk == 96000) + limit = &cdv_intel_limits[CDV_LIMIT_SINGLE_LVDS_96]; + else + limit = &cdv_intel_limits[CDV_LIMIT_SINGLE_LVDS_100]; + } else { + if (refclk == 27000) + limit = &cdv_intel_limits[CDV_LIMIT_DAC_HDMI_27]; + else + limit = &cdv_intel_limits[CDV_LIMIT_DAC_HDMI_96]; + } + return limit; +} + +/* m1 is reserved as 0 in CDV, n is a ring counter */ +static void cdv_intel_clock(struct drm_device *dev, + int refclk, struct cdv_intel_clock_t *clock) +{ + clock->m = clock->m2 + 2; + clock->p = clock->p1 * clock->p2; + clock->vco = (refclk * clock->m) / clock->n; + clock->dot = clock->vco / clock->p; +} + + +#define INTELPllInvalid(s) { /* ErrorF (s) */; return false; } +static bool cdv_intel_PLL_is_valid(struct drm_crtc *crtc, + const struct cdv_intel_limit_t *limit, + struct cdv_intel_clock_t *clock) +{ + if (clock->p1 < limit->p1.min || limit->p1.max < clock->p1) + INTELPllInvalid("p1 out of range\n"); + if (clock->p < limit->p.min || limit->p.max < clock->p) + INTELPllInvalid("p out of range\n"); + /* unnecessary to check the range of m(m1/M2)/n again */ + if (clock->vco < limit->vco.min || limit->vco.max < clock->vco) + INTELPllInvalid("vco out of range\n"); + /* XXX: We may need to be checking "Dot clock" + * depending on the multiplier, connector, etc., + * rather than just a single range. + */ + if (clock->dot < limit->dot.min || limit->dot.max < clock->dot) + INTELPllInvalid("dot out of range\n"); + + return true; +} + +static bool cdv_intel_find_best_PLL(struct drm_crtc *crtc, int target, + int refclk, + struct cdv_intel_clock_t *best_clock) +{ + struct drm_device *dev = crtc->dev; + struct cdv_intel_clock_t clock; + const struct cdv_intel_limit_t *limit = cdv_intel_limit(crtc, refclk); + int err = target; + + + if (cdv_intel_pipe_has_type(crtc, INTEL_OUTPUT_LVDS) && + (REG_READ(LVDS) & LVDS_PORT_EN) != 0) { + /* + * For LVDS, if the panel is on, just rely on its current + * settings for dual-channel. We haven't figured out how to + * reliably set up different single/dual channel state, if we + * even can. + */ + if ((REG_READ(LVDS) & LVDS_CLKB_POWER_MASK) == + LVDS_CLKB_POWER_UP) + clock.p2 = limit->p2.p2_fast; + else + clock.p2 = limit->p2.p2_slow; + } else { + if (target < limit->p2.dot_limit) + clock.p2 = limit->p2.p2_slow; + else + clock.p2 = limit->p2.p2_fast; + } + + memset(best_clock, 0, sizeof(*best_clock)); + clock.m1 = 0; + /* m1 is reserved as 0 in CDV, n is a ring counter. + So skip the m1 loop */ + for (clock.n = limit->n.min; clock.n <= limit->n.max; clock.n++) { + for (clock.m2 = limit->m2.min; clock.m2 <= limit->m2.max; + clock.m2++) { + for (clock.p1 = limit->p1.min; + clock.p1 <= limit->p1.max; + clock.p1++) { + int this_err; + + cdv_intel_clock(dev, refclk, &clock); + + if (!cdv_intel_PLL_is_valid(crtc, + limit, &clock)) + continue; + + this_err = abs(clock.dot - target); + if (this_err < err) { + *best_clock = clock; + err = this_err; + } + } + } + } + + return err != target; +} + +int cdv_intel_pipe_set_base(struct drm_crtc *crtc, + 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 psb_framebuffer *psbfb = to_psb_fb(crtc->fb); + int pipe = psb_intel_crtc->pipe; + unsigned long start, offset; + int dspbase = (pipe == 0 ? DSPABASE : 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; + + if (!gma_power_begin(dev, true)) + return 0; + + /* no fb bound */ + if (!crtc->fb) { + dev_err(dev->dev, "No FB bound\n"); + goto psb_intel_pipe_cleaner; + } + + + /* We are displaying this buffer, make sure it is actually loaded + into the GTT */ + ret = psb_gtt_pin(psbfb->gtt); + if (ret < 0) + goto psb_intel_pipe_set_base_exit; + 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 psb_intel_pipe_set_base_exit; + } + REG_WRITE(dspcntr_reg, dspcntr); + + dev_dbg(dev->dev, + "Writing base %08lX %08lX %d %d\n", start, offset, x, y); + + REG_WRITE(dspbase, offset); + REG_READ(dspbase); + REG_WRITE(dspsurf, start); + REG_READ(dspsurf); + +psb_intel_pipe_cleaner: + /* If there was a previous display we can now unpin it */ + if (old_fb) + psb_gtt_unpin(to_psb_fb(old_fb)->gtt); + +psb_intel_pipe_set_base_exit: + gma_power_end(dev); + return ret; +} + +/** + * 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 cdv_intel_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) ? DPLL_A : DPLL_B; + int dspcntr_reg = (pipe == 0) ? DSPACNTR : DSPBCNTR; + int dspbase_reg = (pipe == 0) ? DSPABASE : DSPBBASE; + int pipeconf_reg = (pipe == 0) ? PIPEACONF : PIPEBCONF; + u32 temp; + bool enabled; + + /* 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); + } + + /* Jim Bish - switch plan and pipe per scott */ + /* 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)); + } + + udelay(150); + + /* Enable the pipe */ + temp = REG_READ(pipeconf_reg); + if ((temp & PIPEACONF_ENABLE) == 0) + REG_WRITE(pipeconf_reg, temp | PIPEACONF_ENABLE); + + 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); + + /* Jim Bish - changed pipe/plane here as well. */ + + /* Wait for vblank for the disable to take effect */ + cdv_intel_wait_for_vblank(dev); + + /* 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 vblank for the disable to take effect. */ + cdv_intel_wait_for_vblank(dev); + + udelay(150); + + /* 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); + } + + 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, 0x3F3E); +} + +static void cdv_intel_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 cdv_intel_crtc_commit(struct drm_crtc *crtc) +{ + struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private; + crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON); +} + +void cdv_intel_encoder_prepare(struct drm_encoder *encoder) +{ + struct drm_encoder_helper_funcs *encoder_funcs = + encoder->helper_private; + /* lvds has its own version of prepare see cdv_intel_lvds_prepare */ + encoder_funcs->dpms(encoder, DRM_MODE_DPMS_OFF); +} + +void cdv_intel_encoder_commit(struct drm_encoder *encoder) +{ + struct drm_encoder_helper_funcs *encoder_funcs = + encoder->helper_private; + /* lvds has its own version of commit see cdv_intel_lvds_commit */ + encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON); +} + +static bool cdv_intel_crtc_mode_fixup(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + + +/** + * Return the pipe currently connected to the panel fitter, + * or -1 if the panel fitter is not present or not in use + */ +static int cdv_intel_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) & 0x3; +} + +static int cdv_intel_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); + int pipe = psb_intel_crtc->pipe; + int dpll_reg = (pipe == 0) ? DPLL_A : DPLL_B; + int dpll_md_reg = (psb_intel_crtc->pipe == 0) ? DPLL_A_MD : DPLL_B_MD; + 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 dspsize_reg = (pipe == 0) ? DSPASIZE : DSPBSIZE; + int dsppos_reg = (pipe == 0) ? DSPAPOS : DSPBPOS; + int pipesrc_reg = (pipe == 0) ? PIPEASRC : PIPEBSRC; + int refclk; + struct cdv_intel_clock_t clock; + u32 dpll = 0, dspcntr, pipeconf; + bool ok, is_sdvo = false, is_dvo = false; + bool is_crt = false, is_lvds = false, is_tv = false; + bool is_hdmi = false; + struct drm_mode_config *mode_config = &dev->mode_config; + struct drm_connector *connector; + + list_for_each_entry(connector, &mode_config->connector_list, head) { + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + + if (!connector->encoder + || connector->encoder->crtc != crtc) + continue; + + switch (psb_intel_output->type) { + case INTEL_OUTPUT_LVDS: + is_lvds = true; + break; + case INTEL_OUTPUT_SDVO: + is_sdvo = true; + break; + case INTEL_OUTPUT_DVO: + is_dvo = true; + break; + case INTEL_OUTPUT_TVOUT: + is_tv = true; + break; + case INTEL_OUTPUT_ANALOG: + is_crt = true; + break; + case INTEL_OUTPUT_HDMI: + is_hdmi = true; + break; + } + } + + refclk = 96000; + + /* Hack selection about ref clk for CRT */ + /* Select 27MHz as the reference clk for HDMI */ + if (is_crt || is_hdmi) + refclk = 27000; + + drm_mode_debug_printmodeline(adjusted_mode); + + ok = cdv_intel_find_best_PLL(crtc, adjusted_mode->clock, refclk, + &clock); + if (!ok) { + dev_err(dev->dev, "Couldn't find PLL settings for mode!\n"); + return 0; + } + + dpll = DPLL_VGA_MODE_DIS; + if (is_tv) { + /* XXX: just matching BIOS for now */ +/* dpll |= PLL_REF_INPUT_TVCLKINBC; */ + dpll |= 3; + } + dpll |= PLL_REF_INPUT_DREFCLK; + + dpll |= DPLL_SYNCLOCK_ENABLE; + dpll |= DPLL_VGA_MODE_DIS; + if (is_lvds) + dpll |= DPLLB_MODE_LVDS; + else + dpll |= DPLLB_MODE_DAC_SERIAL; + /* dpll |= (2 << 11); */ + + /* setup pipeconf */ + pipeconf = REG_READ(pipeconf_reg); + + /* Set up the display plane register */ + dspcntr = DISPPLANE_GAMMA_ENABLE; + + if (pipe == 0) + dspcntr |= DISPPLANE_SEL_PIPE_A; + else + dspcntr |= DISPPLANE_SEL_PIPE_B; + + dspcntr |= DISPLAY_PLANE_ENABLE; + pipeconf |= PIPEACONF_ENABLE; + + REG_WRITE(dpll_reg, dpll | DPLL_VGA_MODE_DIS | DPLL_SYNCLOCK_ENABLE); + REG_READ(dpll_reg); + + cdv_dpll_set_clock_cdv(dev, crtc, &clock); + + udelay(150); + + + /* The LVDS pin pair needs to be on before the DPLLs are enabled. + * This is an exception to the general rule that mode_set doesn't turn + * things on. + */ + if (is_lvds) { + u32 lvds = REG_READ(LVDS); + + lvds |= + LVDS_PORT_EN | LVDS_A0A2_CLKA_POWER_UP | + LVDS_PIPEB_SELECT; + /* Set the B0-B3 data pairs corresponding to + * whether we're going to + * set the DPLLs for dual-channel mode or not. + */ + if (clock.p2 == 7) + lvds |= LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP; + else + lvds &= ~(LVDS_B0B3_POWER_UP | LVDS_CLKB_POWER_UP); + + /* It would be nice to set 24 vs 18-bit mode (LVDS_A3_POWER_UP) + * appropriately here, but we need to look more + * thoroughly into how panels behave in the two modes. + */ + + REG_WRITE(LVDS, lvds); + REG_READ(LVDS); + } + + dpll |= DPLL_VCO_ENABLE; + + /* Disable the panel fitter if it was on our pipe */ + if (cdv_intel_panel_fitter_pipe(dev) == pipe) + REG_WRITE(PFIT_CONTROL, 0); + + DRM_DEBUG_KMS("Mode for pipe %c:\n", pipe == 0 ? 'A' : 'B'); + drm_mode_debug_printmodeline(mode); + + REG_WRITE(dpll_reg, + (REG_READ(dpll_reg) & ~DPLL_LOCK) | DPLL_VCO_ENABLE); + REG_READ(dpll_reg); + /* Wait for the clocks to stabilize. */ + udelay(150); /* 42 usec w/o calibration, 110 with. rounded up. */ + + if (!(REG_READ(dpll_reg) & DPLL_LOCK)) { + dev_err(dev->dev, "Failed to get DPLL lock\n"); + return -EBUSY; + } + + { + int sdvo_pixel_multiply = adjusted_mode->clock / mode->clock; + REG_WRITE(dpll_md_reg, (0 << DPLL_MD_UDI_DIVIDER_SHIFT) | ((sdvo_pixel_multiply - 1) << DPLL_MD_UDI_MULTIPLIER_SHIFT)); + } + + REG_WRITE(htot_reg, (adjusted_mode->crtc_hdisplay - 1) | + ((adjusted_mode->crtc_htotal - 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(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)); + /* pipesrc and dspsize control the size that is scaled from, + * which should always be the user's requested size. + */ + REG_WRITE(dspsize_reg, + ((mode->vdisplay - 1) << 16) | (mode->hdisplay - 1)); + REG_WRITE(dsppos_reg, 0); + REG_WRITE(pipesrc_reg, + ((mode->hdisplay - 1) << 16) | (mode->vdisplay - 1)); + REG_WRITE(pipeconf_reg, pipeconf); + REG_READ(pipeconf_reg); + + cdv_intel_wait_for_vblank(dev); + + REG_WRITE(dspcntr_reg, dspcntr); + + /* Flush the plane changes */ + { + struct drm_crtc_helper_funcs *crtc_funcs = + crtc->helper_private; + crtc_funcs->mode_set_base(crtc, x, y, old_fb); + } + + cdv_intel_wait_for_vblank(dev); + + return 0; +} + +/** Loads the palette/gamma unit for the CRTC with the prepared values */ +void cdv_intel_crtc_load_lut(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_psb_private *dev_priv = + (struct drm_psb_private *)dev->dev_private; + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + int palreg = PALETTE_A; + int i; + + /* The clocks have to be on to load the palette. */ + if (!crtc->enabled) + return; + + switch (psb_intel_crtc->pipe) { + case 0: + break; + case 1: + palreg = PALETTE_B; + break; + case 2: + palreg = PALETTE_C; + break; + default: + dev_err(dev->dev, "Illegal Pipe Number.\n"); + return; + } + + if (gma_power_begin(dev, false)) { + for (i = 0; i < 256; i++) { + REG_WRITE(palreg + 4 * i, + ((psb_intel_crtc->lut_r[i] + + psb_intel_crtc->lut_adj[i]) << 16) | + ((psb_intel_crtc->lut_g[i] + + psb_intel_crtc->lut_adj[i]) << 8) | + (psb_intel_crtc->lut_b[i] + + psb_intel_crtc->lut_adj[i])); + } + gma_power_end(dev); + } else { + for (i = 0; i < 256; i++) { + dev_priv->save_palette_a[i] = + ((psb_intel_crtc->lut_r[i] + + psb_intel_crtc->lut_adj[i]) << 16) | + ((psb_intel_crtc->lut_g[i] + + psb_intel_crtc->lut_adj[i]) << 8) | + (psb_intel_crtc->lut_b[i] + + psb_intel_crtc->lut_adj[i]); + } + + } +} + +/** + * Save HW states of giving crtc + */ +static void cdv_intel_crtc_save(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + /* struct drm_psb_private *dev_priv = + (struct drm_psb_private *)dev->dev_private; */ + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + struct psb_intel_crtc_state *crtc_state = psb_intel_crtc->crtc_state; + int pipeA = (psb_intel_crtc->pipe == 0); + uint32_t paletteReg; + int i; + + if (!crtc_state) { + dev_dbg(dev->dev, "No CRTC state found\n"); + return; + } + + crtc_state->saveDSPCNTR = REG_READ(pipeA ? DSPACNTR : DSPBCNTR); + crtc_state->savePIPECONF = REG_READ(pipeA ? PIPEACONF : PIPEBCONF); + crtc_state->savePIPESRC = REG_READ(pipeA ? PIPEASRC : PIPEBSRC); + crtc_state->saveFP0 = REG_READ(pipeA ? FPA0 : FPB0); + crtc_state->saveFP1 = REG_READ(pipeA ? FPA1 : FPB1); + crtc_state->saveDPLL = REG_READ(pipeA ? DPLL_A : DPLL_B); + crtc_state->saveHTOTAL = REG_READ(pipeA ? HTOTAL_A : HTOTAL_B); + crtc_state->saveHBLANK = REG_READ(pipeA ? HBLANK_A : HBLANK_B); + crtc_state->saveHSYNC = REG_READ(pipeA ? HSYNC_A : HSYNC_B); + crtc_state->saveVTOTAL = REG_READ(pipeA ? VTOTAL_A : VTOTAL_B); + crtc_state->saveVBLANK = REG_READ(pipeA ? VBLANK_A : VBLANK_B); + crtc_state->saveVSYNC = REG_READ(pipeA ? VSYNC_A : VSYNC_B); + crtc_state->saveDSPSTRIDE = REG_READ(pipeA ? DSPASTRIDE : DSPBSTRIDE); + + /*NOTE: DSPSIZE DSPPOS only for psb*/ + crtc_state->saveDSPSIZE = REG_READ(pipeA ? DSPASIZE : DSPBSIZE); + crtc_state->saveDSPPOS = REG_READ(pipeA ? DSPAPOS : DSPBPOS); + + crtc_state->saveDSPBASE = REG_READ(pipeA ? DSPABASE : DSPBBASE); + + DRM_DEBUG("(%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x)\n", + crtc_state->saveDSPCNTR, + crtc_state->savePIPECONF, + crtc_state->savePIPESRC, + crtc_state->saveFP0, + crtc_state->saveFP1, + crtc_state->saveDPLL, + crtc_state->saveHTOTAL, + crtc_state->saveHBLANK, + crtc_state->saveHSYNC, + crtc_state->saveVTOTAL, + crtc_state->saveVBLANK, + crtc_state->saveVSYNC, + crtc_state->saveDSPSTRIDE, + crtc_state->saveDSPSIZE, + crtc_state->saveDSPPOS, + crtc_state->saveDSPBASE + ); + + paletteReg = pipeA ? PALETTE_A : PALETTE_B; + for (i = 0; i < 256; ++i) + crtc_state->savePalette[i] = REG_READ(paletteReg + (i << 2)); +} + +/** + * Restore HW states of giving crtc + */ +static void cdv_intel_crtc_restore(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + /* struct drm_psb_private * dev_priv = + (struct drm_psb_private *)dev->dev_private; */ + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + struct psb_intel_crtc_state *crtc_state = psb_intel_crtc->crtc_state; + /* struct drm_crtc_helper_funcs * crtc_funcs = crtc->helper_private; */ + int pipeA = (psb_intel_crtc->pipe == 0); + uint32_t paletteReg; + int i; + + if (!crtc_state) { + dev_dbg(dev->dev, "No crtc state\n"); + return; + } + + DRM_DEBUG( + "current:(%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x)\n", + REG_READ(pipeA ? DSPACNTR : DSPBCNTR), + REG_READ(pipeA ? PIPEACONF : PIPEBCONF), + REG_READ(pipeA ? PIPEASRC : PIPEBSRC), + REG_READ(pipeA ? FPA0 : FPB0), + REG_READ(pipeA ? FPA1 : FPB1), + REG_READ(pipeA ? DPLL_A : DPLL_B), + REG_READ(pipeA ? HTOTAL_A : HTOTAL_B), + REG_READ(pipeA ? HBLANK_A : HBLANK_B), + REG_READ(pipeA ? HSYNC_A : HSYNC_B), + REG_READ(pipeA ? VTOTAL_A : VTOTAL_B), + REG_READ(pipeA ? VBLANK_A : VBLANK_B), + REG_READ(pipeA ? VSYNC_A : VSYNC_B), + REG_READ(pipeA ? DSPASTRIDE : DSPBSTRIDE), + REG_READ(pipeA ? DSPASIZE : DSPBSIZE), + REG_READ(pipeA ? DSPAPOS : DSPBPOS), + REG_READ(pipeA ? DSPABASE : DSPBBASE) + ); + + DRM_DEBUG( + "saved: (%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x)\n", + crtc_state->saveDSPCNTR, + crtc_state->savePIPECONF, + crtc_state->savePIPESRC, + crtc_state->saveFP0, + crtc_state->saveFP1, + crtc_state->saveDPLL, + crtc_state->saveHTOTAL, + crtc_state->saveHBLANK, + crtc_state->saveHSYNC, + crtc_state->saveVTOTAL, + crtc_state->saveVBLANK, + crtc_state->saveVSYNC, + crtc_state->saveDSPSTRIDE, + crtc_state->saveDSPSIZE, + crtc_state->saveDSPPOS, + crtc_state->saveDSPBASE + ); + + + if (crtc_state->saveDPLL & DPLL_VCO_ENABLE) { + REG_WRITE(pipeA ? DPLL_A : DPLL_B, + crtc_state->saveDPLL & ~DPLL_VCO_ENABLE); + REG_READ(pipeA ? DPLL_A : DPLL_B); + DRM_DEBUG("write dpll: %x\n", + REG_READ(pipeA ? DPLL_A : DPLL_B)); + udelay(150); + } + + REG_WRITE(pipeA ? FPA0 : FPB0, crtc_state->saveFP0); + REG_READ(pipeA ? FPA0 : FPB0); + + REG_WRITE(pipeA ? FPA1 : FPB1, crtc_state->saveFP1); + REG_READ(pipeA ? FPA1 : FPB1); + + REG_WRITE(pipeA ? DPLL_A : DPLL_B, crtc_state->saveDPLL); + REG_READ(pipeA ? DPLL_A : DPLL_B); + udelay(150); + + REG_WRITE(pipeA ? HTOTAL_A : HTOTAL_B, crtc_state->saveHTOTAL); + REG_WRITE(pipeA ? HBLANK_A : HBLANK_B, crtc_state->saveHBLANK); + REG_WRITE(pipeA ? HSYNC_A : HSYNC_B, crtc_state->saveHSYNC); + REG_WRITE(pipeA ? VTOTAL_A : VTOTAL_B, crtc_state->saveVTOTAL); + REG_WRITE(pipeA ? VBLANK_A : VBLANK_B, crtc_state->saveVBLANK); + REG_WRITE(pipeA ? VSYNC_A : VSYNC_B, crtc_state->saveVSYNC); + REG_WRITE(pipeA ? DSPASTRIDE : DSPBSTRIDE, crtc_state->saveDSPSTRIDE); + + REG_WRITE(pipeA ? DSPASIZE : DSPBSIZE, crtc_state->saveDSPSIZE); + REG_WRITE(pipeA ? DSPAPOS : DSPBPOS, crtc_state->saveDSPPOS); + + REG_WRITE(pipeA ? PIPEASRC : PIPEBSRC, crtc_state->savePIPESRC); + REG_WRITE(pipeA ? DSPABASE : DSPBBASE, crtc_state->saveDSPBASE); + REG_WRITE(pipeA ? PIPEACONF : PIPEBCONF, crtc_state->savePIPECONF); + + cdv_intel_wait_for_vblank(dev); + + REG_WRITE(pipeA ? DSPACNTR : DSPBCNTR, crtc_state->saveDSPCNTR); + REG_WRITE(pipeA ? DSPABASE : DSPBBASE, crtc_state->saveDSPBASE); + + cdv_intel_wait_for_vblank(dev); + + paletteReg = pipeA ? PALETTE_A : PALETTE_B; + for (i = 0; i < 256; ++i) + REG_WRITE(paletteReg + (i << 2), crtc_state->savePalette[i]); +} + +static int cdv_intel_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file_priv, + uint32_t handle, + uint32_t width, uint32_t height) +{ + struct drm_device *dev = crtc->dev; + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + int pipe = psb_intel_crtc->pipe; + uint32_t control = (pipe == 0) ? CURACNTR : CURBCNTR; + uint32_t base = (pipe == 0) ? CURABASE : CURBBASE; + uint32_t temp; + size_t addr = 0; + struct gtt_range *gt; + struct drm_gem_object *obj; + int ret; + + /* if we want to turn of the cursor ignore width and height */ + if (!handle) { + /* turn off the cursor */ + temp = CURSOR_MODE_DISABLE; + + if (gma_power_begin(dev, false)) { + REG_WRITE(control, temp); + REG_WRITE(base, 0); + gma_power_end(dev); + } + + /* unpin the old GEM object */ + if (psb_intel_crtc->cursor_obj) { + gt = container_of(psb_intel_crtc->cursor_obj, + struct gtt_range, gem); + psb_gtt_unpin(gt); + drm_gem_object_unreference(psb_intel_crtc->cursor_obj); + psb_intel_crtc->cursor_obj = NULL; + } + + return 0; + } + + /* Currently we only support 64x64 cursors */ + if (width != 64 || height != 64) { + dev_dbg(dev->dev, "we currently only support 64x64 cursors\n"); + return -EINVAL; + } + + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (!obj) + return -ENOENT; + + if (obj->size < width * height * 4) { + dev_dbg(dev->dev, "buffer is to small\n"); + return -ENOMEM; + } + + gt = container_of(obj, struct gtt_range, gem); + + /* Pin the memory into the GTT */ + ret = psb_gtt_pin(gt); + if (ret) { + dev_err(dev->dev, "Can not pin down handle 0x%x\n", handle); + return ret; + } + + addr = gt->offset; /* Or resource.start ??? */ + + psb_intel_crtc->cursor_addr = addr; + + temp = 0; + /* set the pipe for the cursor */ + temp |= (pipe << 28); + temp |= CURSOR_MODE_64_ARGB_AX | MCURSOR_GAMMA_ENABLE; + + if (gma_power_begin(dev, false)) { + REG_WRITE(control, temp); + REG_WRITE(base, addr); + gma_power_end(dev); + } + + /* unpin the old GEM object */ + if (psb_intel_crtc->cursor_obj) { + gt = container_of(psb_intel_crtc->cursor_obj, + struct gtt_range, gem); + psb_gtt_unpin(gt); + drm_gem_object_unreference(psb_intel_crtc->cursor_obj); + psb_intel_crtc->cursor_obj = obj; + } + return 0; +} + +static int cdv_intel_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct drm_device *dev = crtc->dev; + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + int pipe = psb_intel_crtc->pipe; + uint32_t temp = 0; + uint32_t adder; + + + if (x < 0) { + temp |= (CURSOR_POS_SIGN << CURSOR_X_SHIFT); + x = -x; + } + if (y < 0) { + temp |= (CURSOR_POS_SIGN << CURSOR_Y_SHIFT); + y = -y; + } + + temp |= ((x & CURSOR_POS_MASK) << CURSOR_X_SHIFT); + temp |= ((y & CURSOR_POS_MASK) << CURSOR_Y_SHIFT); + + adder = psb_intel_crtc->cursor_addr; + + if (gma_power_begin(dev, false)) { + REG_WRITE((pipe == 0) ? CURAPOS : CURBPOS, temp); + REG_WRITE((pipe == 0) ? CURABASE : CURBBASE, adder); + gma_power_end(dev); + } + return 0; +} + +static void cdv_intel_crtc_gamma_set(struct drm_crtc *crtc, u16 *red, + u16 *green, u16 *blue, uint32_t start, uint32_t size) +{ + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + int i; + int end = (start + size > 256) ? 256 : start + size; + + for (i = start; i < end; i++) { + psb_intel_crtc->lut_r[i] = red[i] >> 8; + psb_intel_crtc->lut_g[i] = green[i] >> 8; + psb_intel_crtc->lut_b[i] = blue[i] >> 8; + } + + cdv_intel_crtc_load_lut(crtc); +} + +static int cdv_crtc_set_config(struct drm_mode_set *set) +{ + int ret = 0; + struct drm_device *dev = set->crtc->dev; + struct drm_psb_private *dev_priv = dev->dev_private; + + if (!dev_priv->rpm_enabled) + return drm_crtc_helper_set_config(set); + + pm_runtime_forbid(&dev->pdev->dev); + + ret = drm_crtc_helper_set_config(set); + + pm_runtime_allow(&dev->pdev->dev); + + return ret; +} + +/** Derive the pixel clock for the given refclk and divisors for 8xx chips. */ + +/* FIXME: why are we using this, should it be cdv_ in this tree ? */ + +static void i8xx_clock(int refclk, struct cdv_intel_clock_t *clock) +{ + clock->m = 5 * (clock->m1 + 2) + (clock->m2 + 2); + clock->p = clock->p1 * clock->p2; + clock->vco = refclk * clock->m / (clock->n + 2); + clock->dot = clock->vco / clock->p; +} + +/* Returns the clock of the currently programmed mode of the given pipe. */ +static int cdv_intel_crtc_clock_get(struct drm_device *dev, + struct drm_crtc *crtc) +{ + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + int pipe = psb_intel_crtc->pipe; + u32 dpll; + u32 fp; + struct cdv_intel_clock_t clock; + bool is_lvds; + struct drm_psb_private *dev_priv = dev->dev_private; + + if (gma_power_begin(dev, false)) { + dpll = REG_READ((pipe == 0) ? DPLL_A : DPLL_B); + if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) + fp = REG_READ((pipe == 0) ? FPA0 : FPB0); + else + fp = REG_READ((pipe == 0) ? FPA1 : FPB1); + is_lvds = (pipe == 1) && (REG_READ(LVDS) & LVDS_PORT_EN); + gma_power_end(dev); + } else { + dpll = (pipe == 0) ? + dev_priv->saveDPLL_A : dev_priv->saveDPLL_B; + + if ((dpll & DISPLAY_RATE_SELECT_FPA1) == 0) + fp = (pipe == 0) ? + dev_priv->saveFPA0 : + dev_priv->saveFPB0; + else + fp = (pipe == 0) ? + dev_priv->saveFPA1 : + dev_priv->saveFPB1; + + is_lvds = (pipe == 1) && (dev_priv->saveLVDS & LVDS_PORT_EN); + } + + clock.m1 = (fp & FP_M1_DIV_MASK) >> FP_M1_DIV_SHIFT; + clock.m2 = (fp & FP_M2_DIV_MASK) >> FP_M2_DIV_SHIFT; + clock.n = (fp & FP_N_DIV_MASK) >> FP_N_DIV_SHIFT; + + if (is_lvds) { + clock.p1 = + ffs((dpll & + DPLL_FPA01_P1_POST_DIV_MASK_I830_LVDS) >> + DPLL_FPA01_P1_POST_DIV_SHIFT); + if (clock.p1 == 0) { + clock.p1 = 4; + dev_err(dev->dev, "PLL %d\n", dpll); + } + clock.p2 = 14; + + if ((dpll & PLL_REF_INPUT_MASK) == + PLLB_REF_INPUT_SPREADSPECTRUMIN) { + /* XXX: might not be 66MHz */ + i8xx_clock(66000, &clock); + } else + i8xx_clock(48000, &clock); + } else { + if (dpll & PLL_P1_DIVIDE_BY_TWO) + clock.p1 = 2; + else { + clock.p1 = + ((dpll & + DPLL_FPA01_P1_POST_DIV_MASK_I830) >> + DPLL_FPA01_P1_POST_DIV_SHIFT) + 2; + } + if (dpll & PLL_P2_DIVIDE_BY_4) + clock.p2 = 4; + else + clock.p2 = 2; + + i8xx_clock(48000, &clock); + } + + /* XXX: It would be nice to validate the clocks, but we can't reuse + * i830PllIsValid() because it relies on the xf86_config connector + * configuration being accurate, which it isn't necessarily. + */ + + return clock.dot; +} + +/** Returns the currently programmed mode of the given pipe. */ +struct drm_display_mode *cdv_intel_crtc_mode_get(struct drm_device *dev, + struct drm_crtc *crtc) +{ + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + int pipe = psb_intel_crtc->pipe; + struct drm_display_mode *mode; + int htot; + int hsync; + int vtot; + int vsync; + struct drm_psb_private *dev_priv = dev->dev_private; + + if (gma_power_begin(dev, false)) { + htot = REG_READ((pipe == 0) ? HTOTAL_A : HTOTAL_B); + hsync = REG_READ((pipe == 0) ? HSYNC_A : HSYNC_B); + vtot = REG_READ((pipe == 0) ? VTOTAL_A : VTOTAL_B); + vsync = REG_READ((pipe == 0) ? VSYNC_A : VSYNC_B); + gma_power_end(dev); + } else { + htot = (pipe == 0) ? + dev_priv->saveHTOTAL_A : dev_priv->saveHTOTAL_B; + hsync = (pipe == 0) ? + dev_priv->saveHSYNC_A : dev_priv->saveHSYNC_B; + vtot = (pipe == 0) ? + dev_priv->saveVTOTAL_A : dev_priv->saveVTOTAL_B; + vsync = (pipe == 0) ? + dev_priv->saveVSYNC_A : dev_priv->saveVSYNC_B; + } + + mode = kzalloc(sizeof(*mode), GFP_KERNEL); + if (!mode) + return NULL; + + mode->clock = cdv_intel_crtc_clock_get(dev, crtc); + mode->hdisplay = (htot & 0xffff) + 1; + mode->htotal = ((htot & 0xffff0000) >> 16) + 1; + mode->hsync_start = (hsync & 0xffff) + 1; + mode->hsync_end = ((hsync & 0xffff0000) >> 16) + 1; + mode->vdisplay = (vtot & 0xffff) + 1; + mode->vtotal = ((vtot & 0xffff0000) >> 16) + 1; + mode->vsync_start = (vsync & 0xffff) + 1; + mode->vsync_end = ((vsync & 0xffff0000) >> 16) + 1; + + drm_mode_set_name(mode); + drm_mode_set_crtcinfo(mode, 0); + + return mode; +} + +static void cdv_intel_crtc_destroy(struct drm_crtc *crtc) +{ + struct psb_intel_crtc *psb_intel_crtc = to_psb_intel_crtc(crtc); + + kfree(psb_intel_crtc->crtc_state); + drm_crtc_cleanup(crtc); + kfree(psb_intel_crtc); +} + +const struct drm_crtc_helper_funcs cdv_intel_helper_funcs = { + .dpms = cdv_intel_crtc_dpms, + .mode_fixup = cdv_intel_crtc_mode_fixup, + .mode_set = cdv_intel_crtc_mode_set, + .mode_set_base = cdv_intel_pipe_set_base, + .prepare = cdv_intel_crtc_prepare, + .commit = cdv_intel_crtc_commit, +}; + +const struct drm_crtc_funcs cdv_intel_crtc_funcs = { + .save = cdv_intel_crtc_save, + .restore = cdv_intel_crtc_restore, + .cursor_set = cdv_intel_crtc_cursor_set, + .cursor_move = cdv_intel_crtc_cursor_move, + .gamma_set = cdv_intel_crtc_gamma_set, + .set_config = cdv_crtc_set_config, + .destroy = cdv_intel_crtc_destroy, +}; + +/* + * Set the default value of cursor control and base register + * to zero. This is a workaround for h/w defect on oaktrail + */ +void cdv_intel_cursor_init(struct drm_device *dev, int pipe) +{ + uint32_t control; + uint32_t base; + + switch (pipe) { + case 0: + control = CURACNTR; + base = CURABASE; + break; + case 1: + control = CURBCNTR; + base = CURBBASE; + break; + case 2: + control = CURCCNTR; + base = CURCBASE; + break; + default: + return; + } + + REG_WRITE(control, 0); + REG_WRITE(base, 0); +} + diff --git a/drivers/gpu/drm/gma500/cdv_intel_hdmi.c b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c new file mode 100644 index 000000000000..cbca2b0c7d58 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_hdmi.c @@ -0,0 +1,376 @@ +/* + * Copyright © 2006-2011 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: + * jim liu <jim.liu@intel.com> + * + * FIXME: + * We should probably make this generic and share it with Medfield + */ + +#include <drm/drmP.h> +#include <drm/drm.h> +#include <drm/drm_crtc.h> +#include <drm/drm_edid.h> +#include "psb_intel_drv.h" +#include "psb_drv.h" +#include "psb_intel_reg.h" +#include <linux/pm_runtime.h> + +/* hdmi control bits */ +#define HDMI_NULL_PACKETS_DURING_VSYNC (1 << 9) +#define HDMI_BORDER_ENABLE (1 << 7) +#define HDMI_AUDIO_ENABLE (1 << 6) +#define HDMI_VSYNC_ACTIVE_HIGH (1 << 4) +#define HDMI_HSYNC_ACTIVE_HIGH (1 << 3) +/* hdmi-b control bits */ +#define HDMIB_PIPE_B_SELECT (1 << 30) + + +struct mid_intel_hdmi_priv { + u32 hdmi_reg; + u32 save_HDMIB; + bool has_hdmi_sink; + bool has_hdmi_audio; + /* Should set this when detect hotplug */ + bool hdmi_device_connected; + struct mdfld_hdmi_i2c *i2c_bus; + struct i2c_adapter *hdmi_i2c_adapter; /* for control functions */ + struct drm_device *dev; +}; + +static void cdv_hdmi_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct psb_intel_output *output = enc_to_psb_intel_output(encoder); + struct mid_intel_hdmi_priv *hdmi_priv = output->dev_priv; + u32 hdmib; + struct drm_crtc *crtc = encoder->crtc; + struct psb_intel_crtc *intel_crtc = to_psb_intel_crtc(crtc); + + hdmib = (2 << 10); + + if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC) + hdmib |= HDMI_VSYNC_ACTIVE_HIGH; + if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC) + hdmib |= HDMI_HSYNC_ACTIVE_HIGH; + + if (intel_crtc->pipe == 1) + hdmib |= HDMIB_PIPE_B_SELECT; + + if (hdmi_priv->has_hdmi_audio) { + hdmib |= HDMI_AUDIO_ENABLE; + hdmib |= HDMI_NULL_PACKETS_DURING_VSYNC; + } + + REG_WRITE(hdmi_priv->hdmi_reg, hdmib); + REG_READ(hdmi_priv->hdmi_reg); +} + +static bool cdv_hdmi_mode_fixup(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + return true; +} + +static void cdv_hdmi_dpms(struct drm_encoder *encoder, int mode) +{ + struct drm_device *dev = encoder->dev; + struct psb_intel_output *output = enc_to_psb_intel_output(encoder); + struct mid_intel_hdmi_priv *hdmi_priv = output->dev_priv; + u32 hdmib; + + hdmib = REG_READ(hdmi_priv->hdmi_reg); + + if (mode != DRM_MODE_DPMS_ON) + REG_WRITE(hdmi_priv->hdmi_reg, hdmib & ~HDMIB_PORT_EN); + else + REG_WRITE(hdmi_priv->hdmi_reg, hdmib | HDMIB_PORT_EN); + REG_READ(hdmi_priv->hdmi_reg); +} + +static void cdv_hdmi_save(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct psb_intel_output *output = to_psb_intel_output(connector); + struct mid_intel_hdmi_priv *hdmi_priv = output->dev_priv; + + hdmi_priv->save_HDMIB = REG_READ(hdmi_priv->hdmi_reg); +} + +static void cdv_hdmi_restore(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct psb_intel_output *output = to_psb_intel_output(connector); + struct mid_intel_hdmi_priv *hdmi_priv = output->dev_priv; + + REG_WRITE(hdmi_priv->hdmi_reg, hdmi_priv->save_HDMIB); + REG_READ(hdmi_priv->hdmi_reg); +} + +static enum drm_connector_status cdv_hdmi_detect( + struct drm_connector *connector, bool force) +{ + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + struct mid_intel_hdmi_priv *hdmi_priv = psb_intel_output->dev_priv; + struct edid *edid = NULL; + enum drm_connector_status status = connector_status_disconnected; + + edid = drm_get_edid(&psb_intel_output->base, + psb_intel_output->hdmi_i2c_adapter); + + hdmi_priv->has_hdmi_sink = false; + hdmi_priv->has_hdmi_audio = false; + if (edid) { + if (edid->input & DRM_EDID_INPUT_DIGITAL) { + status = connector_status_connected; + hdmi_priv->has_hdmi_sink = + drm_detect_hdmi_monitor(edid); + hdmi_priv->has_hdmi_audio = + drm_detect_monitor_audio(edid); + } + + psb_intel_output->base.display_info.raw_edid = NULL; + kfree(edid); + } + return status; +} + +static int cdv_hdmi_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + struct drm_encoder *encoder = connector->encoder; + + if (!strcmp(property->name, "scaling mode") && encoder) { + struct psb_intel_crtc *crtc = to_psb_intel_crtc(encoder->crtc); + bool centre; + uint64_t curValue; + + if (!crtc) + return -1; + + switch (value) { + case DRM_MODE_SCALE_FULLSCREEN: + break; + case DRM_MODE_SCALE_NO_SCALE: + break; + case DRM_MODE_SCALE_ASPECT: + break; + default: + return -1; + } + + if (drm_connector_property_get_value(connector, + property, &curValue)) + return -1; + + if (curValue == value) + return 0; + + if (drm_connector_property_set_value(connector, + property, value)) + return -1; + + centre = (curValue == DRM_MODE_SCALE_NO_SCALE) || + (value == DRM_MODE_SCALE_NO_SCALE); + + if (crtc->saved_mode.hdisplay != 0 && + crtc->saved_mode.vdisplay != 0) { + if (centre) { + if (!drm_crtc_helper_set_mode(encoder->crtc, &crtc->saved_mode, + encoder->crtc->x, encoder->crtc->y, encoder->crtc->fb)) + return -1; + } else { + struct drm_encoder_helper_funcs *helpers + = encoder->helper_private; + helpers->mode_set(encoder, &crtc->saved_mode, + &crtc->saved_adjusted_mode); + } + } + } + return 0; +} + +/* + * Return the list of HDMI DDC modes if available. + */ +static int cdv_hdmi_get_modes(struct drm_connector *connector) +{ + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + struct edid *edid = NULL; + int ret = 0; + + edid = drm_get_edid(&psb_intel_output->base, + psb_intel_output->hdmi_i2c_adapter); + if (edid) { + drm_mode_connector_update_edid_property(&psb_intel_output-> + base, edid); + ret = drm_add_edid_modes(&psb_intel_output->base, edid); + kfree(edid); + } + return ret; +} + +static int cdv_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_HIGH; + + /* just in case */ + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + /* just in case */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + /* + * FIXME: for now we limit the size to 1680x1050 on CDV, otherwise it + * will go beyond the stolen memory size allocated to the framebuffer + */ + if (mode->hdisplay > 1680) + return MODE_PANEL; + if (mode->vdisplay > 1050) + return MODE_PANEL; + return MODE_OK; +} + +static void cdv_hdmi_destroy(struct drm_connector *connector) +{ + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + + if (psb_intel_output->ddc_bus) + psb_intel_i2c_destroy(psb_intel_output->ddc_bus); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +static const struct drm_encoder_helper_funcs cdv_hdmi_helper_funcs = { + .dpms = cdv_hdmi_dpms, + .mode_fixup = cdv_hdmi_mode_fixup, + .prepare = psb_intel_encoder_prepare, + .mode_set = cdv_hdmi_mode_set, + .commit = psb_intel_encoder_commit, +}; + +static const struct drm_connector_helper_funcs + cdv_hdmi_connector_helper_funcs = { + .get_modes = cdv_hdmi_get_modes, + .mode_valid = cdv_hdmi_mode_valid, + .best_encoder = psb_intel_best_encoder, +}; + +static const struct drm_connector_funcs cdv_hdmi_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .save = cdv_hdmi_save, + .restore = cdv_hdmi_restore, + .detect = cdv_hdmi_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = cdv_hdmi_set_property, + .destroy = cdv_hdmi_destroy, +}; + +void cdv_hdmi_init(struct drm_device *dev, + struct psb_intel_mode_device *mode_dev, int reg) +{ + struct psb_intel_output *psb_intel_output; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct mid_intel_hdmi_priv *hdmi_priv; + int ddc_bus; + + psb_intel_output = kzalloc(sizeof(struct psb_intel_output) + + sizeof(struct mid_intel_hdmi_priv), GFP_KERNEL); + if (!psb_intel_output) + return; + + hdmi_priv = (struct mid_intel_hdmi_priv *)(psb_intel_output + 1); + 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, + &cdv_hdmi_connector_funcs, + DRM_MODE_CONNECTOR_DVID); + + drm_encoder_init(dev, &psb_intel_output->enc, &psb_intel_lvds_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; + hdmi_priv->hdmi_reg = reg; + hdmi_priv->has_hdmi_sink = false; + psb_intel_output->dev_priv = hdmi_priv; + + drm_encoder_helper_add(encoder, &cdv_hdmi_helper_funcs); + drm_connector_helper_add(connector, + &cdv_hdmi_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); + + switch (reg) { + case SDVOB: + ddc_bus = GPIOE; + break; + case SDVOC: + ddc_bus = GPIOD; + break; + default: + DRM_ERROR("unknown reg 0x%x for HDMI\n", reg); + goto failed_ddc; + break; + } + + psb_intel_output->ddc_bus = psb_intel_i2c_create(dev, + ddc_bus, (reg == SDVOB) ? "HDMIB" : "HDMIC"); + + if (!psb_intel_output->ddc_bus) { + dev_err(dev->dev, "No ddc adapter available!\n"); + goto failed_ddc; + } + psb_intel_output->hdmi_i2c_adapter = + &(psb_intel_output->ddc_bus->adapter); + hdmi_priv->dev = dev; + drm_sysfs_connector_add(connector); + return; + +failed_ddc: + drm_encoder_cleanup(&psb_intel_output->enc); + drm_connector_cleanup(&psb_intel_output->base); + kfree(psb_intel_output); +} diff --git a/drivers/gpu/drm/gma500/cdv_intel_lvds.c b/drivers/gpu/drm/gma500/cdv_intel_lvds.c new file mode 100644 index 000000000000..988b2d0acf43 --- /dev/null +++ b/drivers/gpu/drm/gma500/cdv_intel_lvds.c @@ -0,0 +1,721 @@ +/* + * Copyright © 2006-2011 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 <linux/dmi.h> +#include <drm/drmP.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> +#include "cdv_device.h" + +/** + * LVDS I2C backlight control macros + */ +#define BRIGHTNESS_MAX_LEVEL 100 +#define BRIGHTNESS_MASK 0xFF +#define BLC_I2C_TYPE 0x01 +#define BLC_PWM_TYPT 0x02 + +#define BLC_POLARITY_NORMAL 0 +#define BLC_POLARITY_INVERSE 1 + +#define PSB_BLC_MAX_PWM_REG_FREQ (0xFFFE) +#define PSB_BLC_MIN_PWM_REG_FREQ (0x2) +#define PSB_BLC_PWM_PRECISION_FACTOR (10) +#define PSB_BACKLIGHT_PWM_CTL_SHIFT (16) +#define PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE) + +struct cdv_intel_lvds_priv { + /** + * Saved LVDO output states + */ + uint32_t savePP_ON; + uint32_t savePP_OFF; + uint32_t saveLVDS; + uint32_t savePP_CONTROL; + uint32_t savePP_CYCLE; + uint32_t savePFIT_CONTROL; + uint32_t savePFIT_PGM_RATIOS; + uint32_t saveBLC_PWM_CTL; +}; + +/* + * Returns the maximum level of the backlight duty cycle field. + */ +static u32 cdv_intel_lvds_get_max_backlight(struct drm_device *dev) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + u32 retval; + + if (gma_power_begin(dev, false)) { + retval = ((REG_READ(BLC_PWM_CTL) & + BACKLIGHT_MODULATION_FREQ_MASK) >> + BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; + + gma_power_end(dev); + } else + retval = ((dev_priv->saveBLC_PWM_CTL & + BACKLIGHT_MODULATION_FREQ_MASK) >> + BACKLIGHT_MODULATION_FREQ_SHIFT) * 2; + + return retval; +} + +/* + * Set LVDS backlight level by I2C command + */ +static int cdv_lvds_i2c_set_brightness(struct drm_device *dev, + unsigned int level) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + struct psb_intel_i2c_chan *lvds_i2c_bus = dev_priv->lvds_i2c_bus; + u8 out_buf[2]; + unsigned int blc_i2c_brightness; + + struct i2c_msg msgs[] = { + { + .addr = lvds_i2c_bus->slave_addr, + .flags = 0, + .len = 2, + .buf = out_buf, + } + }; + + blc_i2c_brightness = BRIGHTNESS_MASK & ((unsigned int)level * + BRIGHTNESS_MASK / + BRIGHTNESS_MAX_LEVEL); + + if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE) + blc_i2c_brightness = BRIGHTNESS_MASK - blc_i2c_brightness; + + out_buf[0] = dev_priv->lvds_bl->brightnesscmd; + out_buf[1] = (u8)blc_i2c_brightness; + + if (i2c_transfer(&lvds_i2c_bus->adapter, msgs, 1) == 1) + return 0; + + DRM_ERROR("I2C transfer error\n"); + return -1; +} + + +static int cdv_lvds_pwm_set_brightness(struct drm_device *dev, int level) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + + u32 max_pwm_blc; + u32 blc_pwm_duty_cycle; + + max_pwm_blc = cdv_intel_lvds_get_max_backlight(dev); + + /*BLC_PWM_CTL Should be initiated while backlight device init*/ + BUG_ON((max_pwm_blc & PSB_BLC_MAX_PWM_REG_FREQ) == 0); + + blc_pwm_duty_cycle = level * max_pwm_blc / BRIGHTNESS_MAX_LEVEL; + + if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE) + blc_pwm_duty_cycle = max_pwm_blc - blc_pwm_duty_cycle; + + blc_pwm_duty_cycle &= PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR; + REG_WRITE(BLC_PWM_CTL, + (max_pwm_blc << PSB_BACKLIGHT_PWM_CTL_SHIFT) | + (blc_pwm_duty_cycle)); + + return 0; +} + +/* + * Set LVDS backlight level either by I2C or PWM + */ +void cdv_intel_lvds_set_brightness(struct drm_device *dev, int level) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + + if (!dev_priv->lvds_bl) { + DRM_ERROR("NO LVDS Backlight Info\n"); + return; + } + + if (dev_priv->lvds_bl->type == BLC_I2C_TYPE) + cdv_lvds_i2c_set_brightness(dev, level); + else + cdv_lvds_pwm_set_brightness(dev, level); +} + +/** + * Sets the backlight level. + * + * level backlight level, from 0 to cdv_intel_lvds_get_max_backlight(). + */ +static void cdv_intel_lvds_set_backlight(struct drm_device *dev, int level) +{ + struct drm_psb_private *dev_priv = dev->dev_private; + u32 blc_pwm_ctl; + + if (gma_power_begin(dev, false)) { + blc_pwm_ctl = + REG_READ(BLC_PWM_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK; + REG_WRITE(BLC_PWM_CTL, + (blc_pwm_ctl | + (level << BACKLIGHT_DUTY_CYCLE_SHIFT))); + gma_power_end(dev); + } else { + blc_pwm_ctl = dev_priv->saveBLC_PWM_CTL & + ~BACKLIGHT_DUTY_CYCLE_MASK; + dev_priv->saveBLC_PWM_CTL = (blc_pwm_ctl | + (level << BACKLIGHT_DUTY_CYCLE_SHIFT)); + } +} + +/** + * Sets the power state for the panel. + */ +static void cdv_intel_lvds_set_power(struct drm_device *dev, + struct psb_intel_output *output, bool on) +{ + u32 pp_status; + + 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) == 0); + + cdv_intel_lvds_set_backlight(dev, + output-> + mode_dev->backlight_duty_cycle); + } else { + cdv_intel_lvds_set_backlight(dev, 0); + + REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) & + ~POWER_TARGET_ON); + do { + pp_status = REG_READ(PP_STATUS); + } while (pp_status & PP_ON); + } + gma_power_end(dev); +} + +static void cdv_intel_lvds_encoder_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) + cdv_intel_lvds_set_power(dev, output, true); + else + cdv_intel_lvds_set_power(dev, output, false); + /* XXX: We never power down the LVDS pairs. */ +} + +static void cdv_intel_lvds_save(struct drm_connector *connector) +{ +} + +static void cdv_intel_lvds_restore(struct drm_connector *connector) +{ +} + +int cdv_intel_lvds_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + struct drm_display_mode *fixed_mode = + psb_intel_output->mode_dev->panel_fixed_mode; + + /* just in case */ + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) + return MODE_NO_DBLESCAN; + + /* just in case */ + if (mode->flags & DRM_MODE_FLAG_INTERLACE) + return MODE_NO_INTERLACE; + + if (fixed_mode) { + if (mode->hdisplay > fixed_mode->hdisplay) + return MODE_PANEL; + if (mode->vdisplay > fixed_mode->vdisplay) + return MODE_PANEL; + } + return MODE_OK; +} + +bool cdv_intel_lvds_mode_fixup(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_encoder *tmp_encoder; + struct drm_display_mode *panel_fixed_mode = mode_dev->panel_fixed_mode; + + /* Should never happen!! */ + list_for_each_entry(tmp_encoder, &dev->mode_config.encoder_list, + head) { + if (tmp_encoder != encoder + && tmp_encoder->crtc == encoder->crtc) { + printk(KERN_ERR "Can't enable LVDS and another " + "encoder on the same pipe\n"); + return false; + } + } + + /* + * If we have timings from the BIOS for the panel, put them in + * to the adjusted mode. The CRTC will be set up for this mode, + * with the panel scaling set up to source from the H/VDisplay + * of the original mode. + */ + if (panel_fixed_mode != NULL) { + adjusted_mode->hdisplay = panel_fixed_mode->hdisplay; + adjusted_mode->hsync_start = panel_fixed_mode->hsync_start; + adjusted_mode->hsync_end = panel_fixed_mode->hsync_end; + adjusted_mode->htotal = panel_fixed_mode->htotal; + adjusted_mode->vdisplay = panel_fixed_mode->vdisplay; + adjusted_mode->vsync_start = panel_fixed_mode->vsync_start; + adjusted_mode->vsync_end = panel_fixed_mode->vsync_end; + adjusted_mode->vtotal = panel_fixed_mode->vtotal; + adjusted_mode->clock = panel_fixed_mode->clock; + drm_mode_set_crtcinfo(adjusted_mode, + CRTC_INTERLACE_HALVE_V); + } + + /* + * XXX: It would be nice to support lower refresh rates on the + * panels to reduce power consumption, and perhaps match the + * user's requested refresh rate. + */ + + return true; +} + +static void cdv_intel_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); + + cdv_intel_lvds_set_power(dev, output, false); + + gma_power_end(dev); +} + +static void cdv_intel_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 = + cdv_intel_lvds_get_max_backlight(dev); + + cdv_intel_lvds_set_power(dev, output, true); +} + +static void cdv_intel_lvds_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_psb_private *dev_priv = dev->dev_private; + u32 pfit_control; + + /* + * The LVDS pin pair will already have been turned on in the + * cdv_intel_crtc_mode_set since it has a large impact on the DPLL + * settings. + */ + + /* + * Enable automatic panel scaling so that non-native modes fill the + * screen. Should be enabled before the pipe is enabled, according to + * register description and PRM. + */ + if (mode->hdisplay != adjusted_mode->hdisplay || + mode->vdisplay != adjusted_mode->vdisplay) + pfit_control = (PFIT_ENABLE | VERT_AUTO_SCALE | + HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR | + HORIZ_INTERP_BILINEAR); + else + pfit_control = 0; + + if (dev_priv->lvds_dither) + pfit_control |= PANEL_8TO6_DITHER_ENABLE; + + REG_WRITE(PFIT_CONTROL, pfit_control); +} + +/** + * Detect the LVDS connection. + * + * This always returns CONNECTOR_STATUS_CONNECTED. + * This connector should only have + * been set up if the LVDS was actually connected anyway. + */ +static enum drm_connector_status cdv_intel_lvds_detect( + struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +/** + * Return the list of DDC modes if available, or the BIOS fixed mode otherwise. + */ +static int cdv_intel_lvds_get_modes(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + struct psb_intel_mode_device *mode_dev = + psb_intel_output->mode_dev; + int ret; + + ret = psb_intel_ddc_get_modes(psb_intel_output); + + if (ret) + return ret; + + /* Didn't get an EDID, so + * Set wide sync ranges so we get all modes + * handed to valid_mode for checking + */ + connector->display_info.min_vfreq = 0; + connector->display_info.max_vfreq = 200; + connector->display_info.min_hfreq = 0; + connector->display_info.max_hfreq = 200; + if (mode_dev->panel_fixed_mode != NULL) { + struct drm_display_mode *mode = + drm_mode_duplicate(dev, mode_dev->panel_fixed_mode); + drm_mode_probed_add(connector, mode); + return 1; + } + + return 0; +} + +/** + * cdv_intel_lvds_destroy - unregister and free LVDS structures + * @connector: connector to free + * + * Unregister the DDC bus for this connector then free the driver private + * structure. + */ +void cdv_intel_lvds_destroy(struct drm_connector *connector) +{ + struct psb_intel_output *psb_intel_output = + to_psb_intel_output(connector); + + if (psb_intel_output->ddc_bus) + psb_intel_i2c_destroy(psb_intel_output->ddc_bus); + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +int cdv_intel_lvds_set_property(struct drm_connector *connector, + struct drm_property *property, + uint64_t value) +{ + struct drm_encoder *encoder = connector->encoder; + + if (!strcmp(property->name, "scaling mode") && encoder) { + struct psb_intel_crtc *crtc = + to_psb_intel_crtc(encoder->crtc); + uint64_t curValue; + + if (!crtc) + return -1; + + switch (value) { + case DRM_MODE_SCALE_FULLSCREEN: + break; + case DRM_MODE_SCALE_NO_SCALE: + break; + case DRM_MODE_SCALE_ASPECT: + break; + default: + return -1; + } + + if (drm_connector_property_get_value(connector, + property, + &curValue)) + return -1; + + if (curValue == value) + return 0; + + if (drm_connector_property_set_value(connector, + property, + value)) + return -1; + + if (crtc->saved_mode.hdisplay != 0 && + crtc->saved_mode.vdisplay != 0) { + if (!drm_crtc_helper_set_mode(encoder->crtc, + &crtc->saved_mode, + encoder->crtc->x, + encoder->crtc->y, + encoder->crtc->fb)) + return -1; + } + } else if (!strcmp(property->name, "backlight") && encoder) { + if (drm_connector_property_set_value(connector, + property, + value)) + return -1; + else { +#ifdef CONFIG_BACKLIGHT_CLASS_DEVICE + struct drm_psb_private *dev_priv = + encoder->dev->dev_private; + struct backlight_device *bd = + dev_priv->backlight_device; + bd->props.brightness = value; + backlight_update_status(bd); +#endif + } + } else if (!strcmp(property->name, "DPMS") && encoder) { + struct drm_encoder_helper_funcs *helpers = + encoder->helper_private; + helpers->dpms(encoder, value); + } + return 0; +} + +static const struct drm_encoder_helper_funcs + cdv_intel_lvds_helper_funcs = { + .dpms = cdv_intel_lvds_encoder_dpms, + .mode_fixup = cdv_intel_lvds_mode_fixup, + .prepare = cdv_intel_lvds_prepare, + .mode_set = cdv_intel_lvds_mode_set, + .commit = cdv_intel_lvds_commit, +}; + +static const struct drm_connector_helper_funcs + cdv_intel_lvds_connector_helper_funcs = { + .get_modes = cdv_intel_lvds_get_modes, + .mode_valid = cdv_intel_lvds_mode_valid, + .best_encoder = psb_intel_best_encoder, +}; + +static const struct drm_connector_funcs cdv_intel_lvds_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .save = cdv_intel_lvds_save, + .restore = cdv_intel_lvds_restore, + .detect = cdv_intel_lvds_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .set_property = cdv_intel_lvds_set_property, + .destroy = cdv_intel_lvds_destroy, +}; + + +static void cdv_intel_lvds_enc_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +const struct drm_encoder_funcs cdv_intel_lvds_enc_funcs = { + .destroy = cdv_intel_lvds_enc_destroy, +}; + +/** + * cdv_intel_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 cdv_intel_lvds_init(struct drm_device *dev, + struct psb_intel_mode_device *mode_dev) +{ + struct psb_intel_output *psb_intel_output; + struct cdv_intel_lvds_priv *lvds_priv; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_display_mode *scan; + struct drm_crtc *crtc; + struct drm_psb_private *dev_priv = dev->dev_private; + u32 lvds; + int pipe; + + psb_intel_output = kzalloc(sizeof(struct psb_intel_output) + + sizeof(struct cdv_intel_lvds_priv), GFP_KERNEL); + if (!psb_intel_output) + return; + + lvds_priv = (struct cdv_intel_lvds_priv *)(psb_intel_output + 1); + + psb_intel_output->dev_priv = lvds_priv; + + 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, + &cdv_intel_lvds_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + + drm_encoder_init(dev, &psb_intel_output->enc, + &cdv_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, &cdv_intel_lvds_helper_funcs); + drm_connector_helper_add(connector, + &cdv_intel_lvds_connector_helper_funcs); + connector->display_info.subpixel_order = SubPixelHorizontalRGB; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + + /*Attach connector properties*/ + 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); + + /** + * Set up I2C bus + * FIXME: distroy i2c_bus when exit + */ + psb_intel_output->i2c_bus = psb_intel_i2c_create(dev, + GPIOB, + "LVDSBLC_B"); + if (!psb_intel_output->i2c_bus) { + dev_printk(KERN_ERR, + &dev->pdev->dev, "I2C bus registration failed.\n"); + goto failed_blc_i2c; + } + psb_intel_output->i2c_bus->slave_addr = 0x2C; + dev_priv->lvds_i2c_bus = psb_intel_output->i2c_bus; + + /* + * 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 + */ + + /* Set up the DDC bus. */ + psb_intel_output->ddc_bus = psb_intel_i2c_create(dev, + GPIOC, + "LVDSDDC_C"); + if (!psb_intel_output->ddc_bus) { + dev_printk(KERN_ERR, &dev->pdev->dev, + "DDC bus registration " "failed.\n"); + goto failed_ddc; + } + + /* + * Attempt to get the fixed panel mode from DDC. Assume that the + * preferred mode is the right one. + */ + psb_intel_ddc_get_modes(psb_intel_output); + 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 */ + } + } + + /* Failed to get EDID, what about VBT? do we need this?*/ + if (dev_priv->lfp_lvds_vbt_mode) { + mode_dev->panel_fixed_mode = + drm_mode_duplicate(dev, dev_priv->lfp_lvds_vbt_mode); + if (mode_dev->panel_fixed_mode) { + mode_dev->panel_fixed_mode->type |= + DRM_MODE_TYPE_PREFERRED; + goto out; /* FIXME: check for quirks */ + } + } + /* + * If we didn't get EDID, try checking if the panel is already turned + * on. If so, assume that whatever is currently programmed is the + * correct mode. + */ + lvds = REG_READ(LVDS); + pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0; + crtc = psb_intel_get_crtc_from_pipe(dev, pipe); + + if (crtc && (lvds & LVDS_PORT_EN)) { + mode_dev->panel_fixed_mode = + cdv_intel_crtc_mode_get(dev, crtc); + 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) { + DRM_DEBUG + ("Found no modes on the lvds, ignoring the LVDS\n"); + goto failed_find; + } + +out: + drm_sysfs_connector_add(connector); + return; + +failed_find: + printk(KERN_ERR "Failed find\n"); + if (psb_intel_output->ddc_bus) + psb_intel_i2c_destroy(psb_intel_output->ddc_bus); +failed_ddc: + printk(KERN_ERR "Failed DDC\n"); + if (psb_intel_output->i2c_bus) + psb_intel_i2c_destroy(psb_intel_output->i2c_bus); +failed_blc_i2c: + printk(KERN_ERR "Failed BLC\n"); + drm_encoder_cleanup(encoder); + drm_connector_cleanup(connector); + kfree(connector); +} |