| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <console/console.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; |
| } |
| |
| static unsigned long gcd(unsigned long a, unsigned long b) |
| { |
| if (a == 0) |
| return b; |
| |
| while (b != 0) { |
| if (a > b) |
| a = a - b; |
| else |
| b = b - a; |
| } |
| |
| return a; |
| } |
| |
| /* Reduce fraction a/b */ |
| static void anx7625_reduction_of_a_fraction(unsigned long *_a, |
| unsigned long *_b) |
| { |
| unsigned long gcd_num; |
| unsigned long 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, |
| unsigned long *m, unsigned long *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) |
| { |
| unsigned long 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(%lu), N(%lu), 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; |
| } |