summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/gma500/psb_intel_sdvo.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/gma500/psb_intel_sdvo.c')
-rw-r--r--drivers/gpu/drm/gma500/psb_intel_sdvo.c1293
1 files changed, 1293 insertions, 0 deletions
diff --git a/drivers/gpu/drm/gma500/psb_intel_sdvo.c b/drivers/gpu/drm/gma500/psb_intel_sdvo.c
new file mode 100644
index 000000000000..a4bad1af4b7c
--- /dev/null
+++ b/drivers/gpu/drm/gma500/psb_intel_sdvo.c
@@ -0,0 +1,1293 @@
+/*
+ * Copyright (c) 2006-2007 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/delay.h>
+/* #include <drm/drm_crtc.h> */
+#include <drm/drmP.h>
+#include "psb_drv.h"
+#include "psb_intel_drv.h"
+#include "psb_intel_reg.h"
+#include "psb_intel_sdvo_regs.h"
+
+struct psb_intel_sdvo_priv {
+ struct psb_intel_i2c_chan *i2c_bus;
+ int slaveaddr;
+ int output_device;
+
+ u16 active_outputs;
+
+ struct psb_intel_sdvo_caps caps;
+ int pixel_clock_min, pixel_clock_max;
+
+ int save_sdvo_mult;
+ u16 save_active_outputs;
+ struct psb_intel_sdvo_dtd save_input_dtd_1, save_input_dtd_2;
+ struct psb_intel_sdvo_dtd save_output_dtd[16];
+ u32 save_SDVOX;
+ u8 in_out_map[4];
+
+ u8 by_input_wiring;
+ u32 active_device;
+};
+
+/**
+ * Writes the SDVOB or SDVOC with the given value, but always writes both
+ * SDVOB and SDVOC to work around apparent hardware issues (according to
+ * comments in the BIOS).
+ */
+void psb_intel_sdvo_write_sdvox(struct psb_intel_output *psb_intel_output,
+ u32 val)
+{
+ struct drm_device *dev = psb_intel_output->base.dev;
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ u32 bval = val, cval = val;
+ int i;
+
+ if (sdvo_priv->output_device == SDVOB)
+ cval = REG_READ(SDVOC);
+ else
+ bval = REG_READ(SDVOB);
+ /*
+ * Write the registers twice for luck. Sometimes,
+ * writing them only once doesn't appear to 'stick'.
+ * The BIOS does this too. Yay, magic
+ */
+ for (i = 0; i < 2; i++) {
+ REG_WRITE(SDVOB, bval);
+ REG_READ(SDVOB);
+ REG_WRITE(SDVOC, cval);
+ REG_READ(SDVOC);
+ }
+}
+
+static bool psb_intel_sdvo_read_byte(
+ struct psb_intel_output *psb_intel_output,
+ u8 addr, u8 *ch)
+{
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ u8 out_buf[2];
+ u8 buf[2];
+ int ret;
+
+ struct i2c_msg msgs[] = {
+ {
+ .addr = sdvo_priv->i2c_bus->slave_addr,
+ .flags = 0,
+ .len = 1,
+ .buf = out_buf,
+ },
+ {
+ .addr = sdvo_priv->i2c_bus->slave_addr,
+ .flags = I2C_M_RD,
+ .len = 1,
+ .buf = buf,
+ }
+ };
+
+ out_buf[0] = addr;
+ out_buf[1] = 0;
+
+ ret = i2c_transfer(&sdvo_priv->i2c_bus->adapter, msgs, 2);
+ if (ret == 2) {
+ *ch = buf[0];
+ return true;
+ }
+
+ return false;
+}
+
+static bool psb_intel_sdvo_write_byte(
+ struct psb_intel_output *psb_intel_output,
+ int addr, u8 ch)
+{
+ u8 out_buf[2];
+ struct i2c_msg msgs[] = {
+ {
+ .addr = psb_intel_output->i2c_bus->slave_addr,
+ .flags = 0,
+ .len = 2,
+ .buf = out_buf,
+ }
+ };
+
+ out_buf[0] = addr;
+ out_buf[1] = ch;
+
+ if (i2c_transfer(&psb_intel_output->i2c_bus->adapter, msgs, 1) == 1)
+ return true;
+ return false;
+}
+
+#define SDVO_CMD_NAME_ENTRY(cmd) {cmd, #cmd}
+/** Mapping of command numbers to names, for debug output */
+static const struct _sdvo_cmd_name {
+ u8 cmd;
+ char *name;
+} sdvo_cmd_names[] = {
+SDVO_CMD_NAME_ENTRY(SDVO_CMD_RESET),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_DEVICE_CAPS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_FIRMWARE_REV),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TRAINED_INPUTS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ACTIVE_OUTPUTS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ACTIVE_OUTPUTS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_IN_OUT_MAP),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_IN_OUT_MAP),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ATTACHED_DISPLAYS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_HOT_PLUG_SUPPORT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_ACTIVE_HOT_PLUG),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_ACTIVE_HOT_PLUG),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_GET_INTERRUPT_EVENT_SOURCE),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TARGET_INPUT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TARGET_OUTPUT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_INPUT_TIMINGS_PART1),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_INPUT_TIMINGS_PART2),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_INPUT_TIMINGS_PART1),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_INPUT_TIMINGS_PART2),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_INPUT_TIMINGS_PART1),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_OUTPUT_TIMINGS_PART1),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_OUTPUT_TIMINGS_PART2),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OUTPUT_TIMINGS_PART1),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_OUTPUT_TIMINGS_PART2),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_CREATE_PREFERRED_INPUT_TIMING),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART1),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_GET_PREFERRED_INPUT_TIMING_PART2),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_GET_INPUT_PIXEL_CLOCK_RANGE),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_GET_OUTPUT_PIXEL_CLOCK_RANGE),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_GET_SUPPORTED_CLOCK_RATE_MULTS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_CLOCK_RATE_MULT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_CLOCK_RATE_MULT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_SUPPORTED_TV_FORMATS),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_GET_TV_FORMAT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_TV_FORMAT),
+ SDVO_CMD_NAME_ENTRY
+ (SDVO_CMD_SET_TV_RESOLUTION_SUPPORT),
+ SDVO_CMD_NAME_ENTRY(SDVO_CMD_SET_CONTROL_BUS_SWITCH),};
+
+#define SDVO_NAME(dev_priv) \
+ ((dev_priv)->output_device == SDVOB ? "SDVOB" : "SDVOC")
+#define SDVO_PRIV(output) ((struct psb_intel_sdvo_priv *) (output)->dev_priv)
+
+static void psb_intel_sdvo_write_cmd(struct psb_intel_output *psb_intel_output,
+ u8 cmd,
+ void *args,
+ int args_len)
+{
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ int i;
+
+ if (0) {
+ printk(KERN_DEBUG "%s: W: %02X ", SDVO_NAME(sdvo_priv), cmd);
+ for (i = 0; i < args_len; i++)
+ printk(KERN_CONT "%02X ", ((u8 *) args)[i]);
+ for (; i < 8; i++)
+ printk(KERN_CONT " ");
+ for (i = 0;
+ i <
+ sizeof(sdvo_cmd_names) / sizeof(sdvo_cmd_names[0]);
+ i++) {
+ if (cmd == sdvo_cmd_names[i].cmd) {
+ printk(KERN_CONT
+ "(%s)", sdvo_cmd_names[i].name);
+ break;
+ }
+ }
+ if (i ==
+ sizeof(sdvo_cmd_names) / sizeof(sdvo_cmd_names[0]))
+ printk(KERN_CONT "(%02X)", cmd);
+ printk(KERN_CONT "\n");
+ }
+
+ for (i = 0; i < args_len; i++) {
+ psb_intel_sdvo_write_byte(psb_intel_output,
+ SDVO_I2C_ARG_0 - i,
+ ((u8 *) args)[i]);
+ }
+
+ psb_intel_sdvo_write_byte(psb_intel_output, SDVO_I2C_OPCODE, cmd);
+}
+
+static const char *const cmd_status_names[] = {
+ "Power on",
+ "Success",
+ "Not supported",
+ "Invalid arg",
+ "Pending",
+ "Target not specified",
+ "Scaling not supported"
+};
+
+static u8 psb_intel_sdvo_read_response(
+ struct psb_intel_output *psb_intel_output,
+ void *response, int response_len)
+{
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ int i;
+ u8 status;
+ u8 retry = 50;
+
+ while (retry--) {
+ /* Read the command response */
+ for (i = 0; i < response_len; i++) {
+ psb_intel_sdvo_read_byte(psb_intel_output,
+ SDVO_I2C_RETURN_0 + i,
+ &((u8 *) response)[i]);
+ }
+
+ /* read the return status */
+ psb_intel_sdvo_read_byte(psb_intel_output,
+ SDVO_I2C_CMD_STATUS,
+ &status);
+
+ if (0) {
+ pr_debug("%s: R: ", SDVO_NAME(sdvo_priv));
+ for (i = 0; i < response_len; i++)
+ printk(KERN_CONT "%02X ", ((u8 *) response)[i]);
+ for (; i < 8; i++)
+ printk(" ");
+ if (status <= SDVO_CMD_STATUS_SCALING_NOT_SUPP)
+ printk(KERN_CONT "(%s)",
+ cmd_status_names[status]);
+ else
+ printk(KERN_CONT "(??? %d)", status);
+ printk(KERN_CONT "\n");
+ }
+
+ if (status != SDVO_CMD_STATUS_PENDING)
+ return status;
+
+ mdelay(50);
+ }
+
+ return status;
+}
+
+int psb_intel_sdvo_get_pixel_multiplier(struct drm_display_mode *mode)
+{
+ if (mode->clock >= 100000)
+ return 1;
+ else if (mode->clock >= 50000)
+ return 2;
+ else
+ return 4;
+}
+
+/**
+ * Don't check status code from this as it switches the bus back to the
+ * SDVO chips which defeats the purpose of doing a bus switch in the first
+ * place.
+ */
+void psb_intel_sdvo_set_control_bus_switch(
+ struct psb_intel_output *psb_intel_output,
+ u8 target)
+{
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_SET_CONTROL_BUS_SWITCH,
+ &target,
+ 1);
+}
+
+static bool psb_intel_sdvo_set_target_input(
+ struct psb_intel_output *psb_intel_output,
+ bool target_0, bool target_1)
+{
+ struct psb_intel_sdvo_set_target_input_args targets = { 0 };
+ u8 status;
+
+ if (target_0 && target_1)
+ return SDVO_CMD_STATUS_NOTSUPP;
+
+ if (target_1)
+ targets.target_1 = 1;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, SDVO_CMD_SET_TARGET_INPUT,
+ &targets, sizeof(targets));
+
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+
+ return status == SDVO_CMD_STATUS_SUCCESS;
+}
+
+/**
+ * Return whether each input is trained.
+ *
+ * This function is making an assumption about the layout of the response,
+ * which should be checked against the docs.
+ */
+static bool psb_intel_sdvo_get_trained_inputs(struct psb_intel_output
+ *psb_intel_output, bool *input_1,
+ bool *input_2)
+{
+ struct psb_intel_sdvo_get_trained_inputs_response response;
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, SDVO_CMD_GET_TRAINED_INPUTS,
+ NULL, 0);
+ status =
+ psb_intel_sdvo_read_response(psb_intel_output, &response,
+ sizeof(response));
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ *input_1 = response.input0_trained;
+ *input_2 = response.input1_trained;
+ return true;
+}
+
+static bool psb_intel_sdvo_get_active_outputs(struct psb_intel_output
+ *psb_intel_output, u16 *outputs)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, SDVO_CMD_GET_ACTIVE_OUTPUTS,
+ NULL, 0);
+ status =
+ psb_intel_sdvo_read_response(psb_intel_output, outputs,
+ sizeof(*outputs));
+
+ return status == SDVO_CMD_STATUS_SUCCESS;
+}
+
+static bool psb_intel_sdvo_set_active_outputs(struct psb_intel_output
+ *psb_intel_output, u16 outputs)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, SDVO_CMD_SET_ACTIVE_OUTPUTS,
+ &outputs, sizeof(outputs));
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+ return status == SDVO_CMD_STATUS_SUCCESS;
+}
+
+static bool psb_intel_sdvo_set_encoder_power_state(struct psb_intel_output
+ *psb_intel_output, int mode)
+{
+ u8 status, state = SDVO_ENCODER_STATE_ON;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ state = SDVO_ENCODER_STATE_ON;
+ break;
+ case DRM_MODE_DPMS_STANDBY:
+ state = SDVO_ENCODER_STATE_STANDBY;
+ break;
+ case DRM_MODE_DPMS_SUSPEND:
+ state = SDVO_ENCODER_STATE_SUSPEND;
+ break;
+ case DRM_MODE_DPMS_OFF:
+ state = SDVO_ENCODER_STATE_OFF;
+ break;
+ }
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_SET_ENCODER_POWER_STATE, &state,
+ sizeof(state));
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+
+ return status == SDVO_CMD_STATUS_SUCCESS;
+}
+
+static bool psb_intel_sdvo_get_input_pixel_clock_range(struct psb_intel_output
+ *psb_intel_output,
+ int *clock_min,
+ int *clock_max)
+{
+ struct psb_intel_sdvo_pixel_clock_range clocks;
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_INPUT_PIXEL_CLOCK_RANGE, NULL,
+ 0);
+
+ status =
+ psb_intel_sdvo_read_response(psb_intel_output, &clocks,
+ sizeof(clocks));
+
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ /* Convert the values from units of 10 kHz to kHz. */
+ *clock_min = clocks.min * 10;
+ *clock_max = clocks.max * 10;
+
+ return true;
+}
+
+static bool psb_intel_sdvo_set_target_output(
+ struct psb_intel_output *psb_intel_output,
+ u16 outputs)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, SDVO_CMD_SET_TARGET_OUTPUT,
+ &outputs, sizeof(outputs));
+
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+ return status == SDVO_CMD_STATUS_SUCCESS;
+}
+
+static bool psb_intel_sdvo_get_timing(struct psb_intel_output *psb_intel_output,
+ u8 cmd, struct psb_intel_sdvo_dtd *dtd)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, cmd, NULL, 0);
+ status = psb_intel_sdvo_read_response(psb_intel_output, &dtd->part1,
+ sizeof(dtd->part1));
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, cmd + 1, NULL, 0);
+ status = psb_intel_sdvo_read_response(psb_intel_output, &dtd->part2,
+ sizeof(dtd->part2));
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ return true;
+}
+
+static bool psb_intel_sdvo_get_input_timing(
+ struct psb_intel_output *psb_intel_output,
+ struct psb_intel_sdvo_dtd *dtd)
+{
+ return psb_intel_sdvo_get_timing(psb_intel_output,
+ SDVO_CMD_GET_INPUT_TIMINGS_PART1,
+ dtd);
+}
+
+static bool psb_intel_sdvo_set_timing(
+ struct psb_intel_output *psb_intel_output,
+ u8 cmd,
+ struct psb_intel_sdvo_dtd *dtd)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, cmd, &dtd->part1,
+ sizeof(dtd->part1));
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output, cmd + 1, &dtd->part2,
+ sizeof(dtd->part2));
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ return true;
+}
+
+static bool psb_intel_sdvo_set_input_timing(
+ struct psb_intel_output *psb_intel_output,
+ struct psb_intel_sdvo_dtd *dtd)
+{
+ return psb_intel_sdvo_set_timing(psb_intel_output,
+ SDVO_CMD_SET_INPUT_TIMINGS_PART1,
+ dtd);
+}
+
+static bool psb_intel_sdvo_set_output_timing(
+ struct psb_intel_output *psb_intel_output,
+ struct psb_intel_sdvo_dtd *dtd)
+{
+ return psb_intel_sdvo_set_timing(psb_intel_output,
+ SDVO_CMD_SET_OUTPUT_TIMINGS_PART1,
+ dtd);
+}
+
+static int psb_intel_sdvo_get_clock_rate_mult(struct psb_intel_output
+ *psb_intel_output)
+{
+ u8 response, status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_CLOCK_RATE_MULT,
+ NULL,
+ 0);
+
+ status = psb_intel_sdvo_read_response(psb_intel_output, &response, 1);
+
+ if (status != SDVO_CMD_STATUS_SUCCESS) {
+ DRM_DEBUG("Couldn't get SDVO clock rate multiplier\n");
+ return SDVO_CLOCK_RATE_MULT_1X;
+ } else {
+ DRM_DEBUG("Current clock rate multiplier: %d\n", response);
+ }
+
+ return response;
+}
+
+static bool psb_intel_sdvo_set_clock_rate_mult(struct psb_intel_output
+ *psb_intel_output, u8 val)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_SET_CLOCK_RATE_MULT,
+ &val,
+ 1);
+
+ status = psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ return true;
+}
+
+static bool psb_sdvo_set_current_inoutmap(struct psb_intel_output *output,
+ u32 in0outputmask,
+ u32 in1outputmask)
+{
+ u8 byArgs[4];
+ u8 status;
+ int i;
+ struct psb_intel_sdvo_priv *sdvo_priv = output->dev_priv;
+
+ /* Make all fields of the args/ret to zero */
+ memset(byArgs, 0, sizeof(byArgs));
+
+ /* Fill up the argument values; */
+ byArgs[0] = (u8) (in0outputmask & 0xFF);
+ byArgs[1] = (u8) ((in0outputmask >> 8) & 0xFF);
+ byArgs[2] = (u8) (in1outputmask & 0xFF);
+ byArgs[3] = (u8) ((in1outputmask >> 8) & 0xFF);
+
+
+ /*save inoutmap arg here*/
+ for (i = 0; i < 4; i++)
+ sdvo_priv->in_out_map[i] = byArgs[0];
+
+ psb_intel_sdvo_write_cmd(output, SDVO_CMD_SET_IN_OUT_MAP, byArgs, 4);
+ status = psb_intel_sdvo_read_response(output, NULL, 0);
+
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+ return true;
+}
+
+
+static void psb_intel_sdvo_set_iomap(struct psb_intel_output *output)
+{
+ u32 dwCurrentSDVOIn0 = 0;
+ u32 dwCurrentSDVOIn1 = 0;
+ u32 dwDevMask = 0;
+
+
+ struct psb_intel_sdvo_priv *sdvo_priv = output->dev_priv;
+
+ /* Please DO NOT change the following code. */
+ /* SDVOB_IN0 or SDVOB_IN1 ==> sdvo_in0 */
+ /* SDVOC_IN0 or SDVOC_IN1 ==> sdvo_in1 */
+ if (sdvo_priv->by_input_wiring & (SDVOB_IN0 | SDVOC_IN0)) {
+ switch (sdvo_priv->active_device) {
+ case SDVO_DEVICE_LVDS:
+ dwDevMask = SDVO_OUTPUT_LVDS0 | SDVO_OUTPUT_LVDS1;
+ break;
+ case SDVO_DEVICE_TMDS:
+ dwDevMask = SDVO_OUTPUT_TMDS0 | SDVO_OUTPUT_TMDS1;
+ break;
+ case SDVO_DEVICE_TV:
+ dwDevMask =
+ SDVO_OUTPUT_YPRPB0 | SDVO_OUTPUT_SVID0 |
+ SDVO_OUTPUT_CVBS0 | SDVO_OUTPUT_YPRPB1 |
+ SDVO_OUTPUT_SVID1 | SDVO_OUTPUT_CVBS1 |
+ SDVO_OUTPUT_SCART0 | SDVO_OUTPUT_SCART1;
+ break;
+ case SDVO_DEVICE_CRT:
+ dwDevMask = SDVO_OUTPUT_RGB0 | SDVO_OUTPUT_RGB1;
+ break;
+ }
+ dwCurrentSDVOIn0 = (sdvo_priv->active_outputs & dwDevMask);
+ } else if (sdvo_priv->by_input_wiring & (SDVOB_IN1 | SDVOC_IN1)) {
+ switch (sdvo_priv->active_device) {
+ case SDVO_DEVICE_LVDS:
+ dwDevMask = SDVO_OUTPUT_LVDS0 | SDVO_OUTPUT_LVDS1;
+ break;
+ case SDVO_DEVICE_TMDS:
+ dwDevMask = SDVO_OUTPUT_TMDS0 | SDVO_OUTPUT_TMDS1;
+ break;
+ case SDVO_DEVICE_TV:
+ dwDevMask =
+ SDVO_OUTPUT_YPRPB0 | SDVO_OUTPUT_SVID0 |
+ SDVO_OUTPUT_CVBS0 | SDVO_OUTPUT_YPRPB1 |
+ SDVO_OUTPUT_SVID1 | SDVO_OUTPUT_CVBS1 |
+ SDVO_OUTPUT_SCART0 | SDVO_OUTPUT_SCART1;
+ break;
+ case SDVO_DEVICE_CRT:
+ dwDevMask = SDVO_OUTPUT_RGB0 | SDVO_OUTPUT_RGB1;
+ break;
+ }
+ dwCurrentSDVOIn1 = (sdvo_priv->active_outputs & dwDevMask);
+ }
+
+ psb_sdvo_set_current_inoutmap(output, dwCurrentSDVOIn0,
+ dwCurrentSDVOIn1);
+}
+
+
+static bool psb_intel_sdvo_mode_fixup(struct drm_encoder *encoder,
+ struct drm_display_mode *mode,
+ struct drm_display_mode *adjusted_mode)
+{
+ /* Make the CRTC code factor in the SDVO pixel multiplier. The SDVO
+ * device will be told of the multiplier during mode_set.
+ */
+ adjusted_mode->clock *= psb_intel_sdvo_get_pixel_multiplier(mode);
+ return true;
+}
+
+static void psb_intel_sdvo_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);
+ struct psb_intel_output *psb_intel_output =
+ enc_to_psb_intel_output(encoder);
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ u16 width, height;
+ u16 h_blank_len, h_sync_len, v_blank_len, v_sync_len;
+ u16 h_sync_offset, v_sync_offset;
+ u32 sdvox;
+ struct psb_intel_sdvo_dtd output_dtd;
+ int sdvo_pixel_multiply;
+
+ if (!mode)
+ return;
+
+ psb_intel_sdvo_set_target_output(psb_intel_output, 0);
+
+ width = mode->crtc_hdisplay;
+ height = mode->crtc_vdisplay;
+
+ /* do some mode translations */
+ h_blank_len = mode->crtc_hblank_end - mode->crtc_hblank_start;
+ h_sync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
+
+ v_blank_len = mode->crtc_vblank_end - mode->crtc_vblank_start;
+ v_sync_len = mode->crtc_vsync_end - mode->crtc_vsync_start;
+
+ h_sync_offset = mode->crtc_hsync_start - mode->crtc_hblank_start;
+ v_sync_offset = mode->crtc_vsync_start - mode->crtc_vblank_start;
+
+ output_dtd.part1.clock = mode->clock / 10;
+ output_dtd.part1.h_active = width & 0xff;
+ output_dtd.part1.h_blank = h_blank_len & 0xff;
+ output_dtd.part1.h_high = (((width >> 8) & 0xf) << 4) |
+ ((h_blank_len >> 8) & 0xf);
+ output_dtd.part1.v_active = height & 0xff;
+ output_dtd.part1.v_blank = v_blank_len & 0xff;
+ output_dtd.part1.v_high = (((height >> 8) & 0xf) << 4) |
+ ((v_blank_len >> 8) & 0xf);
+
+ output_dtd.part2.h_sync_off = h_sync_offset;
+ output_dtd.part2.h_sync_width = h_sync_len & 0xff;
+ output_dtd.part2.v_sync_off_width = (v_sync_offset & 0xf) << 4 |
+ (v_sync_len & 0xf);
+ output_dtd.part2.sync_off_width_high =
+ ((h_sync_offset & 0x300) >> 2) | ((h_sync_len & 0x300) >> 4) |
+ ((v_sync_offset & 0x30) >> 2) | ((v_sync_len & 0x30) >> 4);
+
+ output_dtd.part2.dtd_flags = 0x18;
+ if (mode->flags & DRM_MODE_FLAG_PHSYNC)
+ output_dtd.part2.dtd_flags |= 0x2;
+ if (mode->flags & DRM_MODE_FLAG_PVSYNC)
+ output_dtd.part2.dtd_flags |= 0x4;
+
+ output_dtd.part2.sdvo_flags = 0;
+ output_dtd.part2.v_sync_off_high = v_sync_offset & 0xc0;
+ output_dtd.part2.reserved = 0;
+
+ /* Set the output timing to the screen */
+ psb_intel_sdvo_set_target_output(psb_intel_output,
+ sdvo_priv->active_outputs);
+
+ /* Set the input timing to the screen. Assume always input 0. */
+ psb_intel_sdvo_set_target_input(psb_intel_output, true, false);
+
+ psb_intel_sdvo_set_output_timing(psb_intel_output, &output_dtd);
+
+ /* We would like to use i830_sdvo_create_preferred_input_timing() to
+ * provide the device with a timing it can support, if it supports that
+ * feature. However, presumably we would need to adjust the CRTC to
+ * output the preferred timing, and we don't support that currently.
+ */
+ psb_intel_sdvo_set_input_timing(psb_intel_output, &output_dtd);
+
+ switch (psb_intel_sdvo_get_pixel_multiplier(mode)) {
+ case 1:
+ psb_intel_sdvo_set_clock_rate_mult(psb_intel_output,
+ SDVO_CLOCK_RATE_MULT_1X);
+ break;
+ case 2:
+ psb_intel_sdvo_set_clock_rate_mult(psb_intel_output,
+ SDVO_CLOCK_RATE_MULT_2X);
+ break;
+ case 4:
+ psb_intel_sdvo_set_clock_rate_mult(psb_intel_output,
+ SDVO_CLOCK_RATE_MULT_4X);
+ break;
+ }
+
+ /* Set the SDVO control regs. */
+ sdvox = REG_READ(sdvo_priv->output_device);
+ switch (sdvo_priv->output_device) {
+ case SDVOB:
+ sdvox &= SDVOB_PRESERVE_MASK;
+ break;
+ case SDVOC:
+ sdvox &= SDVOC_PRESERVE_MASK;
+ break;
+ }
+ sdvox |= (9 << 19) | SDVO_BORDER_ENABLE;
+ if (psb_intel_crtc->pipe == 1)
+ sdvox |= SDVO_PIPE_B_SELECT;
+
+ sdvo_pixel_multiply = psb_intel_sdvo_get_pixel_multiplier(mode);
+
+ psb_intel_sdvo_write_sdvox(psb_intel_output, sdvox);
+
+ psb_intel_sdvo_set_iomap(psb_intel_output);
+}
+
+static void psb_intel_sdvo_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct drm_device *dev = encoder->dev;
+ struct psb_intel_output *psb_intel_output =
+ enc_to_psb_intel_output(encoder);
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ u32 temp;
+
+ if (mode != DRM_MODE_DPMS_ON) {
+ psb_intel_sdvo_set_active_outputs(psb_intel_output, 0);
+ if (0)
+ psb_intel_sdvo_set_encoder_power_state(
+ psb_intel_output,
+ mode);
+
+ if (mode == DRM_MODE_DPMS_OFF) {
+ temp = REG_READ(sdvo_priv->output_device);
+ if ((temp & SDVO_ENABLE) != 0) {
+ psb_intel_sdvo_write_sdvox(psb_intel_output,
+ temp &
+ ~SDVO_ENABLE);
+ }
+ }
+ } else {
+ bool input1, input2;
+ int i;
+ u8 status;
+
+ temp = REG_READ(sdvo_priv->output_device);
+ if ((temp & SDVO_ENABLE) == 0)
+ psb_intel_sdvo_write_sdvox(psb_intel_output,
+ temp | SDVO_ENABLE);
+ for (i = 0; i < 2; i++)
+ psb_intel_wait_for_vblank(dev);
+
+ status =
+ psb_intel_sdvo_get_trained_inputs(psb_intel_output,
+ &input1,
+ &input2);
+
+
+ /* Warn if the device reported failure to sync.
+ * A lot of SDVO devices fail to notify of sync, but it's
+ * a given it the status is a success, we succeeded.
+ */
+ if (status == SDVO_CMD_STATUS_SUCCESS && !input1) {
+ DRM_DEBUG
+ ("First %s output reported failure to sync\n",
+ SDVO_NAME(sdvo_priv));
+ }
+
+ if (0)
+ psb_intel_sdvo_set_encoder_power_state(
+ psb_intel_output,
+ mode);
+ psb_intel_sdvo_set_active_outputs(psb_intel_output,
+ sdvo_priv->active_outputs);
+ }
+ return;
+}
+
+static void psb_intel_sdvo_save(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_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ /*int o;*/
+
+ sdvo_priv->save_sdvo_mult =
+ psb_intel_sdvo_get_clock_rate_mult(psb_intel_output);
+ psb_intel_sdvo_get_active_outputs(psb_intel_output,
+ &sdvo_priv->save_active_outputs);
+
+ if (sdvo_priv->caps.sdvo_inputs_mask & 0x1) {
+ psb_intel_sdvo_set_target_input(psb_intel_output,
+ true,
+ false);
+ psb_intel_sdvo_get_input_timing(psb_intel_output,
+ &sdvo_priv->save_input_dtd_1);
+ }
+
+ if (sdvo_priv->caps.sdvo_inputs_mask & 0x2) {
+ psb_intel_sdvo_set_target_input(psb_intel_output,
+ false,
+ true);
+ psb_intel_sdvo_get_input_timing(psb_intel_output,
+ &sdvo_priv->save_input_dtd_2);
+ }
+ sdvo_priv->save_SDVOX = REG_READ(sdvo_priv->output_device);
+
+ /*TODO: save the in_out_map state*/
+}
+
+static void psb_intel_sdvo_restore(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_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+ /*int o;*/
+ int i;
+ bool input1, input2;
+ u8 status;
+
+ psb_intel_sdvo_set_active_outputs(psb_intel_output, 0);
+
+ if (sdvo_priv->caps.sdvo_inputs_mask & 0x1) {
+ psb_intel_sdvo_set_target_input(psb_intel_output, true, false);
+ psb_intel_sdvo_set_input_timing(psb_intel_output,
+ &sdvo_priv->save_input_dtd_1);
+ }
+
+ if (sdvo_priv->caps.sdvo_inputs_mask & 0x2) {
+ psb_intel_sdvo_set_target_input(psb_intel_output, false, true);
+ psb_intel_sdvo_set_input_timing(psb_intel_output,
+ &sdvo_priv->save_input_dtd_2);
+ }
+
+ psb_intel_sdvo_set_clock_rate_mult(psb_intel_output,
+ sdvo_priv->save_sdvo_mult);
+
+ REG_WRITE(sdvo_priv->output_device, sdvo_priv->save_SDVOX);
+
+ if (sdvo_priv->save_SDVOX & SDVO_ENABLE) {
+ for (i = 0; i < 2; i++)
+ psb_intel_wait_for_vblank(dev);
+ status =
+ psb_intel_sdvo_get_trained_inputs(psb_intel_output,
+ &input1,
+ &input2);
+ if (status == SDVO_CMD_STATUS_SUCCESS && !input1)
+ DRM_DEBUG
+ ("First %s output reported failure to sync\n",
+ SDVO_NAME(sdvo_priv));
+ }
+
+ psb_intel_sdvo_set_active_outputs(psb_intel_output,
+ sdvo_priv->save_active_outputs);
+
+ /*TODO: restore in_out_map*/
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_SET_IN_OUT_MAP,
+ sdvo_priv->in_out_map,
+ 4);
+
+ psb_intel_sdvo_read_response(psb_intel_output, NULL, 0);
+}
+
+static int psb_intel_sdvo_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct psb_intel_output *psb_intel_output =
+ to_psb_intel_output(connector);
+ struct psb_intel_sdvo_priv *sdvo_priv = psb_intel_output->dev_priv;
+
+ if (mode->flags & DRM_MODE_FLAG_DBLSCAN)
+ return MODE_NO_DBLESCAN;
+
+ if (sdvo_priv->pixel_clock_min > mode->clock)
+ return MODE_CLOCK_LOW;
+
+ if (sdvo_priv->pixel_clock_max < mode->clock)
+ return MODE_CLOCK_HIGH;
+
+ return MODE_OK;
+}
+
+static bool psb_intel_sdvo_get_capabilities(
+ struct psb_intel_output *psb_intel_output,
+ struct psb_intel_sdvo_caps *caps)
+{
+ u8 status;
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_DEVICE_CAPS,
+ NULL,
+ 0);
+ status = psb_intel_sdvo_read_response(psb_intel_output,
+ caps,
+ sizeof(*caps));
+ if (status != SDVO_CMD_STATUS_SUCCESS)
+ return false;
+
+ return true;
+}
+
+struct drm_connector *psb_intel_sdvo_find(struct drm_device *dev, int sdvoB)
+{
+ struct drm_connector *connector = NULL;
+ struct psb_intel_output *iout = NULL;
+ struct psb_intel_sdvo_priv *sdvo;
+
+ /* find the sdvo connector */
+ list_for_each_entry(connector, &dev->mode_config.connector_list,
+ head) {
+ iout = to_psb_intel_output(connector);
+
+ if (iout->type != INTEL_OUTPUT_SDVO)
+ continue;
+
+ sdvo = iout->dev_priv;
+
+ if (sdvo->output_device == SDVOB && sdvoB)
+ return connector;
+
+ if (sdvo->output_device == SDVOC && !sdvoB)
+ return connector;
+
+ }
+
+ return NULL;
+}
+
+int psb_intel_sdvo_supports_hotplug(struct drm_connector *connector)
+{
+ u8 response[2];
+ u8 status;
+ struct psb_intel_output *psb_intel_output;
+
+ if (!connector)
+ return 0;
+
+ psb_intel_output = to_psb_intel_output(connector);
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_HOT_PLUG_SUPPORT,
+ NULL,
+ 0);
+ status = psb_intel_sdvo_read_response(psb_intel_output,
+ &response,
+ 2);
+
+ if (response[0] != 0)
+ return 1;
+
+ return 0;
+}
+
+void psb_intel_sdvo_set_hotplug(struct drm_connector *connector, int on)
+{
+ u8 response[2];
+ u8 status;
+ struct psb_intel_output *psb_intel_output =
+ to_psb_intel_output(connector);
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_ACTIVE_HOT_PLUG,
+ NULL,
+ 0);
+ psb_intel_sdvo_read_response(psb_intel_output, &response, 2);
+
+ if (on) {
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_HOT_PLUG_SUPPORT, NULL,
+ 0);
+ status = psb_intel_sdvo_read_response(psb_intel_output,
+ &response,
+ 2);
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_SET_ACTIVE_HOT_PLUG,
+ &response, 2);
+ } else {
+ response[0] = 0;
+ response[1] = 0;
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_SET_ACTIVE_HOT_PLUG,
+ &response, 2);
+ }
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_ACTIVE_HOT_PLUG,
+ NULL,
+ 0);
+ psb_intel_sdvo_read_response(psb_intel_output, &response, 2);
+}
+
+static enum drm_connector_status psb_intel_sdvo_detect(struct drm_connector
+ *connector, bool force)
+{
+ u8 response[2];
+ u8 status;
+ struct psb_intel_output *psb_intel_output =
+ to_psb_intel_output(connector);
+
+ psb_intel_sdvo_write_cmd(psb_intel_output,
+ SDVO_CMD_GET_ATTACHED_DISPLAYS,
+ NULL,
+ 0);
+ status = psb_intel_sdvo_read_response(psb_intel_output, &response, 2);
+
+ DRM_DEBUG("SDVO response %d %d\n", response[0], response[1]);
+ if ((response[0] != 0) || (response[1] != 0))
+ return connector_status_connected;
+ else
+ return connector_status_disconnected;
+}
+
+static int psb_intel_sdvo_get_modes(struct drm_connector *connector)
+{
+ struct psb_intel_output *psb_intel_output =
+ to_psb_intel_output(connector);
+
+ /* set the bus switch and get the modes */
+ psb_intel_sdvo_set_control_bus_switch(psb_intel_output,
+ SDVO_CONTROL_BUS_DDC2);
+ psb_intel_ddc_get_modes(psb_intel_output);
+
+ if (list_empty(&connector->probed_modes))
+ return 0;
+ return 1;
+}
+
+static void psb_intel_sdvo_destroy(struct drm_connector *connector)
+{
+ struct psb_intel_output *psb_intel_output =
+ to_psb_intel_output(connector);
+
+ if (psb_intel_output->i2c_bus)
+ psb_intel_i2c_destroy(psb_intel_output->i2c_bus);
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+ kfree(psb_intel_output);
+}
+
+static const struct drm_encoder_helper_funcs psb_intel_sdvo_helper_funcs = {
+ .dpms = psb_intel_sdvo_dpms,
+ .mode_fixup = psb_intel_sdvo_mode_fixup,
+ .prepare = psb_intel_encoder_prepare,
+ .mode_set = psb_intel_sdvo_mode_set,
+ .commit = psb_intel_encoder_commit,
+};
+
+static const struct drm_connector_funcs psb_intel_sdvo_connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .save = psb_intel_sdvo_save,
+ .restore = psb_intel_sdvo_restore,
+ .detect = psb_intel_sdvo_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = psb_intel_sdvo_destroy,
+};
+
+static const struct drm_connector_helper_funcs
+ psb_intel_sdvo_connector_helper_funcs = {
+ .get_modes = psb_intel_sdvo_get_modes,
+ .mode_valid = psb_intel_sdvo_mode_valid,
+ .best_encoder = psb_intel_best_encoder,
+};
+
+void psb_intel_sdvo_enc_destroy(struct drm_encoder *encoder)
+{
+ drm_encoder_cleanup(encoder);
+}
+
+static const struct drm_encoder_funcs psb_intel_sdvo_enc_funcs = {
+ .destroy = psb_intel_sdvo_enc_destroy,
+};
+
+
+void psb_intel_sdvo_init(struct drm_device *dev, int output_device)
+{
+ struct drm_connector *connector;
+ struct psb_intel_output *psb_intel_output;
+ struct psb_intel_sdvo_priv *sdvo_priv;
+ struct psb_intel_i2c_chan *i2cbus = NULL;
+ int connector_type;
+ u8 ch[0x40];
+ int i;
+ int encoder_type, output_id;
+
+ psb_intel_output =
+ kcalloc(sizeof(struct psb_intel_output) +
+ sizeof(struct psb_intel_sdvo_priv), 1, GFP_KERNEL);
+ if (!psb_intel_output)
+ return;
+
+ connector = &psb_intel_output->base;
+
+ drm_connector_init(dev, connector, &psb_intel_sdvo_connector_funcs,
+ DRM_MODE_CONNECTOR_Unknown);
+ drm_connector_helper_add(connector,
+ &psb_intel_sdvo_connector_helper_funcs);
+ sdvo_priv = (struct psb_intel_sdvo_priv *) (psb_intel_output + 1);
+ psb_intel_output->type = INTEL_OUTPUT_SDVO;
+
+ connector->interlace_allowed = 0;
+ connector->doublescan_allowed = 0;
+
+ /* setup the DDC bus. */
+ if (output_device == SDVOB)
+ i2cbus =
+ psb_intel_i2c_create(dev, GPIOE, "SDVOCTRL_E for SDVOB");
+ else
+ i2cbus =
+ psb_intel_i2c_create(dev, GPIOE, "SDVOCTRL_E for SDVOC");
+
+ if (!i2cbus)
+ goto err_connector;
+
+ sdvo_priv->i2c_bus = i2cbus;
+
+ if (output_device == SDVOB) {
+ output_id = 1;
+ sdvo_priv->by_input_wiring = SDVOB_IN0;
+ sdvo_priv->i2c_bus->slave_addr = 0x38;
+ } else {
+ output_id = 2;
+ sdvo_priv->i2c_bus->slave_addr = 0x39;
+ }
+
+ sdvo_priv->output_device = output_device;
+ psb_intel_output->i2c_bus = i2cbus;
+ psb_intel_output->dev_priv = sdvo_priv;
+
+
+ /* Read the regs to test if we can talk to the device */
+ for (i = 0; i < 0x40; i++) {
+ if (!psb_intel_sdvo_read_byte(psb_intel_output, i, &ch[i])) {
+ dev_dbg(dev->dev, "No SDVO device found on SDVO%c\n",
+ output_device == SDVOB ? 'B' : 'C');
+ goto err_i2c;
+ }
+ }
+
+ psb_intel_sdvo_get_capabilities(psb_intel_output, &sdvo_priv->caps);
+
+ memset(&sdvo_priv->active_outputs, 0,
+ sizeof(sdvo_priv->active_outputs));
+
+ /* TODO, CVBS, SVID, YPRPB & SCART outputs. */
+ if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_RGB0) {
+ sdvo_priv->active_outputs = SDVO_OUTPUT_RGB0;
+ sdvo_priv->active_device = SDVO_DEVICE_CRT;
+ connector->display_info.subpixel_order =
+ SubPixelHorizontalRGB;
+ encoder_type = DRM_MODE_ENCODER_DAC;
+ connector_type = DRM_MODE_CONNECTOR_VGA;
+ } else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_RGB1) {
+ sdvo_priv->active_outputs = SDVO_OUTPUT_RGB1;
+ sdvo_priv->active_outputs = SDVO_DEVICE_CRT;
+ connector->display_info.subpixel_order =
+ SubPixelHorizontalRGB;
+ encoder_type = DRM_MODE_ENCODER_DAC;
+ connector_type = DRM_MODE_CONNECTOR_VGA;
+ } else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_TMDS0) {
+ sdvo_priv->active_outputs = SDVO_OUTPUT_TMDS0;
+ sdvo_priv->active_device = SDVO_DEVICE_TMDS;
+ connector->display_info.subpixel_order =
+ SubPixelHorizontalRGB;
+ encoder_type = DRM_MODE_ENCODER_TMDS;
+ connector_type = DRM_MODE_CONNECTOR_DVID;
+ } else if (sdvo_priv->caps.output_flags & SDVO_OUTPUT_TMDS1) {
+ sdvo_priv->active_outputs = SDVO_OUTPUT_TMDS1;
+ sdvo_priv->active_device = SDVO_DEVICE_TMDS;
+ connector->display_info.subpixel_order =
+ SubPixelHorizontalRGB;
+ encoder_type = DRM_MODE_ENCODER_TMDS;
+ connector_type = DRM_MODE_CONNECTOR_DVID;
+ } else {
+ unsigned char bytes[2];
+
+ memcpy(bytes, &sdvo_priv->caps.output_flags, 2);
+ dev_dbg(dev->dev, "%s: No active RGB or TMDS outputs (0x%02x%02x)\n",
+ SDVO_NAME(sdvo_priv), bytes[0], bytes[1]);
+ goto err_i2c;
+ }
+
+ drm_encoder_init(dev, &psb_intel_output->enc, &psb_intel_sdvo_enc_funcs,
+ encoder_type);
+ drm_encoder_helper_add(&psb_intel_output->enc,
+ &psb_intel_sdvo_helper_funcs);
+ connector->connector_type = connector_type;
+
+ drm_mode_connector_attach_encoder(&psb_intel_output->base,
+ &psb_intel_output->enc);
+ drm_sysfs_connector_add(connector);
+
+ /* Set the input timing to the screen. Assume always input 0. */
+ psb_intel_sdvo_set_target_input(psb_intel_output, true, false);
+
+ psb_intel_sdvo_get_input_pixel_clock_range(psb_intel_output,
+ &sdvo_priv->pixel_clock_min,
+ &sdvo_priv->
+ pixel_clock_max);
+
+
+ dev_dbg(dev->dev, "%s device VID/DID: %02X:%02X.%02X, "
+ "clock range %dMHz - %dMHz, "
+ "input 1: %c, input 2: %c, "
+ "output 1: %c, output 2: %c\n",
+ SDVO_NAME(sdvo_priv),
+ sdvo_priv->caps.vendor_id, sdvo_priv->caps.device_id,
+ sdvo_priv->caps.device_rev_id,
+ sdvo_priv->pixel_clock_min / 1000,
+ sdvo_priv->pixel_clock_max / 1000,
+ (sdvo_priv->caps.sdvo_inputs_mask & 0x1) ? 'Y' : 'N',
+ (sdvo_priv->caps.sdvo_inputs_mask & 0x2) ? 'Y' : 'N',
+ /* check currently supported outputs */
+ sdvo_priv->caps.output_flags &
+ (SDVO_OUTPUT_TMDS0 | SDVO_OUTPUT_RGB0) ? 'Y' : 'N',
+ sdvo_priv->caps.output_flags &
+ (SDVO_OUTPUT_TMDS1 | SDVO_OUTPUT_RGB1) ? 'Y' : 'N');
+
+ psb_intel_output->ddc_bus = i2cbus;
+
+ return;
+
+err_i2c:
+ psb_intel_i2c_destroy(psb_intel_output->i2c_bus);
+err_connector:
+ drm_connector_cleanup(connector);
+ kfree(psb_intel_output);
+
+ return;
+}