diff options
author | Alan Cox <alan@linux.intel.com> | 2011-11-03 18:22:15 +0000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2011-11-16 11:26:55 +0000 |
commit | 89c78134cc54dff016c83367912eb055637fa50c (patch) | |
tree | 2f0d9167e187726c47efabe9a4d7811f950d191c /drivers/gpu/drm/gma500/psb_intel_sdvo.c | |
parent | 5c49fd3aa0ab025e5f94617249db10a33138f37b (diff) | |
download | op-kernel-dev-89c78134cc54dff016c83367912eb055637fa50c.zip op-kernel-dev-89c78134cc54dff016c83367912eb055637fa50c.tar.gz |
gma500: Add Poulsbo support
This provides the specific code for Poulsbo, some of which is also used for
the later chipsets. We support the GTT, the 2D engine (for console), and
the display setup/management. We do not support 3D or the video overlays.
In theory enough public info is available to do the video overlay work
but that represents a large task.
Framebuffer X will run nicely with this but do *NOT* use the VESA X
server at the same time as KMS. With a Dell mini 10 things like Xfce4 are
nice and usable even when compositing as the CPU has a good path to the
memory.
Signed-off-by: Alan Cox <alan@linux.intel.com>
Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/gma500/psb_intel_sdvo.c')
-rw-r--r-- | drivers/gpu/drm/gma500/psb_intel_sdvo.c | 1293 |
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 0000000..a4bad1a --- /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; +} |