/* SPDX-License-Identifier: GPL-2.0-only */ #include <console/console.h> #include <commonlib/bsd/gcd.h> #include <delay.h> #include <device/i2c_simple.h> #include <edid.h> #include <gpio.h> #include <string.h> #include <types.h> #include "anx7625.h" #define ANXERROR(format, ...) \ printk(BIOS_ERR, "%s: " format, __func__, ##__VA_ARGS__) #define ANXINFO(format, ...) \ printk(BIOS_INFO, "%s: " format, __func__, ##__VA_ARGS__) #define ANXDEBUG(format, ...) \ printk(BIOS_DEBUG, "%s: " format, __func__, ##__VA_ARGS__) /* * There is a sync issue while accessing I2C register between AP(CPU) and * internal firmware(OCM). To avoid the race condition, AP should access the * reserved slave address before slave address changes. */ static int i2c_access_workaround(uint8_t bus, uint8_t saddr) { uint8_t offset; static uint8_t saddr_backup = 0; int ret = 0; if (saddr == saddr_backup) return ret; saddr_backup = saddr; switch (saddr) { case TCPC_INTERFACE_ADDR: offset = RSVD_00_ADDR; break; case TX_P0_ADDR: offset = RSVD_D1_ADDR; break; case TX_P1_ADDR: offset = RSVD_60_ADDR; break; case RX_P0_ADDR: offset = RSVD_39_ADDR; break; case RX_P1_ADDR: offset = RSVD_7F_ADDR; break; default: offset = RSVD_00_ADDR; break; } ret = i2c_writeb(bus, saddr, offset, 0x00); if (ret < 0) { ANXERROR("Failed to access %#x:%#x\n", saddr, offset); return ret; } return 0; } static int anx7625_reg_read(uint8_t bus, uint8_t saddr, uint8_t offset, uint8_t *val) { int ret; i2c_access_workaround(bus, saddr); ret = i2c_readb(bus, saddr, offset, val); if (ret < 0) { ANXERROR("Failed to read i2c reg=%#x:%#x\n", saddr, offset); return ret; } return 0; } static int anx7625_reg_block_read(uint8_t bus, uint8_t saddr, uint8_t reg_addr, uint8_t len, uint8_t *buf) { int ret; i2c_access_workaround(bus, saddr); ret = i2c_read_bytes(bus, saddr, reg_addr, buf, len); if (ret < 0) { ANXERROR("Failed to read i2c block=%#x:%#x[len=%#x]\n", saddr, reg_addr, len); return ret; } return 0; } static int anx7625_reg_write(uint8_t bus, uint8_t saddr, uint8_t reg_addr, uint8_t reg_val) { int ret; i2c_access_workaround(bus, saddr); ret = i2c_writeb(bus, saddr, reg_addr, reg_val); if (ret < 0) { ANXERROR("Failed to write i2c id=%#x:%#x\n", saddr, reg_addr); return ret; } return 0; } static int anx7625_write_or(uint8_t bus, uint8_t saddr, uint8_t offset, uint8_t mask) { uint8_t val; int ret; ret = anx7625_reg_read(bus, saddr, offset, &val); if (ret < 0) return ret; return anx7625_reg_write(bus, saddr, offset, val | mask); } static int anx7625_write_and(uint8_t bus, uint8_t saddr, uint8_t offset, uint8_t mask) { int ret; uint8_t val; ret = anx7625_reg_read(bus, saddr, offset, &val); if (ret < 0) return ret; return anx7625_reg_write(bus, saddr, offset, val & mask); } static int wait_aux_op_finish(uint8_t bus) { uint8_t val; int ret; if (!retry(150, (anx7625_reg_read(bus, RX_P0_ADDR, AP_AUX_CTRL_STATUS, &val), !(val & AP_AUX_CTRL_OP_EN)), mdelay(2))) { ANXERROR("Timed out waiting aux operation.\n"); return -1; } ret = anx7625_reg_read(bus, RX_P0_ADDR, AP_AUX_CTRL_STATUS, &val); if (ret < 0 || val & 0x0F) { ANXDEBUG("aux status %02x\n", val); return -1; } return 0; } /* Reduce fraction a/b */ static void anx7625_reduction_of_a_fraction(u32 *_a, u32 *_b) { u32 gcd_num; u32 a = *_a, b = *_b, old_a, old_b; u32 denom = 1; gcd_num = gcd(a, b); a /= gcd_num; b /= gcd_num; old_a = a; old_b = b; while (a > MAX_UNSIGNED_24BIT || b > MAX_UNSIGNED_24BIT) { denom++; a = old_a / denom; b = old_b / denom; } /* Increase a, b to have higher ODFC PLL output frequency accuracy. */ while ((a << 1) < MAX_UNSIGNED_24BIT && (b << 1) < MAX_UNSIGNED_24BIT) { a <<= 1; b <<= 1; } *_a = a; *_b = b; } static int anx7625_calculate_m_n(u32 pixelclock, u32 *m, u32 *n, uint8_t *pd) { uint8_t post_divider = *pd; if (pixelclock > PLL_OUT_FREQ_ABS_MAX / POST_DIVIDER_MIN) { /* pixel clock frequency is too high */ ANXERROR("pixelclock %u higher than %lu, " "output may be unstable\n", pixelclock, PLL_OUT_FREQ_ABS_MAX / POST_DIVIDER_MIN); return -1; } if (pixelclock < PLL_OUT_FREQ_ABS_MIN / POST_DIVIDER_MAX) { /* pixel clock frequency is too low */ ANXERROR("pixelclock %u lower than %lu, " "output may be unstable\n", pixelclock, PLL_OUT_FREQ_ABS_MIN / POST_DIVIDER_MAX); return -1; } post_divider = 1; for (post_divider = 1; pixelclock < PLL_OUT_FREQ_MIN / post_divider; post_divider++) ; if (post_divider > POST_DIVIDER_MAX) { for (post_divider = 1; pixelclock < PLL_OUT_FREQ_ABS_MIN / post_divider; post_divider++) ; if (post_divider > POST_DIVIDER_MAX) { ANXERROR("cannot find property post_divider(%d)\n", post_divider); return -1; } } /* Patch to improve the accuracy */ if (post_divider == 7) { /* 27,000,000 is not divisible by 7 */ post_divider = 8; } else if (post_divider == 11) { /* 27,000,000 is not divisible by 11 */ post_divider = 12; } else if (post_divider == 13 || post_divider == 14) { /*27,000,000 is not divisible by 13 or 14*/ post_divider = 15; } if (pixelclock * post_divider > PLL_OUT_FREQ_ABS_MAX) { ANXINFO("act clock(%u) large than maximum(%lu)\n", pixelclock * post_divider, PLL_OUT_FREQ_ABS_MAX); return -1; } *m = pixelclock; *n = XTAL_FRQ / post_divider; *pd = post_divider; anx7625_reduction_of_a_fraction(m, n); return 0; } static int anx7625_odfc_config(uint8_t bus, uint8_t post_divider) { int ret; /* config input reference clock frequency 27MHz/19.2MHz */ ret = anx7625_write_and(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_16, ~(REF_CLK_27000kHz << MIPI_FREF_D_IND)); ret |= anx7625_write_or(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_16, (REF_CLK_27000kHz << MIPI_FREF_D_IND)); /* post divider */ ret |= anx7625_write_and(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_8, 0x0f); ret |= anx7625_write_or(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_8, post_divider << 4); /* add patch for MIS2-125 (5pcs ANX7625 fail ATE MBIST test) */ ret |= anx7625_write_and(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_7, ~MIPI_PLL_VCO_TUNE_REG_VAL); /* reset ODFC PLL */ ret |= anx7625_write_and(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_7, ~MIPI_PLL_RESET_N); ret |= anx7625_write_or(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_7, MIPI_PLL_RESET_N); if (ret < 0) { ANXERROR("IO error.\n"); return ret; } return 0; } static int anx7625_dsi_video_config(uint8_t bus, struct display_timing *dt) { u32 m, n; u16 htotal; int ret; uint8_t post_divider = 0; if (anx7625_calculate_m_n(dt->pixelclock * 1000, &m, &n, &post_divider) < 0) { ANXERROR("cannot get property m n value.\n"); return -1; } ANXINFO("compute M(%u), N(%u), divider(%d).\n", m, n, post_divider); /* configure pixel clock */ ret = anx7625_reg_write(bus, RX_P0_ADDR, PIXEL_CLOCK_L, (dt->pixelclock / 1000) & 0xFF); ret |= anx7625_reg_write(bus, RX_P0_ADDR, PIXEL_CLOCK_H, (dt->pixelclock / 1000) >> 8); /* lane count */ ret |= anx7625_write_and(bus, RX_P1_ADDR, MIPI_LANE_CTRL_0, 0xfc); ret |= anx7625_write_or(bus, RX_P1_ADDR, MIPI_LANE_CTRL_0, 3); /* Htotal */ htotal = dt->hactive + dt->hfront_porch + dt->hback_porch + dt->hsync_len; ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_TOTAL_PIXELS_L, htotal & 0xFF); ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_TOTAL_PIXELS_H, htotal >> 8); /* Hactive */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_ACTIVE_PIXELS_L, dt->hactive & 0xFF); ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_ACTIVE_PIXELS_H, dt->hactive >> 8); /* HFP */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_FRONT_PORCH_L, dt->hfront_porch); ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_FRONT_PORCH_H, dt->hfront_porch >> 8); /* HWS */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_SYNC_WIDTH_L, dt->hsync_len); ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_SYNC_WIDTH_H, dt->hsync_len >> 8); /* HBP */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_BACK_PORCH_L, dt->hback_porch); ret |= anx7625_reg_write(bus, RX_P2_ADDR, HORIZONTAL_BACK_PORCH_H, dt->hback_porch >> 8); /* Vactive */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, ACTIVE_LINES_L, dt->vactive); ret |= anx7625_reg_write(bus, RX_P2_ADDR, ACTIVE_LINES_H, dt->vactive >> 8); /* VFP */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, VERTICAL_FRONT_PORCH, dt->vfront_porch); /* VWS */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, VERTICAL_SYNC_WIDTH, dt->vsync_len); /* VBP */ ret |= anx7625_reg_write(bus, RX_P2_ADDR, VERTICAL_BACK_PORCH, dt->vback_porch); /* M value */ ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PLL_M_NUM_23_16, (m >> 16) & 0xff); ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PLL_M_NUM_15_8, (m >> 8) & 0xff); ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PLL_M_NUM_7_0, (m & 0xff)); /* N value */ ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PLL_N_NUM_23_16, (n >> 16) & 0xff); ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PLL_N_NUM_15_8, (n >> 8) & 0xff); ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PLL_N_NUM_7_0, (n & 0xff)); /* diff */ ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_DIGITAL_ADJ_1, dt->k_val); ret |= anx7625_odfc_config(bus, post_divider - 1); if (ret < 0) { ANXERROR("mipi dsi setup IO error.\n"); return ret; } return 0; } static int anx7625_swap_dsi_lane3(uint8_t bus) { int ret; uint8_t val; /* swap MIPI-DSI data lane 3 P and N */ ret = anx7625_reg_read(bus, RX_P1_ADDR, MIPI_SWAP, &val); if (ret < 0) { ANXERROR("IO error: access MIPI_SWAP.\n"); return ret; } val |= (1 << MIPI_SWAP_CH3); return anx7625_reg_write(bus, RX_P1_ADDR, MIPI_SWAP, val); } static int anx7625_api_dsi_config(uint8_t bus, struct display_timing *dt) { int val, ret; /* swap MIPI-DSI data lane 3 P and N */ ret = anx7625_swap_dsi_lane3(bus); if (ret < 0) { ANXERROR("IO error: swap dsi lane 3 failed.\n"); return ret; } /* DSI clock settings */ val = (0 << MIPI_HS_PWD_CLK) | (0 << MIPI_HS_RT_CLK) | (0 << MIPI_PD_CLK) | (1 << MIPI_CLK_RT_MANUAL_PD_EN) | (1 << MIPI_CLK_HS_MANUAL_PD_EN) | (0 << MIPI_CLK_DET_DET_BYPASS) | (0 << MIPI_CLK_MISS_CTRL) | (0 << MIPI_PD_LPTX_CH_MANUAL_PD_EN); ret = anx7625_reg_write(bus, RX_P1_ADDR, MIPI_PHY_CONTROL_3, val); /* * Decreased HS prepare tg delay from 160ns to 80ns work with * a) Dragon board 810 series (Qualcomm AP) * b) Moving Pixel DSI source (PG3A pattern generator + * P332 D-PHY Probe) default D-PHY tg 5ns/step */ ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_TIME_HS_PRPR, 0x10); /* enable DSI mode */ ret |= anx7625_write_or(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_18, SELECT_DSI << MIPI_DPI_SELECT); ret |= anx7625_dsi_video_config(bus, dt); if (ret < 0) { ANXERROR("dsi video tg config failed\n"); return ret; } /* toggle m, n ready */ ret = anx7625_write_and(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_6, ~(MIPI_M_NUM_READY | MIPI_N_NUM_READY)); mdelay(1); ret |= anx7625_write_or(bus, RX_P1_ADDR, MIPI_DIGITAL_PLL_6, MIPI_M_NUM_READY | MIPI_N_NUM_READY); /* configure integer stable register */ ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_VIDEO_STABLE_CNT, 0x02); /* power on MIPI RX */ ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_LANE_CTRL_10, 0x00); ret |= anx7625_reg_write(bus, RX_P1_ADDR, MIPI_LANE_CTRL_10, 0x80); if (ret < 0) { ANXERROR("IO error: mipi dsi enable init failed.\n"); return ret; } return 0; } static int anx7625_dsi_config(uint8_t bus, struct display_timing *dt) { int ret; ANXINFO("config dsi.\n"); /* DSC disable */ ret = anx7625_write_and(bus, RX_P0_ADDR, R_DSC_CTRL_0, ~DSC_EN); ret |= anx7625_api_dsi_config(bus, dt); if (ret < 0) { ANXERROR("IO error: api dsi config error.\n"); return ret; } /* set MIPI RX EN */ ret = anx7625_write_or(bus, RX_P0_ADDR, AP_AV_STATUS, AP_MIPI_RX_EN); /* clear mute flag */ ret |= anx7625_write_and(bus, RX_P0_ADDR, AP_AV_STATUS, ~AP_MIPI_MUTE); if (ret < 0) { ANXERROR("IO error: enable mipi rx failed.\n"); return ret; } ANXINFO("success to config DSI\n"); return 0; } static int sp_tx_rst_aux(uint8_t bus) { int ret; ret = anx7625_write_or(bus, TX_P2_ADDR, RST_CTRL2, AUX_RST); ret |= anx7625_write_and(bus, TX_P2_ADDR, RST_CTRL2, ~AUX_RST); return ret; } static int sp_tx_aux_wr(uint8_t bus, uint8_t offset) { int ret; ret = anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_BUFF_START, offset); ret |= anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_COMMAND, 0x04); ret |= anx7625_write_or(bus, RX_P0_ADDR, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN); return ret | wait_aux_op_finish(bus); } static int sp_tx_aux_rd(uint8_t bus, uint8_t len_cmd) { int ret; ret = anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_COMMAND, len_cmd); ret |= anx7625_write_or(bus, RX_P0_ADDR, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN); return ret | wait_aux_op_finish(bus); } static int sp_tx_get_edid_block(uint8_t bus) { int ret; uint8_t val = 0; sp_tx_aux_wr(bus, 0x7e); sp_tx_aux_rd(bus, 0x01); ret = anx7625_reg_read(bus, RX_P0_ADDR, AP_AUX_BUFF_START, &val); if (ret < 0) { ANXERROR("IO error: access AUX BUFF.\n"); return -1; } ANXINFO("EDID Block = %d\n", val + 1); if (val > 3) val = 1; return val; } static int edid_read(uint8_t bus, uint8_t offset, uint8_t *pblock_buf) { int ret, cnt; for (cnt = 0; cnt < 3; cnt++) { sp_tx_aux_wr(bus, offset); /* set I2C read com 0x01 mot = 0 and read 16 bytes */ ret = sp_tx_aux_rd(bus, 0xf1); if (ret < 0) { sp_tx_rst_aux(bus); ANXERROR("edid read failed, reset!\n"); } else { if (anx7625_reg_block_read(bus, RX_P0_ADDR, AP_AUX_BUFF_START, MAX_DPCD_BUFFER_SIZE, pblock_buf) >= 0) return 0; } } return -1; } static int segments_edid_read(uint8_t bus, uint8_t segment, uint8_t *buf, uint8_t offset) { int ret, cnt; /* write address only */ ret = anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_ADDR_7_0, 0x30); ret |= anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_COMMAND, 0x04); ret |= anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_ADDRONLY | AP_AUX_CTRL_OP_EN); ret |= wait_aux_op_finish(bus); /* write segment address */ ret |= sp_tx_aux_wr(bus, segment); /* data read */ ret |= anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_ADDR_7_0, 0x50); if (ret < 0) { ANXERROR("IO error: aux initial failed.\n"); return ret; } for (cnt = 0; cnt < 3; cnt++) { sp_tx_aux_wr(bus, offset); /* set I2C read com 0x01 mot = 0 and read 16 bytes */ ret = sp_tx_aux_rd(bus, 0xf1); if (ret < 0) { sp_tx_rst_aux(bus); ANXERROR("segment read failed, reset!\n"); } else { if (anx7625_reg_block_read(bus, RX_P0_ADDR, AP_AUX_BUFF_START, MAX_DPCD_BUFFER_SIZE, buf) >= 0) return 0; } } return -1; } static int sp_tx_edid_read(uint8_t bus, uint8_t *pedid_blocks_buf, uint32_t size) { uint8_t offset, edid_pos; int count, blocks_num; uint8_t pblock_buf[MAX_DPCD_BUFFER_SIZE]; int i, ret, g_edid_break = 0; /* address initial */ ret = anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_ADDR_7_0, 0x50); ret |= anx7625_reg_write(bus, RX_P0_ADDR, AP_AUX_ADDR_15_8, 0); ret |= anx7625_write_and(bus, RX_P0_ADDR, AP_AUX_ADDR_19_16, 0xf0); if (ret < 0) { ANXERROR("access aux channel IO error.\n"); return -1; } blocks_num = sp_tx_get_edid_block(bus); if (blocks_num < 0) return -1; count = 0; do { switch (count) { case 0: case 1: for (i = 0; i < 8; i++) { offset = (i + count * 8) * MAX_DPCD_BUFFER_SIZE; g_edid_break = !!edid_read(bus, offset, pblock_buf); if (g_edid_break) break; if (offset <= size - MAX_DPCD_BUFFER_SIZE) memcpy(&pedid_blocks_buf[offset], pblock_buf, MAX_DPCD_BUFFER_SIZE); } break; case 2: case 3: offset = (count == 2) ? 0x00 : 0x80; for (i = 0; i < 8; i++) { edid_pos = (i + count * 8) * MAX_DPCD_BUFFER_SIZE; if (g_edid_break) break; segments_edid_read(bus, count / 2, pblock_buf, offset); if (edid_pos <= size - MAX_DPCD_BUFFER_SIZE) memcpy(&pedid_blocks_buf[edid_pos], pblock_buf, MAX_DPCD_BUFFER_SIZE); offset = offset + 0x10; } break; default: die("%s: count should be <= 3", __func__); break; } count++; } while (blocks_num >= count); /* reset aux channel */ sp_tx_rst_aux(bus); return blocks_num; } static void anx7625_disable_pd_protocol(uint8_t bus) { int ret; /* reset main ocm */ ret = anx7625_reg_write(bus, RX_P0_ADDR, 0x88, 0x40); /* Disable PD */ ret |= anx7625_reg_write(bus, RX_P0_ADDR, AP_AV_STATUS, AP_DISABLE_PD); /* release main ocm */ ret |= anx7625_reg_write(bus, RX_P0_ADDR, 0x88, 0x00); if (ret < 0) ANXERROR("Failed to disable PD feature.\n"); else ANXINFO("Disabled PD feature.\n"); } #define FLASH_LOAD_STA 0x05 #define FLASH_LOAD_STA_CHK (1 << 7) static int anx7625_power_on_init(uint8_t bus) { int i, ret; uint8_t val, version, revision; anx7625_reg_write(bus, RX_P0_ADDR, XTAL_FRQ_SEL, XTAL_FRQ_27M); for (i = 0; i < OCM_LOADING_TIME; i++) { /* check interface */ ret = anx7625_reg_read(bus, RX_P0_ADDR, FLASH_LOAD_STA, &val); if (ret < 0) { ANXERROR("Failed to load flash\n"); return ret; } if ((val & FLASH_LOAD_STA_CHK) != FLASH_LOAD_STA_CHK) { mdelay(1); continue; } ANXINFO("Init interface.\n"); anx7625_disable_pd_protocol(bus); anx7625_reg_read(bus, RX_P0_ADDR, OCM_FW_VERSION, &version); anx7625_reg_read(bus, RX_P0_ADDR, OCM_FW_REVERSION, &revision); ANXINFO("Firmware: ver %#02x, rev %#02x.\n", version, revision); return 0; } return -1; } static void anx7625_start_dp_work(uint8_t bus) { int ret; uint8_t val; /* not support HDCP */ ret = anx7625_write_and(bus, RX_P1_ADDR, 0xee, 0x9f); /* try auth flag */ ret |= anx7625_write_or(bus, RX_P1_ADDR, 0xec, 0x10); /* interrupt for DRM */ ret |= anx7625_write_or(bus, RX_P1_ADDR, 0xff, 0x01); if (ret < 0) return; ret = anx7625_reg_read(bus, RX_P1_ADDR, 0x86, &val); if (ret < 0) return; ANXINFO("Secure OCM version=%02x\n", val); } static int anx7625_hpd_change_detect(uint8_t bus) { int ret; uint8_t status; ret = anx7625_reg_read(bus, RX_P0_ADDR, SYSTEM_STSTUS, &status); if (ret < 0) { ANXERROR("IO error: Failed to clear interrupt status.\n"); return ret; } if (status & HPD_STATUS) { anx7625_start_dp_work(bus); ANXINFO("HPD received 0x7e:0x45=%#x\n", status); return 1; } return 0; } static void anx7625_parse_edid(const struct edid *edid, struct display_timing *dt) { dt->pixelclock = edid->mode.pixel_clock; dt->hactive = edid->mode.ha; dt->hsync_len = edid->mode.hspw; dt->hback_porch = (edid->mode.hbl - edid->mode.hso - edid->mode.hborder - edid->mode.hspw); dt->hfront_porch = edid->mode.hso - edid->mode.hborder; dt->vactive = edid->mode.va; dt->vsync_len = edid->mode.vspw; dt->vfront_porch = edid->mode.vso - edid->mode.vborder; dt->vback_porch = (edid->mode.vbl - edid->mode.vso - edid->mode.vspw - edid->mode.vborder); /* * The k_val is a ratio to match MIPI input and DP output video clocks. * Most panels can follow the default value (0x3d). * IVO panels have smaller variation than DP CTS spec and need smaller * k_val (0x3b). */ if (!strncmp(edid->manufacturer_name, "IVO", 3)) { dt->k_val = 0x3b; ANXINFO("detected IVO panel, use k value 0x3b\n"); } else { dt->k_val = 0x3d; ANXINFO("set default k value to 0x3d for panel\n"); } ANXINFO("pixelclock(%d).\n" " hactive(%d), hsync(%d), hfp(%d), hbp(%d)\n" " vactive(%d), vsync(%d), vfp(%d), vbp(%d)\n", dt->pixelclock, dt->hactive, dt->hsync_len, dt->hfront_porch, dt->hback_porch, dt->vactive, dt->vsync_len, dt->vfront_porch, dt->vback_porch); } int anx7625_dp_start(uint8_t bus, const struct edid *edid) { int ret; struct display_timing dt; anx7625_parse_edid(edid, &dt); ret = anx7625_dsi_config(bus, &dt); if (ret < 0) { ANXERROR("MIPI phy setup error.\n"); return ret; } ANXINFO("MIPI phy setup OK.\n"); return 0; } int anx7625_dp_get_edid(uint8_t bus, struct edid *out) { int block_num; int ret; u8 edid[FOUR_BLOCK_SIZE]; block_num = sp_tx_edid_read(bus, edid, FOUR_BLOCK_SIZE); if (block_num < 0) { ANXERROR("Failed to get eDP EDID.\n"); return -1; } ret = decode_edid(edid, (block_num + 1) * ONE_BLOCK_SIZE, out); if (ret != EDID_CONFORMANT) { ANXERROR("Failed to decode EDID.\n"); return -1; } return 0; } int anx7625_init(uint8_t bus) { int retry_hpd_change = 50; if (!retry(3, anx7625_power_on_init(bus) >= 0)) { ANXERROR("Failed to power on.\n"); return -1; } while (--retry_hpd_change) { mdelay(10); int detected = anx7625_hpd_change_detect(bus); if (detected < 0) return -1; if (detected > 0) return 0; } ANXERROR("Timed out to detect HPD change on bus %d.\n", bus); return -1; }