| /* Copyright (c) 2013, The Linux Foundation. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in |
| * the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of The Linux Foundation nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS |
| * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE |
| * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, |
| * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS |
| * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED |
| * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT |
| * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "edp.h" |
| |
| struct edp_aux_ctrl edpctrl; |
| |
| int edp_hpd_done = 0; |
| int edp_video_ready = 0; |
| |
| /* |
| * edid |
| */ |
| static char edid_hdr[8] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00}; |
| |
| |
| int edp_edid_buf_error(char *buf, int len) |
| { |
| char *bp; |
| int i; |
| char csum = 0; |
| int ret = 0; |
| |
| bp = buf; |
| if (len < 128) { |
| dprintf(INFO, "%s: Error: len=%x\n", __func__, len); |
| return -1; |
| } |
| |
| for (i = 0; i < 128; i++) |
| csum += *bp++; |
| |
| if (csum != 0) { |
| dprintf(INFO, "%s: Error: csum=%x\n", __func__, csum); |
| return -1; |
| } |
| |
| if (buf[1] != 0xff) { |
| dprintf(INFO, "%s: Error: header\n", __func__); |
| return -1; |
| } |
| |
| return ret; |
| } |
| |
| |
| void edp_extract_edid_manufacturer(struct edp_edid *edid, char *buf) |
| { |
| char *bp; |
| char data; |
| |
| bp = &buf[8]; |
| data = *bp & 0x7f; |
| data >>= 2; |
| edid->id_name[0] = 'A' + data - 1; |
| data = *bp & 0x03; |
| data <<= 3; |
| bp++; |
| data |= (*bp >> 5); |
| edid->id_name[1] = 'A' + data - 1; |
| data = *bp & 0x1f; |
| edid->id_name[2] = 'A' + data - 1; |
| edid->id_name[3] = 0; |
| |
| dprintf(SPEW, "%s: edid manufacturer = %s", __func__, edid->id_name); |
| } |
| |
| void edp_extract_edid_product(struct edp_edid *edid, char *buf) |
| { |
| char *bp; |
| int data; |
| |
| bp = &buf[0x0a]; |
| data = *bp; |
| edid->id_product = *bp++; |
| edid->id_product &= 0x0ff; |
| data = *bp & 0x0ff; |
| data <<= 8; |
| edid->id_product |= data; |
| |
| dprintf(SPEW, "%s: edid product = 0x%x\n", __func__, edid->id_product); |
| }; |
| |
| void edp_extract_edid_version(struct edp_edid *edid, char *buf) |
| { |
| edid->version = buf[0x12]; |
| edid->revision = buf[0x13]; |
| dprintf(SPEW, "%s: edid version = %d.%d", __func__, |
| edid->version, edid->revision); |
| }; |
| |
| void edp_extract_edid_ext_block_cnt(struct edp_edid *edid, char *buf) |
| { |
| edid->ext_block_cnt = buf[0x7e]; |
| dprintf(SPEW, "%s: edid extension = %d", __func__, edid->ext_block_cnt); |
| }; |
| |
| void edp_extract_edid_video_support(struct edp_edid *edid, char *buf) |
| { |
| char *bp; |
| |
| bp = &buf[0x14]; |
| if (*bp & 0x80) { |
| edid->video_intf = *bp & 0x0f; |
| /* 6, 8, 10, 12, 14 and 16 bit per component */ |
| edid->color_depth = ((*bp & 0x70) >> 4); /* color bit depth */ |
| if (edid->color_depth) { |
| edid->color_depth *= 2; |
| edid->color_depth += 4; |
| } |
| dprintf(SPEW, "%s: Digital Video intf=%d color_depth=%d\n", |
| __func__, edid->video_intf, edid->color_depth); |
| return; |
| } |
| dprintf(INFO, "%s: Error, Analog video interface", __func__); |
| }; |
| |
| void edp_extract_edid_feature(struct edp_edid *edid, char *buf) |
| { |
| char *bp; |
| char data; |
| |
| bp = &buf[0x18]; |
| data = *bp; |
| data &= 0xe0; |
| data >>= 5; |
| if (data == 0x01) |
| edid->dpm = 1; /* display power management */ |
| |
| if (edid->video_intf) { |
| if (*bp & 0x80) { |
| /* RGB 4:4:4, YcrCb 4:4:4 and YCrCb 4:2:2 */ |
| edid->color_format = *bp & 0x18; |
| edid->color_format >>= 3; |
| } |
| } |
| |
| dprintf(SPEW, "%s: edid dpm=%d color_format=%d", |
| __func__, edid->dpm, edid->color_format); |
| }; |
| |
| void edp_extract_edid_detailed_timing_description(struct edp_edid *edid, |
| char *buf) |
| { |
| char *bp; |
| int data; |
| struct display_timing_desc *dp; |
| |
| dp = &edid->timing[0]; |
| |
| bp = &buf[0x36]; |
| dp->pclk = 0; |
| dp->pclk = *bp++; /* byte 0x36 */ |
| dp->pclk |= (*bp++ << 8); /* byte 0x37 */ |
| |
| dp->h_addressable = *bp++; /* byte 0x38 */ |
| |
| if (dp->pclk == 0 && dp->h_addressable == 0) |
| return; /* Not detailed timing definition */ |
| |
| dp->pclk *= 10000; |
| |
| dp->h_blank = *bp++;/* byte 0x39 */ |
| data = *bp & 0xf0; /* byte 0x3A */ |
| data <<= 4; |
| dp->h_addressable |= data; |
| |
| data = *bp++ & 0x0f; |
| data <<= 8; |
| dp->h_blank |= data; |
| |
| dp->v_addressable = *bp++; /* byte 0x3B */ |
| dp->v_blank = *bp++; /* byte 0x3C */ |
| data = *bp & 0xf0; /* byte 0x3D */ |
| data <<= 4; |
| dp->v_addressable |= data; |
| |
| data = *bp++ & 0x0f; |
| data <<= 8; |
| dp->v_blank |= data; |
| |
| dp->h_fporch = *bp++; /* byte 0x3E */ |
| dp->h_sync_pulse = *bp++; /* byte 0x3F */ |
| |
| dp->v_fporch = *bp & 0x0f0; /* byte 0x40 */ |
| dp->v_fporch >>= 4; |
| dp->v_sync_pulse = *bp & 0x0f; |
| |
| bp++; |
| data = *bp & 0xc0; /* byte 0x41 */ |
| data <<= 2; |
| dp->h_fporch |= data; |
| |
| data = *bp & 0x30; |
| data <<= 4; |
| dp->h_sync_pulse |= data; |
| |
| data = *bp & 0x0c; |
| data <<= 2; |
| dp->v_fporch |= data; |
| |
| data = *bp & 0x03; |
| data <<= 4; |
| dp->v_sync_pulse |= data; |
| |
| bp++; |
| dp->width_mm = *bp++; /* byte 0x42 */ |
| dp->height_mm = *bp++; /* byte 0x43 */ |
| data = *bp & 0x0f0; /* byte 0x44 */ |
| data <<= 4; |
| dp->width_mm |= data; |
| data = *bp & 0x0f; |
| data <<= 8; |
| dp->height_mm |= data; |
| |
| bp++; |
| dp->h_border = *bp++; /* byte 0x45 */ |
| dp->v_border = *bp++; /* byte 0x46 */ |
| |
| dp->interlaced = *bp & 0x80; /* byte 0x47 */ |
| |
| dp->stereo = *bp & 0x60; |
| dp->stereo >>= 5; |
| |
| data = *bp & 0x1e; /* bit 4,3,2 1*/ |
| data >>= 1; |
| dp->sync_type = data & 0x08; |
| dp->sync_type >>= 3; /* analog or digital */ |
| if (dp->sync_type) { |
| dp->sync_separate = data & 0x04; |
| dp->sync_separate >>= 2; |
| if (dp->sync_separate) { |
| if (data & 0x02) |
| dp->vsync_pol = 1; /* positive */ |
| else |
| dp->vsync_pol = 0;/* negative */ |
| |
| if (data & 0x01) |
| dp->hsync_pol = 1; /* positive */ |
| else |
| dp->hsync_pol = 0; /* negative */ |
| } |
| } |
| |
| dprintf(SPEW, "%s: pixel_clock = %d\n", __func__, dp->pclk); |
| |
| dprintf(SPEW, "%s: horizontal=%d, blank=%d, porch=%d, sync=%d\n", |
| __func__, dp->h_addressable, dp->h_blank, |
| dp->h_fporch, dp->h_sync_pulse); |
| dprintf(SPEW, "%s: vertical=%d, blank=%d, porch=%d, vsync=%d\n", |
| __func__, dp->v_addressable, dp->v_blank, |
| dp->v_fporch, dp->v_sync_pulse); |
| dprintf(SPEW, "%s: panel size in mm, width=%d height=%d\n", |
| __func__, dp->width_mm, dp->height_mm); |
| dprintf(SPEW, "%s: panel border horizontal=%d vertical=%d\n", |
| __func__, dp->h_border, dp->v_border); |
| dprintf(SPEW, "%s: interlaced=%d stereo=%d sync_type=%d sync_sep=%d\n", |
| __func__, dp->interlaced, dp->stereo, |
| dp->sync_type, dp->sync_separate); |
| dprintf(SPEW, "%s: polarity vsync=%d, hsync=%d\n", |
| __func__, dp->vsync_pol, dp->hsync_pol); |
| } |
| |
| |
| /* |
| * EDID structure can be found in VESA standart here: |
| * http://read.pudn.com/downloads110/ebook/456020/E-EDID%20Standard.pdf |
| * |
| * following table contains default edid |
| * static char edid_raw_data[128] = { |
| * 0, 255, 255, 255, 255, 255, 255, 0, |
| * 6, 175, 93, 48, 0, 0, 0, 0, 0, 22, |
| * 1, 4, |
| * 149, 26, 14, 120, 2, |
| * 164, 21,158, 85, 78, 155, 38, 15, 80, 84, |
| * 0, 0, 0, |
| * 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
| * 29, 54, 128, 160, 112, 56, 30, 64, 48, 32, 142, 0, 0, 144, 16,0,0,24, |
| * 19, 36, 128, 160, 112, 56, 30, 64, 48, 32, 142, 0, 0, 144, 16,0,0,24, |
| * 0, 0, 0, 254, 0, 65, 85, 79, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, |
| * 0, 0, 0, 254, 0, 66, 49, 49, 54, 72, 65, 78, 48, 51, 46, 48, 32, 10, |
| * 0, 75 }; |
| */ |
| |
| static int edp_aux_chan_ready(struct edp_aux_ctrl *ep) |
| { |
| int cnt, ret; |
| char data = 0; |
| |
| cnt = 5; |
| while(cnt--) { |
| ret = edp_aux_write_buf(ep, 0x50, &data, 1, 1); |
| dprintf(SPEW, "%s: ret=%d\n", __func__, ret); |
| if (ret >= 0) |
| break; |
| dprintf(INFO, "%s: failed in write\n", __func__); |
| mdelay(100); |
| } |
| |
| if (cnt == 0) |
| return 0; |
| |
| return 1; |
| } |
| |
| static int edp_sink_edid_read(struct edp_aux_ctrl *ep, int block) |
| { |
| struct edp_buf *rp; |
| int cnt, rlen; |
| char data = 0; |
| int ret = 0; |
| |
| start: |
| cnt = 5; |
| dprintf(SPEW, "%s: cnt=%d\n", __func__, cnt); |
| /* need to write a dummy byte before read edid */ |
| while(cnt--) { |
| ret = edp_aux_write_buf(ep, 0x50, &data, 1, 1); |
| if (ret >= 0) |
| break; |
| dprintf(INFO, "%s: failed in write\n", __func__); |
| mdelay(100); |
| } |
| |
| if (cnt == 0) |
| return -1; |
| |
| rlen = edp_aux_read_buf(ep, 0x50, 128, 1); |
| |
| dprintf(SPEW, "%s: rlen=%d\n", rlen, __func__); |
| |
| if (rlen < 0) |
| goto start; |
| |
| rp = &ep->rxp; |
| if (edp_edid_buf_error(rp->data, rp->len)) |
| goto start; |
| |
| edp_extract_edid_manufacturer(&ep->edid, rp->data); |
| edp_extract_edid_product(&ep->edid, rp->data); |
| edp_extract_edid_version(&ep->edid, rp->data); |
| edp_extract_edid_ext_block_cnt(&ep->edid, rp->data); |
| edp_extract_edid_video_support(&ep->edid, rp->data); |
| edp_extract_edid_feature(&ep->edid, rp->data); |
| edp_extract_edid_detailed_timing_description(&ep->edid, rp->data); |
| |
| return 128; |
| } |
| |
| /* |
| * Converts from EDID struct to msm_panel_info |
| */ |
| void edp_edid2pinfo(struct msm_panel_info *pinfo) |
| { |
| struct display_timing_desc *dp; |
| |
| dp = &edpctrl.edid.timing[0]; |
| |
| pinfo->clk_rate = dp->pclk; |
| |
| dprintf(SPEW, "%s: pclk=%d\n", __func__, pinfo->clk_rate); |
| |
| pinfo->xres = dp->h_addressable + dp->h_border * 2; |
| pinfo->yres = dp->v_addressable + dp->v_border * 2; |
| |
| pinfo->lcdc.h_back_porch = dp->h_blank - dp->h_fporch \ |
| - dp->h_sync_pulse; |
| pinfo->lcdc.h_front_porch = dp->h_fporch; |
| pinfo->lcdc.h_pulse_width = dp->h_sync_pulse; |
| |
| pinfo->lcdc.v_back_porch = dp->v_blank - dp->v_fporch \ |
| - dp->v_sync_pulse; |
| pinfo->lcdc.v_front_porch = dp->v_fporch; |
| pinfo->lcdc.v_pulse_width = dp->v_sync_pulse; |
| |
| pinfo->type = EDP_PANEL; |
| pinfo->wait_cycle = 0; |
| pinfo->bpp = 24; |
| |
| pinfo->lcdc.border_clr = 0; /* black */ |
| pinfo->lcdc.underflow_clr = 0xff; /* blue */ |
| pinfo->lcdc.hsync_skew = 0; |
| } |
| |
| void edp_cap2pinfo(struct msm_panel_info *pinfo) |
| { |
| struct dpcd_cap *cap; |
| |
| cap = &edpctrl.dpcd; |
| |
| pinfo->edp.max_lane_count = cap->max_lane_count; |
| pinfo->edp.max_link_clk = cap->max_link_rate; |
| |
| dprintf(SPEW, "%s: clk=%d lane=%d\n", __func__, |
| pinfo->edp.max_lane_count, pinfo->edp.max_link_clk); |
| } |
| |
| static void edp_sink_capability_read(struct edp_aux_ctrl *ep, |
| int len) |
| { |
| char *bp; |
| char data; |
| struct dpcd_cap *cap; |
| struct edp_buf *rp; |
| int rlen; |
| |
| dprintf(SPEW, "%s:\n",__func__); |
| |
| rlen = edp_aux_read_buf(ep, 0, len, 0); |
| if (rlen <= 0) { |
| dprintf(INFO, "%s: edp aux read failed\n", __func__); |
| return; |
| } |
| rp = &ep->rxp; |
| cap = &ep->dpcd; |
| bp = rp->data; |
| |
| data = *bp++; /* byte 0 */ |
| cap->major = (data >> 4) & 0x0f; |
| cap->minor = data & 0x0f; |
| if (--rlen <= 0) |
| return; |
| dprintf(SPEW, "%s: version: %d.%d\n", __func__, cap->major, cap->minor); |
| |
| data = *bp++; /* byte 1 */ |
| /* 162, 270 and 540 MB, symbol rate, NOT bit rate */ |
| cap->max_link_rate = data * 27; |
| if (--rlen <= 0) |
| return; |
| dprintf(SPEW, "%s: link_rate=%d\n", __func__, cap->max_link_rate); |
| |
| data = *bp++; /* byte 2 */ |
| if (data & BIT(7)) |
| cap->flags |= DPCD_ENHANCED_FRAME; |
| if (data & 0x40) |
| cap->flags |= DPCD_TPS3; |
| data &= 0x0f; |
| cap->max_lane_count = data; |
| if (--rlen <= 0) |
| return; |
| dprintf(SPEW, "%s: lane_count=%d\n", __func__, cap->max_lane_count); |
| |
| data = *bp++; /* byte 3 */ |
| if (data & BIT(0)) { |
| cap->flags |= DPCD_MAX_DOWNSPREAD_0_5; |
| dprintf(SPEW, "%s: max_downspread\n", __func__); |
| } |
| |
| if (data & BIT(6)) { |
| cap->flags |= DPCD_NO_AUX_HANDSHAKE; |
| dprintf(SPEW, "%s: NO Link Training\n", __func__); |
| } |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 4 */ |
| cap->num_rx_port = (data & BIT(0)) + 1; |
| dprintf(SPEW, "%s: rx_ports=%d", __func__, cap->num_rx_port); |
| if (--rlen <= 0) |
| return; |
| |
| bp += 3; /* skip 5, 6 and 7 */ |
| rlen -= 3; |
| if (rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 8 */ |
| if (data & BIT(1)) { |
| cap->flags |= DPCD_PORT_0_EDID_PRESENTED; |
| dprintf(SPEW, "%s: edid presented\n", __func__); |
| } |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 9 */ |
| cap->rx_port0_buf_size = (data + 1) * 32; |
| dprintf(SPEW, "%s: lane_buf_size=%d", __func__, cap->rx_port0_buf_size); |
| if (--rlen <= 0) |
| return; |
| |
| bp += 2; /* skip 10, 11 port1 capability */ |
| rlen -= 2; |
| if (rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 12 */ |
| cap->i2c_speed_ctrl = data; |
| if (cap->i2c_speed_ctrl > 0) |
| dprintf(SPEW, "%s: i2c_rate=%d", __func__, cap->i2c_speed_ctrl); |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 13 */ |
| cap->scrambler_reset = data & BIT(0); |
| dprintf(SPEW, "%s: scrambler_reset=%d\n", __func__, |
| cap->scrambler_reset); |
| |
| cap->enhanced_frame = data & BIT(1); |
| dprintf(SPEW, "%s: enhanced_framing=%d\n", __func__, |
| cap->enhanced_frame); |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 14 */ |
| if (data == 0) |
| cap->training_read_interval = 100; /* us */ |
| else |
| cap->training_read_interval = 4000 * data; /* us */ |
| dprintf(SPEW, "%s: training_interval=%d\n", __func__, |
| cap->training_read_interval); |
| } |
| |
| static void edp_link_status_read(struct edp_aux_ctrl *ep, int len) |
| { |
| char *bp; |
| char data; |
| struct dpcd_link_status *sp; |
| struct edp_buf *rp; |
| int rlen; |
| |
| |
| /* skip byte 0x200 and 0x201 */ |
| rlen = edp_aux_read_buf(ep, 0x202, len, 0); |
| dprintf(SPEW, "%s: rlen=%d\n", __func__, rlen); |
| if (rlen <= 0) { |
| dprintf(SPEW, "%s: edp aux read failed\n", __func__); |
| return; |
| } |
| rp = &ep->rxp; |
| bp = rp->data; |
| sp = &ep->link_status; |
| |
| data = *bp++; /* byte 0x202 */ |
| sp->lane_01_status = data; /* lane 0, 1 */ |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 0x203 */ |
| sp->lane_23_status = data; /* lane 2, 3 */ |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 0x204 */ |
| sp->interlane_align_done = (data & BIT(0)); |
| sp->downstream_port_status_changed = (data & BIT(6)); |
| sp->link_status_updated = (data & BIT(7)); |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 0x205 */ |
| sp->port_0_in_sync = (data & BIT(0)); |
| sp->port_1_in_sync = (data & BIT(1)); |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 0x206 */ |
| sp->req_voltage_swing[0] = data & 0x03; |
| data >>= 2; |
| sp->req_pre_emphasis[0] = data & 0x03; |
| data >>= 2; |
| sp->req_voltage_swing[1] = data & 0x03; |
| data >>= 2; |
| sp->req_pre_emphasis[1] = data & 0x03; |
| if (--rlen <= 0) |
| return; |
| |
| data = *bp++; /* byte 0x207 */ |
| sp->req_voltage_swing[2] = data & 0x03; |
| data >>= 2; |
| sp->req_pre_emphasis[2] = data & 0x03; |
| data >>= 2; |
| sp->req_voltage_swing[3] = data & 0x03; |
| data >>= 2; |
| sp->req_pre_emphasis[3] = data & 0x03; |
| |
| bp = rp->data; |
| dprintf(SPEW, "%s: %x %x %x %x %x %x\n", __func__, *bp, |
| *(bp+1), *(bp+2), *(bp+3), *(bp+4), *(bp+5)); |
| |
| dprintf(SPEW, "%s: align=%d v=%d p=%d\n", __func__, |
| sp->interlane_align_done, sp->req_voltage_swing[0], sp->req_pre_emphasis[0]); |
| } |
| |
| |
| static int edp_cap_lane_rate_set(struct edp_aux_ctrl *ep) |
| { |
| char buf[4]; |
| int len = 0; |
| |
| dprintf(SPEW, "%s: bw=%x lane=%d\n", __func__, |
| ep->link_rate, ep->lane_cnt); |
| buf[0] = ep->link_rate; |
| buf[1] = ep->lane_cnt; |
| len = edp_aux_write_buf(ep, 0x100, buf, 2, 0); |
| |
| return len; |
| } |
| |
| static int edp_lane_set_write(struct edp_aux_ctrl *ep, int voltage_level, |
| int pre_emphasis_level) |
| { |
| int i; |
| char buf[4]; |
| |
| |
| if (voltage_level >= DPCD_LINK_VOLTAGE_MAX) |
| voltage_level |= 0x04; |
| |
| if (pre_emphasis_level >= DPCD_LINK_PRE_EMPHASIS_MAX) |
| pre_emphasis_level |= 0x04; |
| |
| pre_emphasis_level <<= 3; |
| |
| for (i = 0; i < 4; i++) |
| buf[i] = voltage_level | pre_emphasis_level; |
| |
| dprintf(SPEW, "%s: p|v=0x%x\n", __func__, voltage_level | pre_emphasis_level); |
| return edp_aux_write_buf(ep, 0x103, buf, 4, 0); |
| } |
| |
| static int edp_powerstate_write(struct edp_aux_ctrl *ep, |
| char powerstate) |
| { |
| return edp_aux_write_buf(ep, 0x600, &powerstate, 1, 0); |
| } |
| |
| static int edp_train_pattern_set_write(struct edp_aux_ctrl *ep, |
| int pattern) |
| { |
| char buf[4]; |
| |
| buf[0] = pattern; |
| return edp_aux_write_buf(ep, 0x102, buf, 1, 0); |
| } |
| |
| static int edp_sink_clock_recovery_done(struct edp_aux_ctrl *ep) |
| { |
| int mask; |
| int data; |
| |
| |
| if (ep->lane_cnt == 1) { |
| mask = 0x01; /* lane 0 */ |
| data = ep->link_status.lane_01_status; |
| } else if (ep->lane_cnt == 2) { |
| mask = 0x011; /*B lane 0, 1 */ |
| data = ep->link_status.lane_01_status; |
| } else { |
| mask = 0x01111; /*B lane 0, 1 */ |
| data = ep->link_status.lane_23_status; |
| data <<= 8; |
| data |= ep->link_status.lane_01_status; |
| } |
| |
| dprintf(SPEW, "%s: data=%x mask=%x\n", __func__, data, mask); |
| data &= mask; |
| if (data == mask) /* all done */ |
| return 1; |
| |
| return 0; |
| } |
| |
| static int edp_sink_channel_eq_done(struct edp_aux_ctrl *ep) |
| { |
| int mask; |
| int data; |
| |
| |
| if (!ep->link_status.interlane_align_done) /* not align */ |
| return 0; |
| |
| if (ep->lane_cnt == 1) { |
| mask = 0x7; |
| data = ep->link_status.lane_01_status; |
| } else if (ep->lane_cnt == 2) { |
| mask = 0x77; |
| data = ep->link_status.lane_01_status; |
| } else { |
| mask = 0x7777; |
| data = ep->link_status.lane_23_status; |
| data <<= 8; |
| data |= ep->link_status.lane_01_status; |
| } |
| |
| dprintf(SPEW, "%s: data=%x mask=%x\n", __func__, data, mask); |
| |
| data &= mask; |
| if (data == mask)/* all done */ |
| return 1; |
| |
| return 0; |
| } |
| |
| void edp_sink_train_set_adjust(struct edp_aux_ctrl *ep) |
| { |
| int i; |
| int max = 0; |
| |
| |
| /* use the max level across lanes */ |
| for (i = 0; i < ep->lane_cnt; i++) { |
| if (max < ep->link_status.req_voltage_swing[i]) |
| max = ep->link_status.req_voltage_swing[i]; |
| } |
| |
| ep->v_level = max; |
| |
| /* use the max level across lanes */ |
| max = 0; |
| for (i = 0; i < ep->lane_cnt; i++) { |
| if (max < ep->link_status.req_pre_emphasis[i]) |
| max = ep->link_status.req_pre_emphasis[i]; |
| } |
| |
| ep->p_level = max; |
| dprintf(SPEW, "%s: v_level=%d, p_level=%d\n", __func__, |
| ep->v_level, ep->p_level); |
| } |
| |
| static void edp_host_train_set(struct edp_aux_ctrl *ep, int train) |
| { |
| int bit, cnt; |
| int data; |
| |
| |
| bit = 1; |
| bit <<= (train - 1); |
| edp_write(EDP_BASE + EDP_STATE_CTRL, bit); |
| |
| bit = 8; |
| bit <<= (train - 1); |
| cnt = 10; |
| while (cnt--) { |
| data = edp_read(EDP_BASE + EDP_MAINLINK_READY); |
| if (data & bit) |
| break; |
| } |
| |
| if (cnt == 0) |
| dprintf(SPEW, "%s: set link_train=%d failed\n", __func__, train); |
| } |
| |
| char vm_pre_emphasis[4][4] = { |
| {0x03, 0x06, 0x09, 0x0C}, |
| {0x03, 0x06, 0x09, 0xFF}, |
| {0x03, 0x06, 0xFF, 0xFF}, |
| {0x03, 0xFF, 0xFF, 0xFF} |
| }; |
| |
| char vm_voltage_swing[4][4] = { |
| {0x64, 0x68, 0x6A, 0x6E}, |
| {0x68, 0x6A, 0x6E, 0xFF}, |
| {0x6A, 0x6E, 0xFF, 0xFF}, |
| {0x6E, 0xFF, 0xFF, 0xFF} |
| }; |
| |
| static void edp_voltage_pre_emphasise_set(struct edp_aux_ctrl *ep) |
| { |
| int value0 = 0; |
| int value1 = 0; |
| |
| dprintf(SPEW, "%s: v=%d p=%d\n", __func__, ep->v_level, ep->p_level); |
| |
| value0 = vm_pre_emphasis[(int)(ep->v_level)][(int)(ep->p_level)]; |
| value1 = vm_voltage_swing[(int)(ep->v_level)][(int)(ep->p_level)]; |
| |
| /* Configure host and panel only if both values are allowed */ |
| if (value0 != 0xFF && value1 != 0xFF) { |
| edp_write(EDP_BASE + EDP_PHY_EDPPHY_GLB_VM_CFG0, value0); |
| edp_write(EDP_BASE + EDP_PHY_EDPPHY_GLB_VM_CFG1, value1); |
| dprintf(SPEW, "%s: value0=0x%x value1=0x%x\n", __func__, |
| value0, value1); |
| edp_lane_set_write(ep, ep->v_level, ep->p_level); |
| } |
| |
| } |
| |
| static int edp_start_link_train_1(struct edp_aux_ctrl *ep) |
| { |
| int tries, old_v_level; |
| int ret = 0; |
| |
| dprintf(SPEW, "%s:\n", __func__); |
| |
| edp_host_train_set(ep, 0x01); /* train_1 */ |
| edp_voltage_pre_emphasise_set(ep); |
| edp_train_pattern_set_write(ep, 0x21); /* train_1 */ |
| |
| tries = 0; |
| old_v_level = ep->v_level; |
| while (1) { |
| udelay(ep->dpcd.training_read_interval * 10); |
| |
| edp_link_status_read(ep, 6); |
| if (edp_sink_clock_recovery_done(ep)) { |
| ret = 0; |
| break; |
| } |
| |
| if (ep->v_level == DPCD_LINK_VOLTAGE_MAX) { |
| ret = -1; |
| break; /* quit */ |
| } |
| |
| if (old_v_level == ep->v_level) { |
| tries++; |
| if (tries >= 5) { |
| ret = -1; |
| break; /* quit */ |
| } |
| } else { |
| tries = 0; |
| old_v_level = ep->v_level; |
| } |
| |
| edp_sink_train_set_adjust(ep); |
| edp_voltage_pre_emphasise_set(ep); |
| } |
| |
| return ret; |
| } |
| |
| static int edp_start_link_train_2(struct edp_aux_ctrl *ep) |
| { |
| int tries; |
| int ret = 0; |
| char pattern; |
| |
| dprintf(SPEW, "%s\n", __func__); |
| |
| if (ep->dpcd.flags & DPCD_TPS3) |
| pattern = 0x03; |
| else |
| pattern = 0x02; |
| |
| edp_host_train_set(ep, pattern); /* train_2 */ |
| edp_voltage_pre_emphasise_set(ep); |
| edp_train_pattern_set_write(ep, pattern | 0x20);/* train_2 */ |
| |
| tries = 0; |
| while (1) { |
| udelay(ep->dpcd.training_read_interval); |
| |
| edp_link_status_read(ep, 6); |
| |
| if (edp_sink_channel_eq_done(ep)) { |
| ret = 0; |
| break; |
| } |
| |
| tries++; |
| if (tries > 5) { |
| ret = -1; |
| break; |
| } |
| |
| edp_sink_train_set_adjust(ep); |
| edp_voltage_pre_emphasise_set(ep); |
| } |
| |
| return ret; |
| } |
| |
| static int edp_link_rate_shift(struct edp_aux_ctrl *ep) |
| { |
| /* add calculation later */ |
| return -1; |
| } |
| |
| static void edp_clear_training_pattern(struct edp_aux_ctrl *ep) |
| { |
| dprintf(SPEW, "%s:\n", __func__); |
| edp_write(EDP_BASE + EDP_STATE_CTRL, 0); |
| edp_train_pattern_set_write(ep, 0); |
| udelay(ep->dpcd.training_read_interval); |
| } |
| |
| static int edp_aux_link_train(struct edp_aux_ctrl *ep) |
| { |
| int ret = 0; |
| |
| dprintf(SPEW, "%s:\n", __func__); |
| ret = edp_aux_chan_ready(ep); |
| if (ret == 0) { |
| dprintf(INFO, "%s: Error: aux chan NOT ready\n", |
| __func__); |
| return ret; |
| } |
| |
| /* start with max rate and lane */ |
| ep->lane_cnt = ep->dpcd.max_lane_count; |
| ep->link_rate = ep->dpcd.max_link_rate; |
| edp_write(EDP_BASE + EDP_MAINLINK_CTRL, 0x1); |
| |
| train_start: |
| ep->v_level = 0; /* start from default level */ |
| ep->p_level = 0; |
| edp_cap_lane_rate_set(ep); |
| |
| edp_clear_training_pattern(ep); |
| udelay(ep->dpcd.training_read_interval); |
| edp_powerstate_write(ep, 1); |
| |
| ret = edp_start_link_train_1(ep); |
| if (ret < 0) { |
| if (edp_link_rate_shift(ep) == 0) { |
| goto train_start; |
| } else { |
| dprintf(INFO, "%s: Training 1 failed\n", __func__); |
| ret = -1; |
| goto clear; |
| } |
| } |
| |
| dprintf(INFO, "%s: Training 1 completed successfully\n", __func__); |
| |
| edp_clear_training_pattern(ep); |
| ret = edp_start_link_train_2(ep); |
| if (ret < 0) { |
| if (edp_link_rate_shift(ep) == 0) { |
| goto train_start; |
| } else { |
| dprintf(INFO, "%s: Training 2 failed\n", __func__); |
| ret = -1; |
| goto clear; |
| } |
| } |
| |
| dprintf(INFO, "%s: Training 2 completed successfully\n", __func__); |
| |
| clear: |
| edp_clear_training_pattern(ep); |
| |
| return ret; |
| } |
| |
| void mdss_edp_wait_for_hpd(void) |
| { |
| while(1) { |
| udelay(1000); |
| edp_isr_poll(); |
| if (edp_hpd_done) { |
| edp_hpd_done = 0; |
| break; |
| } |
| } |
| } |
| |
| void mdss_edp_wait_for_video_ready(void) |
| { |
| while(1) { |
| udelay(1000); |
| edp_isr_poll(); |
| if (edp_video_ready) { |
| edp_video_ready = 0; |
| break; |
| } |
| } |
| } |
| |
| void mdss_edp_dpcd_cap_read(void) |
| { |
| edp_sink_capability_read(&edpctrl, 16); |
| } |
| void mdss_edp_pll_configure(void) |
| { |
| struct display_timing_desc *dp; |
| |
| dp = &edpctrl.edid.timing[0]; |
| edp_pll_configure(dp->pclk); |
| } |
| |
| void mdss_edp_lane_power_ctrl(int up) |
| { |
| |
| dprintf(SPEW, "%s: max_lane=%d\n", __func__, edpctrl.dpcd.max_lane_count); |
| edp_lane_power_ctrl(edpctrl.dpcd.max_lane_count, up); |
| |
| } |
| |
| void mdss_edp_dpcd_status_read(void) |
| { |
| edp_link_status_read(&edpctrl, 6); |
| } |
| |
| void mdss_edp_edid_read(void) |
| { |
| edp_sink_edid_read(&edpctrl, 0); |
| } |
| |
| int mdss_edp_link_train(void) |
| { |
| return edp_aux_link_train(&edpctrl); |
| } |
| |
| void mdss_edp_aux_init(void) |
| { |
| edp_buf_init(&edpctrl.txp, edpctrl.txbuf, sizeof(edpctrl.txbuf)); |
| edp_buf_init(&edpctrl.rxp, edpctrl.rxbuf, sizeof(edpctrl.rxbuf)); |
| } |