18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright © 2006-2007 Intel Corporation
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Authors:
68c2ecf20Sopenharmony_ci *	Eric Anholt <eric@anholt.net>
78c2ecf20Sopenharmony_ci *	Dave Airlie <airlied@linux.ie>
88c2ecf20Sopenharmony_ci *	Jesse Barnes <jesse.barnes@intel.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/i2c.h>
128c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h>
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include "intel_bios.h"
178c2ecf20Sopenharmony_ci#include "power.h"
188c2ecf20Sopenharmony_ci#include "psb_drv.h"
198c2ecf20Sopenharmony_ci#include "psb_intel_drv.h"
208c2ecf20Sopenharmony_ci#include "psb_intel_reg.h"
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci/*
238c2ecf20Sopenharmony_ci * LVDS I2C backlight control macros
248c2ecf20Sopenharmony_ci */
258c2ecf20Sopenharmony_ci#define BRIGHTNESS_MAX_LEVEL 100
268c2ecf20Sopenharmony_ci#define BRIGHTNESS_MASK 0xFF
278c2ecf20Sopenharmony_ci#define BLC_I2C_TYPE	0x01
288c2ecf20Sopenharmony_ci#define BLC_PWM_TYPT	0x02
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define BLC_POLARITY_NORMAL 0
318c2ecf20Sopenharmony_ci#define BLC_POLARITY_INVERSE 1
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#define PSB_BLC_MAX_PWM_REG_FREQ       (0xFFFE)
348c2ecf20Sopenharmony_ci#define PSB_BLC_MIN_PWM_REG_FREQ	(0x2)
358c2ecf20Sopenharmony_ci#define PSB_BLC_PWM_PRECISION_FACTOR	(10)
368c2ecf20Sopenharmony_ci#define PSB_BACKLIGHT_PWM_CTL_SHIFT	(16)
378c2ecf20Sopenharmony_ci#define PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR (0xFFFE)
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct psb_intel_lvds_priv {
408c2ecf20Sopenharmony_ci	/*
418c2ecf20Sopenharmony_ci	 * Saved LVDO output states
428c2ecf20Sopenharmony_ci	 */
438c2ecf20Sopenharmony_ci	uint32_t savePP_ON;
448c2ecf20Sopenharmony_ci	uint32_t savePP_OFF;
458c2ecf20Sopenharmony_ci	uint32_t saveLVDS;
468c2ecf20Sopenharmony_ci	uint32_t savePP_CONTROL;
478c2ecf20Sopenharmony_ci	uint32_t savePP_CYCLE;
488c2ecf20Sopenharmony_ci	uint32_t savePFIT_CONTROL;
498c2ecf20Sopenharmony_ci	uint32_t savePFIT_PGM_RATIOS;
508c2ecf20Sopenharmony_ci	uint32_t saveBLC_PWM_CTL;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	struct psb_intel_i2c_chan *i2c_bus;
538c2ecf20Sopenharmony_ci	struct psb_intel_i2c_chan *ddc_bus;
548c2ecf20Sopenharmony_ci};
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/*
588c2ecf20Sopenharmony_ci * Returns the maximum level of the backlight duty cycle field.
598c2ecf20Sopenharmony_ci */
608c2ecf20Sopenharmony_cistatic u32 psb_intel_lvds_get_max_backlight(struct drm_device *dev)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
638c2ecf20Sopenharmony_ci	u32 ret;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (gma_power_begin(dev, false)) {
668c2ecf20Sopenharmony_ci		ret = REG_READ(BLC_PWM_CTL);
678c2ecf20Sopenharmony_ci		gma_power_end(dev);
688c2ecf20Sopenharmony_ci	} else /* Powered off, use the saved value */
698c2ecf20Sopenharmony_ci		ret = dev_priv->regs.saveBLC_PWM_CTL;
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	/* Top 15bits hold the frequency mask */
728c2ecf20Sopenharmony_ci	ret = (ret &  BACKLIGHT_MODULATION_FREQ_MASK) >>
738c2ecf20Sopenharmony_ci					BACKLIGHT_MODULATION_FREQ_SHIFT;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci        ret *= 2;	/* Return a 16bit range as needed for setting */
768c2ecf20Sopenharmony_ci        if (ret == 0)
778c2ecf20Sopenharmony_ci                dev_err(dev->dev, "BL bug: Reg %08x save %08X\n",
788c2ecf20Sopenharmony_ci                        REG_READ(BLC_PWM_CTL), dev_priv->regs.saveBLC_PWM_CTL);
798c2ecf20Sopenharmony_ci	return ret;
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci/*
838c2ecf20Sopenharmony_ci * Set LVDS backlight level by I2C command
848c2ecf20Sopenharmony_ci *
858c2ecf20Sopenharmony_ci * FIXME: at some point we need to both track this for PM and also
868c2ecf20Sopenharmony_ci * disable runtime pm on MRST if the brightness is nil (ie blanked)
878c2ecf20Sopenharmony_ci */
888c2ecf20Sopenharmony_cistatic int psb_lvds_i2c_set_brightness(struct drm_device *dev,
898c2ecf20Sopenharmony_ci					unsigned int level)
908c2ecf20Sopenharmony_ci{
918c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv =
928c2ecf20Sopenharmony_ci		(struct drm_psb_private *)dev->dev_private;
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	struct psb_intel_i2c_chan *lvds_i2c_bus = dev_priv->lvds_i2c_bus;
958c2ecf20Sopenharmony_ci	u8 out_buf[2];
968c2ecf20Sopenharmony_ci	unsigned int blc_i2c_brightness;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	struct i2c_msg msgs[] = {
998c2ecf20Sopenharmony_ci		{
1008c2ecf20Sopenharmony_ci			.addr = lvds_i2c_bus->slave_addr,
1018c2ecf20Sopenharmony_ci			.flags = 0,
1028c2ecf20Sopenharmony_ci			.len = 2,
1038c2ecf20Sopenharmony_ci			.buf = out_buf,
1048c2ecf20Sopenharmony_ci		}
1058c2ecf20Sopenharmony_ci	};
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	blc_i2c_brightness = BRIGHTNESS_MASK & ((unsigned int)level *
1088c2ecf20Sopenharmony_ci			     BRIGHTNESS_MASK /
1098c2ecf20Sopenharmony_ci			     BRIGHTNESS_MAX_LEVEL);
1108c2ecf20Sopenharmony_ci
1118c2ecf20Sopenharmony_ci	if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE)
1128c2ecf20Sopenharmony_ci		blc_i2c_brightness = BRIGHTNESS_MASK - blc_i2c_brightness;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	out_buf[0] = dev_priv->lvds_bl->brightnesscmd;
1158c2ecf20Sopenharmony_ci	out_buf[1] = (u8)blc_i2c_brightness;
1168c2ecf20Sopenharmony_ci
1178c2ecf20Sopenharmony_ci	if (i2c_transfer(&lvds_i2c_bus->adapter, msgs, 1) == 1) {
1188c2ecf20Sopenharmony_ci		dev_dbg(dev->dev, "I2C set brightness.(command, value) (%d, %d)\n",
1198c2ecf20Sopenharmony_ci			dev_priv->lvds_bl->brightnesscmd,
1208c2ecf20Sopenharmony_ci			blc_i2c_brightness);
1218c2ecf20Sopenharmony_ci		return 0;
1228c2ecf20Sopenharmony_ci	}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	dev_err(dev->dev, "I2C transfer error\n");
1258c2ecf20Sopenharmony_ci	return -1;
1268c2ecf20Sopenharmony_ci}
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic int psb_lvds_pwm_set_brightness(struct drm_device *dev, int level)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv =
1328c2ecf20Sopenharmony_ci			(struct drm_psb_private *)dev->dev_private;
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci	u32 max_pwm_blc;
1358c2ecf20Sopenharmony_ci	u32 blc_pwm_duty_cycle;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	max_pwm_blc = psb_intel_lvds_get_max_backlight(dev);
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	/*BLC_PWM_CTL Should be initiated while backlight device init*/
1408c2ecf20Sopenharmony_ci	BUG_ON(max_pwm_blc == 0);
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	blc_pwm_duty_cycle = level * max_pwm_blc / BRIGHTNESS_MAX_LEVEL;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	if (dev_priv->lvds_bl->pol == BLC_POLARITY_INVERSE)
1458c2ecf20Sopenharmony_ci		blc_pwm_duty_cycle = max_pwm_blc - blc_pwm_duty_cycle;
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	blc_pwm_duty_cycle &= PSB_BACKLIGHT_PWM_POLARITY_BIT_CLEAR;
1488c2ecf20Sopenharmony_ci	REG_WRITE(BLC_PWM_CTL,
1498c2ecf20Sopenharmony_ci		  (max_pwm_blc << PSB_BACKLIGHT_PWM_CTL_SHIFT) |
1508c2ecf20Sopenharmony_ci		  (blc_pwm_duty_cycle));
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci        dev_info(dev->dev, "Backlight lvds set brightness %08x\n",
1538c2ecf20Sopenharmony_ci		  (max_pwm_blc << PSB_BACKLIGHT_PWM_CTL_SHIFT) |
1548c2ecf20Sopenharmony_ci		  (blc_pwm_duty_cycle));
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ci	return 0;
1578c2ecf20Sopenharmony_ci}
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci/*
1608c2ecf20Sopenharmony_ci * Set LVDS backlight level either by I2C or PWM
1618c2ecf20Sopenharmony_ci */
1628c2ecf20Sopenharmony_civoid psb_intel_lvds_set_brightness(struct drm_device *dev, int level)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	dev_dbg(dev->dev, "backlight level is %d\n", level);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	if (!dev_priv->lvds_bl) {
1698c2ecf20Sopenharmony_ci		dev_err(dev->dev, "NO LVDS backlight info\n");
1708c2ecf20Sopenharmony_ci		return;
1718c2ecf20Sopenharmony_ci	}
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	if (dev_priv->lvds_bl->type == BLC_I2C_TYPE)
1748c2ecf20Sopenharmony_ci		psb_lvds_i2c_set_brightness(dev, level);
1758c2ecf20Sopenharmony_ci	else
1768c2ecf20Sopenharmony_ci		psb_lvds_pwm_set_brightness(dev, level);
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci/*
1808c2ecf20Sopenharmony_ci * Sets the backlight level.
1818c2ecf20Sopenharmony_ci *
1828c2ecf20Sopenharmony_ci * level: backlight level, from 0 to psb_intel_lvds_get_max_backlight().
1838c2ecf20Sopenharmony_ci */
1848c2ecf20Sopenharmony_cistatic void psb_intel_lvds_set_backlight(struct drm_device *dev, int level)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
1878c2ecf20Sopenharmony_ci	u32 blc_pwm_ctl;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	if (gma_power_begin(dev, false)) {
1908c2ecf20Sopenharmony_ci		blc_pwm_ctl = REG_READ(BLC_PWM_CTL);
1918c2ecf20Sopenharmony_ci		blc_pwm_ctl &= ~BACKLIGHT_DUTY_CYCLE_MASK;
1928c2ecf20Sopenharmony_ci		REG_WRITE(BLC_PWM_CTL,
1938c2ecf20Sopenharmony_ci				(blc_pwm_ctl |
1948c2ecf20Sopenharmony_ci				(level << BACKLIGHT_DUTY_CYCLE_SHIFT)));
1958c2ecf20Sopenharmony_ci		dev_priv->regs.saveBLC_PWM_CTL = (blc_pwm_ctl |
1968c2ecf20Sopenharmony_ci					(level << BACKLIGHT_DUTY_CYCLE_SHIFT));
1978c2ecf20Sopenharmony_ci		gma_power_end(dev);
1988c2ecf20Sopenharmony_ci	} else {
1998c2ecf20Sopenharmony_ci		blc_pwm_ctl = dev_priv->regs.saveBLC_PWM_CTL &
2008c2ecf20Sopenharmony_ci				~BACKLIGHT_DUTY_CYCLE_MASK;
2018c2ecf20Sopenharmony_ci		dev_priv->regs.saveBLC_PWM_CTL = (blc_pwm_ctl |
2028c2ecf20Sopenharmony_ci					(level << BACKLIGHT_DUTY_CYCLE_SHIFT));
2038c2ecf20Sopenharmony_ci	}
2048c2ecf20Sopenharmony_ci}
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci/*
2078c2ecf20Sopenharmony_ci * Sets the power state for the panel.
2088c2ecf20Sopenharmony_ci */
2098c2ecf20Sopenharmony_cistatic void psb_intel_lvds_set_power(struct drm_device *dev, bool on)
2108c2ecf20Sopenharmony_ci{
2118c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
2128c2ecf20Sopenharmony_ci	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev;
2138c2ecf20Sopenharmony_ci	u32 pp_status;
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_ci	if (!gma_power_begin(dev, true)) {
2168c2ecf20Sopenharmony_ci	        dev_err(dev->dev, "set power, chip off!\n");
2178c2ecf20Sopenharmony_ci		return;
2188c2ecf20Sopenharmony_ci        }
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	if (on) {
2218c2ecf20Sopenharmony_ci		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) |
2228c2ecf20Sopenharmony_ci			  POWER_TARGET_ON);
2238c2ecf20Sopenharmony_ci		do {
2248c2ecf20Sopenharmony_ci			pp_status = REG_READ(PP_STATUS);
2258c2ecf20Sopenharmony_ci		} while ((pp_status & PP_ON) == 0);
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci		psb_intel_lvds_set_backlight(dev,
2288c2ecf20Sopenharmony_ci					     mode_dev->backlight_duty_cycle);
2298c2ecf20Sopenharmony_ci	} else {
2308c2ecf20Sopenharmony_ci		psb_intel_lvds_set_backlight(dev, 0);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) &
2338c2ecf20Sopenharmony_ci			  ~POWER_TARGET_ON);
2348c2ecf20Sopenharmony_ci		do {
2358c2ecf20Sopenharmony_ci			pp_status = REG_READ(PP_STATUS);
2368c2ecf20Sopenharmony_ci		} while (pp_status & PP_ON);
2378c2ecf20Sopenharmony_ci	}
2388c2ecf20Sopenharmony_ci
2398c2ecf20Sopenharmony_ci	gma_power_end(dev);
2408c2ecf20Sopenharmony_ci}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_cistatic void psb_intel_lvds_encoder_dpms(struct drm_encoder *encoder, int mode)
2438c2ecf20Sopenharmony_ci{
2448c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
2458c2ecf20Sopenharmony_ci
2468c2ecf20Sopenharmony_ci	if (mode == DRM_MODE_DPMS_ON)
2478c2ecf20Sopenharmony_ci		psb_intel_lvds_set_power(dev, true);
2488c2ecf20Sopenharmony_ci	else
2498c2ecf20Sopenharmony_ci		psb_intel_lvds_set_power(dev, false);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci	/* XXX: We never power down the LVDS pairs. */
2528c2ecf20Sopenharmony_ci}
2538c2ecf20Sopenharmony_ci
2548c2ecf20Sopenharmony_cistatic void psb_intel_lvds_save(struct drm_connector *connector)
2558c2ecf20Sopenharmony_ci{
2568c2ecf20Sopenharmony_ci	struct drm_device *dev = connector->dev;
2578c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv =
2588c2ecf20Sopenharmony_ci		(struct drm_psb_private *)dev->dev_private;
2598c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder = gma_attached_encoder(connector);
2608c2ecf20Sopenharmony_ci	struct psb_intel_lvds_priv *lvds_priv =
2618c2ecf20Sopenharmony_ci		(struct psb_intel_lvds_priv *)gma_encoder->dev_priv;
2628c2ecf20Sopenharmony_ci
2638c2ecf20Sopenharmony_ci	lvds_priv->savePP_ON = REG_READ(LVDSPP_ON);
2648c2ecf20Sopenharmony_ci	lvds_priv->savePP_OFF = REG_READ(LVDSPP_OFF);
2658c2ecf20Sopenharmony_ci	lvds_priv->saveLVDS = REG_READ(LVDS);
2668c2ecf20Sopenharmony_ci	lvds_priv->savePP_CONTROL = REG_READ(PP_CONTROL);
2678c2ecf20Sopenharmony_ci	lvds_priv->savePP_CYCLE = REG_READ(PP_CYCLE);
2688c2ecf20Sopenharmony_ci	/*lvds_priv->savePP_DIVISOR = REG_READ(PP_DIVISOR);*/
2698c2ecf20Sopenharmony_ci	lvds_priv->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL);
2708c2ecf20Sopenharmony_ci	lvds_priv->savePFIT_CONTROL = REG_READ(PFIT_CONTROL);
2718c2ecf20Sopenharmony_ci	lvds_priv->savePFIT_PGM_RATIOS = REG_READ(PFIT_PGM_RATIOS);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	/*TODO: move backlight_duty_cycle to psb_intel_lvds_priv*/
2748c2ecf20Sopenharmony_ci	dev_priv->backlight_duty_cycle = (dev_priv->regs.saveBLC_PWM_CTL &
2758c2ecf20Sopenharmony_ci						BACKLIGHT_DUTY_CYCLE_MASK);
2768c2ecf20Sopenharmony_ci
2778c2ecf20Sopenharmony_ci	/*
2788c2ecf20Sopenharmony_ci	 * If the light is off at server startup,
2798c2ecf20Sopenharmony_ci	 * just make it full brightness
2808c2ecf20Sopenharmony_ci	 */
2818c2ecf20Sopenharmony_ci	if (dev_priv->backlight_duty_cycle == 0)
2828c2ecf20Sopenharmony_ci		dev_priv->backlight_duty_cycle =
2838c2ecf20Sopenharmony_ci		psb_intel_lvds_get_max_backlight(dev);
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	dev_dbg(dev->dev, "(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)\n",
2868c2ecf20Sopenharmony_ci			lvds_priv->savePP_ON,
2878c2ecf20Sopenharmony_ci			lvds_priv->savePP_OFF,
2888c2ecf20Sopenharmony_ci			lvds_priv->saveLVDS,
2898c2ecf20Sopenharmony_ci			lvds_priv->savePP_CONTROL,
2908c2ecf20Sopenharmony_ci			lvds_priv->savePP_CYCLE,
2918c2ecf20Sopenharmony_ci			lvds_priv->saveBLC_PWM_CTL);
2928c2ecf20Sopenharmony_ci}
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_cistatic void psb_intel_lvds_restore(struct drm_connector *connector)
2958c2ecf20Sopenharmony_ci{
2968c2ecf20Sopenharmony_ci	struct drm_device *dev = connector->dev;
2978c2ecf20Sopenharmony_ci	u32 pp_status;
2988c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder = gma_attached_encoder(connector);
2998c2ecf20Sopenharmony_ci	struct psb_intel_lvds_priv *lvds_priv =
3008c2ecf20Sopenharmony_ci		(struct psb_intel_lvds_priv *)gma_encoder->dev_priv;
3018c2ecf20Sopenharmony_ci
3028c2ecf20Sopenharmony_ci	dev_dbg(dev->dev, "(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)\n",
3038c2ecf20Sopenharmony_ci			lvds_priv->savePP_ON,
3048c2ecf20Sopenharmony_ci			lvds_priv->savePP_OFF,
3058c2ecf20Sopenharmony_ci			lvds_priv->saveLVDS,
3068c2ecf20Sopenharmony_ci			lvds_priv->savePP_CONTROL,
3078c2ecf20Sopenharmony_ci			lvds_priv->savePP_CYCLE,
3088c2ecf20Sopenharmony_ci			lvds_priv->saveBLC_PWM_CTL);
3098c2ecf20Sopenharmony_ci
3108c2ecf20Sopenharmony_ci	REG_WRITE(BLC_PWM_CTL, lvds_priv->saveBLC_PWM_CTL);
3118c2ecf20Sopenharmony_ci	REG_WRITE(PFIT_CONTROL, lvds_priv->savePFIT_CONTROL);
3128c2ecf20Sopenharmony_ci	REG_WRITE(PFIT_PGM_RATIOS, lvds_priv->savePFIT_PGM_RATIOS);
3138c2ecf20Sopenharmony_ci	REG_WRITE(LVDSPP_ON, lvds_priv->savePP_ON);
3148c2ecf20Sopenharmony_ci	REG_WRITE(LVDSPP_OFF, lvds_priv->savePP_OFF);
3158c2ecf20Sopenharmony_ci	/*REG_WRITE(PP_DIVISOR, lvds_priv->savePP_DIVISOR);*/
3168c2ecf20Sopenharmony_ci	REG_WRITE(PP_CYCLE, lvds_priv->savePP_CYCLE);
3178c2ecf20Sopenharmony_ci	REG_WRITE(PP_CONTROL, lvds_priv->savePP_CONTROL);
3188c2ecf20Sopenharmony_ci	REG_WRITE(LVDS, lvds_priv->saveLVDS);
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ci	if (lvds_priv->savePP_CONTROL & POWER_TARGET_ON) {
3218c2ecf20Sopenharmony_ci		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) |
3228c2ecf20Sopenharmony_ci			POWER_TARGET_ON);
3238c2ecf20Sopenharmony_ci		do {
3248c2ecf20Sopenharmony_ci			pp_status = REG_READ(PP_STATUS);
3258c2ecf20Sopenharmony_ci		} while ((pp_status & PP_ON) == 0);
3268c2ecf20Sopenharmony_ci	} else {
3278c2ecf20Sopenharmony_ci		REG_WRITE(PP_CONTROL, REG_READ(PP_CONTROL) &
3288c2ecf20Sopenharmony_ci			~POWER_TARGET_ON);
3298c2ecf20Sopenharmony_ci		do {
3308c2ecf20Sopenharmony_ci			pp_status = REG_READ(PP_STATUS);
3318c2ecf20Sopenharmony_ci		} while (pp_status & PP_ON);
3328c2ecf20Sopenharmony_ci	}
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cienum drm_mode_status psb_intel_lvds_mode_valid(struct drm_connector *connector,
3368c2ecf20Sopenharmony_ci				 struct drm_display_mode *mode)
3378c2ecf20Sopenharmony_ci{
3388c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = connector->dev->dev_private;
3398c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder = gma_attached_encoder(connector);
3408c2ecf20Sopenharmony_ci	struct drm_display_mode *fixed_mode =
3418c2ecf20Sopenharmony_ci					dev_priv->mode_dev.panel_fixed_mode;
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci	if (gma_encoder->type == INTEL_OUTPUT_MIPI2)
3448c2ecf20Sopenharmony_ci		fixed_mode = dev_priv->mode_dev.panel_fixed_mode2;
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci	/* just in case */
3478c2ecf20Sopenharmony_ci	if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
3488c2ecf20Sopenharmony_ci		return MODE_NO_DBLESCAN;
3498c2ecf20Sopenharmony_ci
3508c2ecf20Sopenharmony_ci	/* just in case */
3518c2ecf20Sopenharmony_ci	if (mode->flags & DRM_MODE_FLAG_INTERLACE)
3528c2ecf20Sopenharmony_ci		return MODE_NO_INTERLACE;
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	if (fixed_mode) {
3558c2ecf20Sopenharmony_ci		if (mode->hdisplay > fixed_mode->hdisplay)
3568c2ecf20Sopenharmony_ci			return MODE_PANEL;
3578c2ecf20Sopenharmony_ci		if (mode->vdisplay > fixed_mode->vdisplay)
3588c2ecf20Sopenharmony_ci			return MODE_PANEL;
3598c2ecf20Sopenharmony_ci	}
3608c2ecf20Sopenharmony_ci	return MODE_OK;
3618c2ecf20Sopenharmony_ci}
3628c2ecf20Sopenharmony_ci
3638c2ecf20Sopenharmony_cibool psb_intel_lvds_mode_fixup(struct drm_encoder *encoder,
3648c2ecf20Sopenharmony_ci				  const struct drm_display_mode *mode,
3658c2ecf20Sopenharmony_ci				  struct drm_display_mode *adjusted_mode)
3668c2ecf20Sopenharmony_ci{
3678c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
3688c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
3698c2ecf20Sopenharmony_ci	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev;
3708c2ecf20Sopenharmony_ci	struct gma_crtc *gma_crtc = to_gma_crtc(encoder->crtc);
3718c2ecf20Sopenharmony_ci	struct drm_encoder *tmp_encoder;
3728c2ecf20Sopenharmony_ci	struct drm_display_mode *panel_fixed_mode = mode_dev->panel_fixed_mode;
3738c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder = to_gma_encoder(encoder);
3748c2ecf20Sopenharmony_ci
3758c2ecf20Sopenharmony_ci	if (gma_encoder->type == INTEL_OUTPUT_MIPI2)
3768c2ecf20Sopenharmony_ci		panel_fixed_mode = mode_dev->panel_fixed_mode2;
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	/* PSB requires the LVDS is on pipe B, MRST has only one pipe anyway */
3798c2ecf20Sopenharmony_ci	if (!IS_MRST(dev) && gma_crtc->pipe == 0) {
3808c2ecf20Sopenharmony_ci		pr_err("Can't support LVDS on pipe A\n");
3818c2ecf20Sopenharmony_ci		return false;
3828c2ecf20Sopenharmony_ci	}
3838c2ecf20Sopenharmony_ci	if (IS_MRST(dev) && gma_crtc->pipe != 0) {
3848c2ecf20Sopenharmony_ci		pr_err("Must use PIPE A\n");
3858c2ecf20Sopenharmony_ci		return false;
3868c2ecf20Sopenharmony_ci	}
3878c2ecf20Sopenharmony_ci	/* Should never happen!! */
3888c2ecf20Sopenharmony_ci	list_for_each_entry(tmp_encoder, &dev->mode_config.encoder_list,
3898c2ecf20Sopenharmony_ci			    head) {
3908c2ecf20Sopenharmony_ci		if (tmp_encoder != encoder
3918c2ecf20Sopenharmony_ci		    && tmp_encoder->crtc == encoder->crtc) {
3928c2ecf20Sopenharmony_ci			pr_err("Can't enable LVDS and another encoder on the same pipe\n");
3938c2ecf20Sopenharmony_ci			return false;
3948c2ecf20Sopenharmony_ci		}
3958c2ecf20Sopenharmony_ci	}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci	/*
3988c2ecf20Sopenharmony_ci	 * If we have timings from the BIOS for the panel, put them in
3998c2ecf20Sopenharmony_ci	 * to the adjusted mode.  The CRTC will be set up for this mode,
4008c2ecf20Sopenharmony_ci	 * with the panel scaling set up to source from the H/VDisplay
4018c2ecf20Sopenharmony_ci	 * of the original mode.
4028c2ecf20Sopenharmony_ci	 */
4038c2ecf20Sopenharmony_ci	if (panel_fixed_mode != NULL) {
4048c2ecf20Sopenharmony_ci		adjusted_mode->hdisplay = panel_fixed_mode->hdisplay;
4058c2ecf20Sopenharmony_ci		adjusted_mode->hsync_start = panel_fixed_mode->hsync_start;
4068c2ecf20Sopenharmony_ci		adjusted_mode->hsync_end = panel_fixed_mode->hsync_end;
4078c2ecf20Sopenharmony_ci		adjusted_mode->htotal = panel_fixed_mode->htotal;
4088c2ecf20Sopenharmony_ci		adjusted_mode->vdisplay = panel_fixed_mode->vdisplay;
4098c2ecf20Sopenharmony_ci		adjusted_mode->vsync_start = panel_fixed_mode->vsync_start;
4108c2ecf20Sopenharmony_ci		adjusted_mode->vsync_end = panel_fixed_mode->vsync_end;
4118c2ecf20Sopenharmony_ci		adjusted_mode->vtotal = panel_fixed_mode->vtotal;
4128c2ecf20Sopenharmony_ci		adjusted_mode->clock = panel_fixed_mode->clock;
4138c2ecf20Sopenharmony_ci		drm_mode_set_crtcinfo(adjusted_mode,
4148c2ecf20Sopenharmony_ci				      CRTC_INTERLACE_HALVE_V);
4158c2ecf20Sopenharmony_ci	}
4168c2ecf20Sopenharmony_ci
4178c2ecf20Sopenharmony_ci	/*
4188c2ecf20Sopenharmony_ci	 * XXX: It would be nice to support lower refresh rates on the
4198c2ecf20Sopenharmony_ci	 * panels to reduce power consumption, and perhaps match the
4208c2ecf20Sopenharmony_ci	 * user's requested refresh rate.
4218c2ecf20Sopenharmony_ci	 */
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci	return true;
4248c2ecf20Sopenharmony_ci}
4258c2ecf20Sopenharmony_ci
4268c2ecf20Sopenharmony_cistatic void psb_intel_lvds_prepare(struct drm_encoder *encoder)
4278c2ecf20Sopenharmony_ci{
4288c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4298c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
4308c2ecf20Sopenharmony_ci	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev;
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ci	if (!gma_power_begin(dev, true))
4338c2ecf20Sopenharmony_ci		return;
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	mode_dev->saveBLC_PWM_CTL = REG_READ(BLC_PWM_CTL);
4368c2ecf20Sopenharmony_ci	mode_dev->backlight_duty_cycle = (mode_dev->saveBLC_PWM_CTL &
4378c2ecf20Sopenharmony_ci					  BACKLIGHT_DUTY_CYCLE_MASK);
4388c2ecf20Sopenharmony_ci
4398c2ecf20Sopenharmony_ci	psb_intel_lvds_set_power(dev, false);
4408c2ecf20Sopenharmony_ci
4418c2ecf20Sopenharmony_ci	gma_power_end(dev);
4428c2ecf20Sopenharmony_ci}
4438c2ecf20Sopenharmony_ci
4448c2ecf20Sopenharmony_cistatic void psb_intel_lvds_commit(struct drm_encoder *encoder)
4458c2ecf20Sopenharmony_ci{
4468c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4478c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
4488c2ecf20Sopenharmony_ci	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	if (mode_dev->backlight_duty_cycle == 0)
4518c2ecf20Sopenharmony_ci		mode_dev->backlight_duty_cycle =
4528c2ecf20Sopenharmony_ci		    psb_intel_lvds_get_max_backlight(dev);
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	psb_intel_lvds_set_power(dev, true);
4558c2ecf20Sopenharmony_ci}
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_cistatic void psb_intel_lvds_mode_set(struct drm_encoder *encoder,
4588c2ecf20Sopenharmony_ci				struct drm_display_mode *mode,
4598c2ecf20Sopenharmony_ci				struct drm_display_mode *adjusted_mode)
4608c2ecf20Sopenharmony_ci{
4618c2ecf20Sopenharmony_ci	struct drm_device *dev = encoder->dev;
4628c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
4638c2ecf20Sopenharmony_ci	u32 pfit_control;
4648c2ecf20Sopenharmony_ci
4658c2ecf20Sopenharmony_ci	/*
4668c2ecf20Sopenharmony_ci	 * The LVDS pin pair will already have been turned on in the
4678c2ecf20Sopenharmony_ci	 * psb_intel_crtc_mode_set since it has a large impact on the DPLL
4688c2ecf20Sopenharmony_ci	 * settings.
4698c2ecf20Sopenharmony_ci	 */
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	/*
4728c2ecf20Sopenharmony_ci	 * Enable automatic panel scaling so that non-native modes fill the
4738c2ecf20Sopenharmony_ci	 * screen.  Should be enabled before the pipe is enabled, according to
4748c2ecf20Sopenharmony_ci	 * register description and PRM.
4758c2ecf20Sopenharmony_ci	 */
4768c2ecf20Sopenharmony_ci	if (mode->hdisplay != adjusted_mode->hdisplay ||
4778c2ecf20Sopenharmony_ci	    mode->vdisplay != adjusted_mode->vdisplay)
4788c2ecf20Sopenharmony_ci		pfit_control = (PFIT_ENABLE | VERT_AUTO_SCALE |
4798c2ecf20Sopenharmony_ci				HORIZ_AUTO_SCALE | VERT_INTERP_BILINEAR |
4808c2ecf20Sopenharmony_ci				HORIZ_INTERP_BILINEAR);
4818c2ecf20Sopenharmony_ci	else
4828c2ecf20Sopenharmony_ci		pfit_control = 0;
4838c2ecf20Sopenharmony_ci
4848c2ecf20Sopenharmony_ci	if (dev_priv->lvds_dither)
4858c2ecf20Sopenharmony_ci		pfit_control |= PANEL_8TO6_DITHER_ENABLE;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci	REG_WRITE(PFIT_CONTROL, pfit_control);
4888c2ecf20Sopenharmony_ci}
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci/*
4918c2ecf20Sopenharmony_ci * Return the list of DDC modes if available, or the BIOS fixed mode otherwise.
4928c2ecf20Sopenharmony_ci */
4938c2ecf20Sopenharmony_cistatic int psb_intel_lvds_get_modes(struct drm_connector *connector)
4948c2ecf20Sopenharmony_ci{
4958c2ecf20Sopenharmony_ci	struct drm_device *dev = connector->dev;
4968c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
4978c2ecf20Sopenharmony_ci	struct psb_intel_mode_device *mode_dev = &dev_priv->mode_dev;
4988c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder = gma_attached_encoder(connector);
4998c2ecf20Sopenharmony_ci	struct psb_intel_lvds_priv *lvds_priv = gma_encoder->dev_priv;
5008c2ecf20Sopenharmony_ci	int ret = 0;
5018c2ecf20Sopenharmony_ci
5028c2ecf20Sopenharmony_ci	if (!IS_MRST(dev))
5038c2ecf20Sopenharmony_ci		ret = psb_intel_ddc_get_modes(connector, &lvds_priv->i2c_bus->adapter);
5048c2ecf20Sopenharmony_ci
5058c2ecf20Sopenharmony_ci	if (ret)
5068c2ecf20Sopenharmony_ci		return ret;
5078c2ecf20Sopenharmony_ci
5088c2ecf20Sopenharmony_ci	if (mode_dev->panel_fixed_mode != NULL) {
5098c2ecf20Sopenharmony_ci		struct drm_display_mode *mode =
5108c2ecf20Sopenharmony_ci		    drm_mode_duplicate(dev, mode_dev->panel_fixed_mode);
5118c2ecf20Sopenharmony_ci		drm_mode_probed_add(connector, mode);
5128c2ecf20Sopenharmony_ci		return 1;
5138c2ecf20Sopenharmony_ci	}
5148c2ecf20Sopenharmony_ci
5158c2ecf20Sopenharmony_ci	return 0;
5168c2ecf20Sopenharmony_ci}
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci/**
5198c2ecf20Sopenharmony_ci * psb_intel_lvds_destroy - unregister and free LVDS structures
5208c2ecf20Sopenharmony_ci * @connector: connector to free
5218c2ecf20Sopenharmony_ci *
5228c2ecf20Sopenharmony_ci * Unregister the DDC bus for this connector then free the driver private
5238c2ecf20Sopenharmony_ci * structure.
5248c2ecf20Sopenharmony_ci */
5258c2ecf20Sopenharmony_civoid psb_intel_lvds_destroy(struct drm_connector *connector)
5268c2ecf20Sopenharmony_ci{
5278c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder = gma_attached_encoder(connector);
5288c2ecf20Sopenharmony_ci	struct psb_intel_lvds_priv *lvds_priv = gma_encoder->dev_priv;
5298c2ecf20Sopenharmony_ci
5308c2ecf20Sopenharmony_ci	psb_intel_i2c_destroy(lvds_priv->ddc_bus);
5318c2ecf20Sopenharmony_ci	drm_connector_unregister(connector);
5328c2ecf20Sopenharmony_ci	drm_connector_cleanup(connector);
5338c2ecf20Sopenharmony_ci	kfree(connector);
5348c2ecf20Sopenharmony_ci}
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ciint psb_intel_lvds_set_property(struct drm_connector *connector,
5378c2ecf20Sopenharmony_ci				       struct drm_property *property,
5388c2ecf20Sopenharmony_ci				       uint64_t value)
5398c2ecf20Sopenharmony_ci{
5408c2ecf20Sopenharmony_ci	struct drm_encoder *encoder = connector->encoder;
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_ci	if (!encoder)
5438c2ecf20Sopenharmony_ci		return -1;
5448c2ecf20Sopenharmony_ci
5458c2ecf20Sopenharmony_ci	if (!strcmp(property->name, "scaling mode")) {
5468c2ecf20Sopenharmony_ci		struct gma_crtc *crtc = to_gma_crtc(encoder->crtc);
5478c2ecf20Sopenharmony_ci		uint64_t curval;
5488c2ecf20Sopenharmony_ci
5498c2ecf20Sopenharmony_ci		if (!crtc)
5508c2ecf20Sopenharmony_ci			goto set_prop_error;
5518c2ecf20Sopenharmony_ci
5528c2ecf20Sopenharmony_ci		switch (value) {
5538c2ecf20Sopenharmony_ci		case DRM_MODE_SCALE_FULLSCREEN:
5548c2ecf20Sopenharmony_ci			break;
5558c2ecf20Sopenharmony_ci		case DRM_MODE_SCALE_NO_SCALE:
5568c2ecf20Sopenharmony_ci			break;
5578c2ecf20Sopenharmony_ci		case DRM_MODE_SCALE_ASPECT:
5588c2ecf20Sopenharmony_ci			break;
5598c2ecf20Sopenharmony_ci		default:
5608c2ecf20Sopenharmony_ci			goto set_prop_error;
5618c2ecf20Sopenharmony_ci		}
5628c2ecf20Sopenharmony_ci
5638c2ecf20Sopenharmony_ci		if (drm_object_property_get_value(&connector->base,
5648c2ecf20Sopenharmony_ci						     property,
5658c2ecf20Sopenharmony_ci						     &curval))
5668c2ecf20Sopenharmony_ci			goto set_prop_error;
5678c2ecf20Sopenharmony_ci
5688c2ecf20Sopenharmony_ci		if (curval == value)
5698c2ecf20Sopenharmony_ci			goto set_prop_done;
5708c2ecf20Sopenharmony_ci
5718c2ecf20Sopenharmony_ci		if (drm_object_property_set_value(&connector->base,
5728c2ecf20Sopenharmony_ci							property,
5738c2ecf20Sopenharmony_ci							value))
5748c2ecf20Sopenharmony_ci			goto set_prop_error;
5758c2ecf20Sopenharmony_ci
5768c2ecf20Sopenharmony_ci		if (crtc->saved_mode.hdisplay != 0 &&
5778c2ecf20Sopenharmony_ci		    crtc->saved_mode.vdisplay != 0) {
5788c2ecf20Sopenharmony_ci			if (!drm_crtc_helper_set_mode(encoder->crtc,
5798c2ecf20Sopenharmony_ci						      &crtc->saved_mode,
5808c2ecf20Sopenharmony_ci						      encoder->crtc->x,
5818c2ecf20Sopenharmony_ci						      encoder->crtc->y,
5828c2ecf20Sopenharmony_ci						      encoder->crtc->primary->fb))
5838c2ecf20Sopenharmony_ci				goto set_prop_error;
5848c2ecf20Sopenharmony_ci		}
5858c2ecf20Sopenharmony_ci	} else if (!strcmp(property->name, "backlight")) {
5868c2ecf20Sopenharmony_ci		if (drm_object_property_set_value(&connector->base,
5878c2ecf20Sopenharmony_ci							property,
5888c2ecf20Sopenharmony_ci							value))
5898c2ecf20Sopenharmony_ci			goto set_prop_error;
5908c2ecf20Sopenharmony_ci		else
5918c2ecf20Sopenharmony_ci                        gma_backlight_set(encoder->dev, value);
5928c2ecf20Sopenharmony_ci	} else if (!strcmp(property->name, "DPMS")) {
5938c2ecf20Sopenharmony_ci		const struct drm_encoder_helper_funcs *hfuncs
5948c2ecf20Sopenharmony_ci						= encoder->helper_private;
5958c2ecf20Sopenharmony_ci		hfuncs->dpms(encoder, value);
5968c2ecf20Sopenharmony_ci	}
5978c2ecf20Sopenharmony_ci
5988c2ecf20Sopenharmony_ciset_prop_done:
5998c2ecf20Sopenharmony_ci	return 0;
6008c2ecf20Sopenharmony_ciset_prop_error:
6018c2ecf20Sopenharmony_ci	return -1;
6028c2ecf20Sopenharmony_ci}
6038c2ecf20Sopenharmony_ci
6048c2ecf20Sopenharmony_cistatic const struct drm_encoder_helper_funcs psb_intel_lvds_helper_funcs = {
6058c2ecf20Sopenharmony_ci	.dpms = psb_intel_lvds_encoder_dpms,
6068c2ecf20Sopenharmony_ci	.mode_fixup = psb_intel_lvds_mode_fixup,
6078c2ecf20Sopenharmony_ci	.prepare = psb_intel_lvds_prepare,
6088c2ecf20Sopenharmony_ci	.mode_set = psb_intel_lvds_mode_set,
6098c2ecf20Sopenharmony_ci	.commit = psb_intel_lvds_commit,
6108c2ecf20Sopenharmony_ci};
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_ciconst struct drm_connector_helper_funcs
6138c2ecf20Sopenharmony_ci				psb_intel_lvds_connector_helper_funcs = {
6148c2ecf20Sopenharmony_ci	.get_modes = psb_intel_lvds_get_modes,
6158c2ecf20Sopenharmony_ci	.mode_valid = psb_intel_lvds_mode_valid,
6168c2ecf20Sopenharmony_ci	.best_encoder = gma_best_encoder,
6178c2ecf20Sopenharmony_ci};
6188c2ecf20Sopenharmony_ci
6198c2ecf20Sopenharmony_ciconst struct drm_connector_funcs psb_intel_lvds_connector_funcs = {
6208c2ecf20Sopenharmony_ci	.dpms = drm_helper_connector_dpms,
6218c2ecf20Sopenharmony_ci	.fill_modes = drm_helper_probe_single_connector_modes,
6228c2ecf20Sopenharmony_ci	.set_property = psb_intel_lvds_set_property,
6238c2ecf20Sopenharmony_ci	.destroy = psb_intel_lvds_destroy,
6248c2ecf20Sopenharmony_ci};
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_ci/**
6278c2ecf20Sopenharmony_ci * psb_intel_lvds_init - setup LVDS connectors on this device
6288c2ecf20Sopenharmony_ci * @dev: drm device
6298c2ecf20Sopenharmony_ci *
6308c2ecf20Sopenharmony_ci * Create the connector, register the LVDS DDC bus, and try to figure out what
6318c2ecf20Sopenharmony_ci * modes we can display on the LVDS panel (if present).
6328c2ecf20Sopenharmony_ci */
6338c2ecf20Sopenharmony_civoid psb_intel_lvds_init(struct drm_device *dev,
6348c2ecf20Sopenharmony_ci			 struct psb_intel_mode_device *mode_dev)
6358c2ecf20Sopenharmony_ci{
6368c2ecf20Sopenharmony_ci	struct gma_encoder *gma_encoder;
6378c2ecf20Sopenharmony_ci	struct gma_connector *gma_connector;
6388c2ecf20Sopenharmony_ci	struct psb_intel_lvds_priv *lvds_priv;
6398c2ecf20Sopenharmony_ci	struct drm_connector *connector;
6408c2ecf20Sopenharmony_ci	struct drm_encoder *encoder;
6418c2ecf20Sopenharmony_ci	struct drm_display_mode *scan;	/* *modes, *bios_mode; */
6428c2ecf20Sopenharmony_ci	struct drm_crtc *crtc;
6438c2ecf20Sopenharmony_ci	struct drm_psb_private *dev_priv = dev->dev_private;
6448c2ecf20Sopenharmony_ci	u32 lvds;
6458c2ecf20Sopenharmony_ci	int pipe;
6468c2ecf20Sopenharmony_ci
6478c2ecf20Sopenharmony_ci	gma_encoder = kzalloc(sizeof(struct gma_encoder), GFP_KERNEL);
6488c2ecf20Sopenharmony_ci	if (!gma_encoder) {
6498c2ecf20Sopenharmony_ci		dev_err(dev->dev, "gma_encoder allocation error\n");
6508c2ecf20Sopenharmony_ci		return;
6518c2ecf20Sopenharmony_ci	}
6528c2ecf20Sopenharmony_ci
6538c2ecf20Sopenharmony_ci	gma_connector = kzalloc(sizeof(struct gma_connector), GFP_KERNEL);
6548c2ecf20Sopenharmony_ci	if (!gma_connector) {
6558c2ecf20Sopenharmony_ci		dev_err(dev->dev, "gma_connector allocation error\n");
6568c2ecf20Sopenharmony_ci		goto failed_encoder;
6578c2ecf20Sopenharmony_ci	}
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci	lvds_priv = kzalloc(sizeof(struct psb_intel_lvds_priv), GFP_KERNEL);
6608c2ecf20Sopenharmony_ci	if (!lvds_priv) {
6618c2ecf20Sopenharmony_ci		dev_err(dev->dev, "LVDS private allocation error\n");
6628c2ecf20Sopenharmony_ci		goto failed_connector;
6638c2ecf20Sopenharmony_ci	}
6648c2ecf20Sopenharmony_ci
6658c2ecf20Sopenharmony_ci	gma_encoder->dev_priv = lvds_priv;
6668c2ecf20Sopenharmony_ci
6678c2ecf20Sopenharmony_ci	connector = &gma_connector->base;
6688c2ecf20Sopenharmony_ci	gma_connector->save = psb_intel_lvds_save;
6698c2ecf20Sopenharmony_ci	gma_connector->restore = psb_intel_lvds_restore;
6708c2ecf20Sopenharmony_ci
6718c2ecf20Sopenharmony_ci	encoder = &gma_encoder->base;
6728c2ecf20Sopenharmony_ci	drm_connector_init(dev, connector,
6738c2ecf20Sopenharmony_ci			   &psb_intel_lvds_connector_funcs,
6748c2ecf20Sopenharmony_ci			   DRM_MODE_CONNECTOR_LVDS);
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_ci	drm_simple_encoder_init(dev, encoder, DRM_MODE_ENCODER_LVDS);
6778c2ecf20Sopenharmony_ci
6788c2ecf20Sopenharmony_ci	gma_connector_attach_encoder(gma_connector, gma_encoder);
6798c2ecf20Sopenharmony_ci	gma_encoder->type = INTEL_OUTPUT_LVDS;
6808c2ecf20Sopenharmony_ci
6818c2ecf20Sopenharmony_ci	drm_encoder_helper_add(encoder, &psb_intel_lvds_helper_funcs);
6828c2ecf20Sopenharmony_ci	drm_connector_helper_add(connector,
6838c2ecf20Sopenharmony_ci				 &psb_intel_lvds_connector_helper_funcs);
6848c2ecf20Sopenharmony_ci	connector->display_info.subpixel_order = SubPixelHorizontalRGB;
6858c2ecf20Sopenharmony_ci	connector->interlace_allowed = false;
6868c2ecf20Sopenharmony_ci	connector->doublescan_allowed = false;
6878c2ecf20Sopenharmony_ci
6888c2ecf20Sopenharmony_ci	/*Attach connector properties*/
6898c2ecf20Sopenharmony_ci	drm_object_attach_property(&connector->base,
6908c2ecf20Sopenharmony_ci				      dev->mode_config.scaling_mode_property,
6918c2ecf20Sopenharmony_ci				      DRM_MODE_SCALE_FULLSCREEN);
6928c2ecf20Sopenharmony_ci	drm_object_attach_property(&connector->base,
6938c2ecf20Sopenharmony_ci				      dev_priv->backlight_property,
6948c2ecf20Sopenharmony_ci				      BRIGHTNESS_MAX_LEVEL);
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	/*
6978c2ecf20Sopenharmony_ci	 * Set up I2C bus
6988c2ecf20Sopenharmony_ci	 * FIXME: distroy i2c_bus when exit
6998c2ecf20Sopenharmony_ci	 */
7008c2ecf20Sopenharmony_ci	lvds_priv->i2c_bus = psb_intel_i2c_create(dev, GPIOB, "LVDSBLC_B");
7018c2ecf20Sopenharmony_ci	if (!lvds_priv->i2c_bus) {
7028c2ecf20Sopenharmony_ci		dev_printk(KERN_ERR,
7038c2ecf20Sopenharmony_ci			&dev->pdev->dev, "I2C bus registration failed.\n");
7048c2ecf20Sopenharmony_ci		goto failed_blc_i2c;
7058c2ecf20Sopenharmony_ci	}
7068c2ecf20Sopenharmony_ci	lvds_priv->i2c_bus->slave_addr = 0x2C;
7078c2ecf20Sopenharmony_ci	dev_priv->lvds_i2c_bus =  lvds_priv->i2c_bus;
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	/*
7108c2ecf20Sopenharmony_ci	 * LVDS discovery:
7118c2ecf20Sopenharmony_ci	 * 1) check for EDID on DDC
7128c2ecf20Sopenharmony_ci	 * 2) check for VBT data
7138c2ecf20Sopenharmony_ci	 * 3) check to see if LVDS is already on
7148c2ecf20Sopenharmony_ci	 *    if none of the above, no panel
7158c2ecf20Sopenharmony_ci	 * 4) make sure lid is open
7168c2ecf20Sopenharmony_ci	 *    if closed, act like it's not there for now
7178c2ecf20Sopenharmony_ci	 */
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_ci	/* Set up the DDC bus. */
7208c2ecf20Sopenharmony_ci	lvds_priv->ddc_bus = psb_intel_i2c_create(dev, GPIOC, "LVDSDDC_C");
7218c2ecf20Sopenharmony_ci	if (!lvds_priv->ddc_bus) {
7228c2ecf20Sopenharmony_ci		dev_printk(KERN_ERR, &dev->pdev->dev,
7238c2ecf20Sopenharmony_ci			   "DDC bus registration " "failed.\n");
7248c2ecf20Sopenharmony_ci		goto failed_ddc;
7258c2ecf20Sopenharmony_ci	}
7268c2ecf20Sopenharmony_ci
7278c2ecf20Sopenharmony_ci	/*
7288c2ecf20Sopenharmony_ci	 * Attempt to get the fixed panel mode from DDC.  Assume that the
7298c2ecf20Sopenharmony_ci	 * preferred mode is the right one.
7308c2ecf20Sopenharmony_ci	 */
7318c2ecf20Sopenharmony_ci	mutex_lock(&dev->mode_config.mutex);
7328c2ecf20Sopenharmony_ci	psb_intel_ddc_get_modes(connector, &lvds_priv->ddc_bus->adapter);
7338c2ecf20Sopenharmony_ci	list_for_each_entry(scan, &connector->probed_modes, head) {
7348c2ecf20Sopenharmony_ci		if (scan->type & DRM_MODE_TYPE_PREFERRED) {
7358c2ecf20Sopenharmony_ci			mode_dev->panel_fixed_mode =
7368c2ecf20Sopenharmony_ci			    drm_mode_duplicate(dev, scan);
7378c2ecf20Sopenharmony_ci			DRM_DEBUG_KMS("Using mode from DDC\n");
7388c2ecf20Sopenharmony_ci			goto out;	/* FIXME: check for quirks */
7398c2ecf20Sopenharmony_ci		}
7408c2ecf20Sopenharmony_ci	}
7418c2ecf20Sopenharmony_ci
7428c2ecf20Sopenharmony_ci	/* Failed to get EDID, what about VBT? do we need this? */
7438c2ecf20Sopenharmony_ci	if (dev_priv->lfp_lvds_vbt_mode) {
7448c2ecf20Sopenharmony_ci		mode_dev->panel_fixed_mode =
7458c2ecf20Sopenharmony_ci			drm_mode_duplicate(dev, dev_priv->lfp_lvds_vbt_mode);
7468c2ecf20Sopenharmony_ci
7478c2ecf20Sopenharmony_ci		if (mode_dev->panel_fixed_mode) {
7488c2ecf20Sopenharmony_ci			mode_dev->panel_fixed_mode->type |=
7498c2ecf20Sopenharmony_ci				DRM_MODE_TYPE_PREFERRED;
7508c2ecf20Sopenharmony_ci			DRM_DEBUG_KMS("Using mode from VBT\n");
7518c2ecf20Sopenharmony_ci			goto out;
7528c2ecf20Sopenharmony_ci		}
7538c2ecf20Sopenharmony_ci	}
7548c2ecf20Sopenharmony_ci
7558c2ecf20Sopenharmony_ci	/*
7568c2ecf20Sopenharmony_ci	 * If we didn't get EDID, try checking if the panel is already turned
7578c2ecf20Sopenharmony_ci	 * on.	If so, assume that whatever is currently programmed is the
7588c2ecf20Sopenharmony_ci	 * correct mode.
7598c2ecf20Sopenharmony_ci	 */
7608c2ecf20Sopenharmony_ci	lvds = REG_READ(LVDS);
7618c2ecf20Sopenharmony_ci	pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0;
7628c2ecf20Sopenharmony_ci	crtc = psb_intel_get_crtc_from_pipe(dev, pipe);
7638c2ecf20Sopenharmony_ci
7648c2ecf20Sopenharmony_ci	if (crtc && (lvds & LVDS_PORT_EN)) {
7658c2ecf20Sopenharmony_ci		mode_dev->panel_fixed_mode =
7668c2ecf20Sopenharmony_ci		    psb_intel_crtc_mode_get(dev, crtc);
7678c2ecf20Sopenharmony_ci		if (mode_dev->panel_fixed_mode) {
7688c2ecf20Sopenharmony_ci			mode_dev->panel_fixed_mode->type |=
7698c2ecf20Sopenharmony_ci			    DRM_MODE_TYPE_PREFERRED;
7708c2ecf20Sopenharmony_ci			DRM_DEBUG_KMS("Using pre-programmed mode\n");
7718c2ecf20Sopenharmony_ci			goto out;	/* FIXME: check for quirks */
7728c2ecf20Sopenharmony_ci		}
7738c2ecf20Sopenharmony_ci	}
7748c2ecf20Sopenharmony_ci
7758c2ecf20Sopenharmony_ci	/* If we still don't have a mode after all that, give up. */
7768c2ecf20Sopenharmony_ci	if (!mode_dev->panel_fixed_mode) {
7778c2ecf20Sopenharmony_ci		dev_err(dev->dev, "Found no modes on the lvds, ignoring the LVDS\n");
7788c2ecf20Sopenharmony_ci		goto failed_find;
7798c2ecf20Sopenharmony_ci	}
7808c2ecf20Sopenharmony_ci
7818c2ecf20Sopenharmony_ci	/*
7828c2ecf20Sopenharmony_ci	 * Blacklist machines with BIOSes that list an LVDS panel without
7838c2ecf20Sopenharmony_ci	 * actually having one.
7848c2ecf20Sopenharmony_ci	 */
7858c2ecf20Sopenharmony_ciout:
7868c2ecf20Sopenharmony_ci	mutex_unlock(&dev->mode_config.mutex);
7878c2ecf20Sopenharmony_ci	drm_connector_register(connector);
7888c2ecf20Sopenharmony_ci	return;
7898c2ecf20Sopenharmony_ci
7908c2ecf20Sopenharmony_cifailed_find:
7918c2ecf20Sopenharmony_ci	mutex_unlock(&dev->mode_config.mutex);
7928c2ecf20Sopenharmony_ci	psb_intel_i2c_destroy(lvds_priv->ddc_bus);
7938c2ecf20Sopenharmony_cifailed_ddc:
7948c2ecf20Sopenharmony_ci	psb_intel_i2c_destroy(lvds_priv->i2c_bus);
7958c2ecf20Sopenharmony_cifailed_blc_i2c:
7968c2ecf20Sopenharmony_ci	drm_encoder_cleanup(encoder);
7978c2ecf20Sopenharmony_ci	drm_connector_cleanup(connector);
7988c2ecf20Sopenharmony_cifailed_connector:
7998c2ecf20Sopenharmony_ci	kfree(gma_connector);
8008c2ecf20Sopenharmony_cifailed_encoder:
8018c2ecf20Sopenharmony_ci	kfree(gma_encoder);
8028c2ecf20Sopenharmony_ci}
8038c2ecf20Sopenharmony_ci
804