| // SPDX-License-Identifier: (GPL-2.0+ OR MIT) |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <config.h> |
| #include <linux/kernel.h> |
| #ifdef CONFIG_SECURE_POWER_CONTROL |
| #include <asm/arch/pwr_ctrl.h> |
| #endif |
| #include <amlogic/media/vout/lcd/aml_lcd.h> |
| #include "../lcd_reg.h" |
| #include "../lcd_common.h" |
| |
| #define EDP_TX_AUX_REQ_TIMEOUT 1000 |
| #define EDP_TX_AUX_REQ_INTERVAL 1 |
| #define EDP_AUX_RETRY_CNT 5 |
| #define EDP_AUX_TIMEOUT 1000 |
| #define EDP_AUX_INTERVAL 200 |
| static int dptx_aux_write(int index, unsigned int addr, unsigned int len, unsigned char *buf) |
| { |
| unsigned int data, i, state; |
| unsigned int retry_cnt = 0, timeout = 0; |
| |
| dptx_aux_write_retry: |
| timeout = 0; |
| while (timeout++ < EDP_TX_AUX_REQ_TIMEOUT) { /* wait REQUEST_IN_PROGRESS */ |
| state = dptx_reg_getb(index, EDP_TX_AUX_STATE, 1, 1); |
| if (state == 0) |
| break; |
| udelay(EDP_TX_AUX_REQ_INTERVAL); |
| }; |
| |
| dptx_reg_write(index, EDP_TX_AUX_ADDRESS, addr); |
| for (i = 0; i < len; i++) |
| dptx_reg_write(index, EDP_TX_AUX_WRITE_FIFO, buf[i]); |
| |
| dptx_reg_write(index, EDP_TX_AUX_COMMAND, (0x800 | ((len - 1) & 0xf))); |
| |
| timeout = 0; |
| while (timeout++ < EDP_AUX_TIMEOUT) { |
| udelay(EDP_AUX_INTERVAL); |
| data = dptx_reg_read(index, EDP_TX_AUX_TRANSFER_STATUS); |
| if (data & (1 << 0)) { |
| state = dptx_reg_read(index, EDP_TX_AUX_REPLY_CODE); |
| if (state == 0) |
| return 0; |
| if (state == 1) { |
| LCDPR("[%d]: edp aux write addr 0x%x NACK!\n", |
| index, addr); |
| return -1; |
| } |
| if (state == 2) { |
| LCDPR("[%d]: edp aux write addr 0x%x Defer!\n", |
| index, addr); |
| } |
| break; |
| } |
| |
| if (data & (1 << 3)) { |
| LCDPR("[%d]: edp aux write addr 0x%x Error!\n", index, addr); |
| break; |
| } |
| } |
| |
| if (retry_cnt++ < EDP_AUX_RETRY_CNT) { |
| udelay(EDP_AUX_INTERVAL); |
| LCDPR("[%d]: edp aux write addr 0x%x timeout, retry %d\n", |
| index, addr, retry_cnt); |
| goto dptx_aux_write_retry; |
| } |
| |
| LCDPR("[%d]: edp aux write addr 0x%x failed\n", index, addr); |
| return -1; |
| } |
| |
| static int dptx_aux_read(int index, unsigned int addr, unsigned int len, unsigned char *buf) |
| { |
| unsigned int data, i, state; |
| unsigned int retry_cnt = 0, timeout = 0; |
| |
| dptx_aux_read_retry: |
| timeout = 0; |
| while (timeout++ < EDP_TX_AUX_REQ_TIMEOUT) { /* wait REQUEST_IN_PROGRESS */ |
| state = dptx_reg_getb(index, EDP_TX_AUX_STATE, 1, 1); |
| if (state == 0) |
| break; |
| udelay(EDP_TX_AUX_REQ_INTERVAL); |
| }; |
| |
| dptx_reg_write(index, EDP_TX_AUX_ADDRESS, addr); |
| dptx_reg_write(index, EDP_TX_AUX_COMMAND, (0x900 | ((len - 1) & 0xf))); |
| |
| timeout = 0; |
| while (timeout++ < EDP_AUX_TIMEOUT) { |
| udelay(EDP_AUX_INTERVAL); |
| data = dptx_reg_read(index, EDP_TX_AUX_TRANSFER_STATUS); |
| if (data & (1 << 0)) { |
| state = dptx_reg_read(index, EDP_TX_AUX_REPLY_CODE); |
| if (state == 0) |
| goto dptx_aux_read_succeed; |
| if (state == 1) { |
| LCDPR("[%d]: edp aux read addr 0x%x NACK!\n", |
| index, addr); |
| return -1; |
| } |
| if (state == 2) { |
| LCDPR("[%d]: edp aux read addr 0x%x Defer!\n", |
| index, addr); |
| } |
| break; |
| } |
| |
| if (data & (1 << 3)) { |
| LCDPR("[%d]: edp aux read addr 0x%x Error!\n", index, addr); |
| break; |
| } |
| } |
| |
| if (retry_cnt++ < EDP_AUX_RETRY_CNT) { |
| udelay(EDP_AUX_INTERVAL); |
| LCDPR("[%d]: edp aux read addr 0x%x timeout, retry %d\n", |
| index, addr, retry_cnt); |
| goto dptx_aux_read_retry; |
| } |
| |
| LCDPR("[%d]: edp aux read addr 0x%x failed\n", index, addr); |
| return -1; |
| |
| dptx_aux_read_succeed: |
| for (i = 0; i < len; i++) |
| buf[i] = (unsigned char)(dptx_reg_read(index, EDP_TX_AUX_REPLY_DATA)); |
| |
| return 0; |
| } |
| |
| static void dptx_link_fast_training(int index) |
| { |
| unsigned char p_data = 0; |
| int ret; |
| |
| // disable scrambling |
| dptx_reg_write(index, EDP_TX_SCRAMBLING_DISABLE ,0x1); |
| |
| // set training pattern 1 |
| dptx_reg_write(index, EDP_TX_TRAINING_PATTERN_SET, 0x1); |
| p_data = 0x21; |
| ret = dptx_aux_write(index, EDP_DPCD_TRAINING_PATTERN_SET, 1, &p_data); |
| if (ret) |
| LCDERR("[%d]: edp training pattern 1 failed.....\n", index); |
| udelay(10); |
| |
| // set training pattern 2 |
| dptx_reg_write(index, EDP_TX_TRAINING_PATTERN_SET, 0x2); |
| p_data = 0x22; |
| ret = dptx_aux_write(index, EDP_DPCD_TRAINING_PATTERN_SET, 1, &p_data); |
| if (ret) |
| LCDERR("[%d]: edp training pattern 2 failed.....\n", index); |
| udelay(10); |
| |
| // set training pattern 3 |
| dptx_reg_write(index, EDP_TX_TRAINING_PATTERN_SET, 0x3); |
| p_data = 0x23; |
| ret = dptx_aux_write(index, EDP_DPCD_TRAINING_PATTERN_SET, 1, &p_data); |
| if (ret) |
| LCDERR("[%d]: edp training pattern 3 failed.....\n", index); |
| udelay(10); |
| |
| // disable the training pattern |
| p_data = 0x20; |
| ret = dptx_aux_write(index, EDP_DPCD_TRAINING_PATTERN_SET, 1, &p_data); |
| if (ret) |
| LCDERR("[%d]: edp training pattern off failed.....\n", index); |
| dptx_reg_write(index, EDP_TX_TRAINING_PATTERN_SET, 0x0); |
| } |
| |
| void dptx_dpcd_dump(struct aml_lcd_drv_s *pdrv) |
| { |
| int index; |
| unsigned char p_data[12]; |
| int ret, i; |
| |
| index = pdrv->index; |
| if (index > 1) { |
| LCDERR("[%d]: %s: invalid drv_index\n", index, __func__); |
| return; |
| } |
| |
| memset(p_data, 0, 12); |
| LCDPR("[%d]: edp DPCD link status:\n", index); |
| ret = dptx_aux_read(index, 0x100, 8, p_data); |
| if (ret == 0) { |
| for (i = 0; i < 8; i++) |
| printf("0x%04x: 0x%02x\n", (0x100 + i), p_data[i]); |
| printf("\n"); |
| } |
| |
| memset(p_data, 0, 12); |
| LCDPR("[%d]: edp DPCD training status:\n", index); |
| ret = dptx_aux_read(index, 0x200, 12, p_data); |
| if (ret == 0) { |
| for (i = 0; i < 12; i++) |
| printf("0x%04x: 0x%02x\n", (0x200 + i), p_data[i]); |
| printf("\n"); |
| } |
| } |
| |
| static void dptx_set_msa(int index, struct lcd_config_s *pconf) |
| { |
| unsigned int hactive = pconf->basic.h_active; |
| unsigned int vactive = pconf->basic.v_active; |
| unsigned int htotal = pconf->basic.h_period; |
| unsigned int vtotal = pconf->basic.v_period; |
| unsigned int hsw = pconf->timing.hsync_width; |
| unsigned int hbp = pconf->timing.hsync_bp; |
| unsigned int vsw = pconf->timing.vsync_width; |
| unsigned int vbp = pconf->timing.vsync_bp; |
| unsigned int bpc = pconf->basic.lcd_bits; // bits per color |
| unsigned int data_per_lane, misc0_data, bit_depth, sync_mode; |
| unsigned int m_vid; //pclk/1000 |
| unsigned int n_vid; //162000, 270000, 540000 |
| unsigned int ppc = 1; // 1 pix per clock pix0 only |
| unsigned int cfmt = 0; // RGB |
| |
| m_vid = pconf->timing.lcd_clk / 1000; |
| switch (pconf->control.edp_cfg.link_rate) { |
| case 1: /* 2.7G */ |
| n_vid = 270000; |
| break; |
| case 0: /* 1.62G */ |
| default: |
| n_vid = 162000; |
| break; |
| } |
| //6bit:0x0, 8bit:0x1, 10bit:0x2, 12bit:0x3 |
| switch (pconf->basic.lcd_bits) { |
| case 6: |
| bit_depth = 0x0; |
| break; |
| case 8: |
| bit_depth = 0x1; |
| break; |
| case 10: |
| bit_depth = 0x2; |
| break; |
| default: |
| bit_depth = 0x7; |
| break; |
| } |
| sync_mode = pconf->control.edp_cfg.sync_clk_mode; |
| data_per_lane = ((hactive * bpc * 3) + 15) / 16 - 1; |
| |
| //bit[0] sync mode (1=sync 0=async) |
| misc0_data = (cfmt << 1) | (sync_mode << 0); |
| misc0_data |= (bit_depth << 5); |
| |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_HTOTAL, htotal); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_VTOTAL, vtotal); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_POLARITY, (0 << 1) | (0 << 0)); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_HSWIDTH, hsw); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_VSWIDTH, vsw); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_HRES, hactive); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_VRES, vactive); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_HSTART, (hsw + hbp)); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_VSTART, (vsw + vbp)); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_MISC0, misc0_data); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_MISC1, 0x00000000); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_M_VID, m_vid); //unit: 1kHz |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_N_VID, n_vid); //unit: 10kHz |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_TRANSFER_UNIT_SIZE, 32); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_DATA_COUNT_PER_LANE, data_per_lane); |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_USER_PIXEL_WIDTH, ppc); |
| } |
| |
| static void dptx_reset(int index) |
| { |
| unsigned int bit; |
| |
| if (index) |
| bit = 18; |
| else |
| bit = 17; |
| |
| lcd_reset_setb(RESETCTRL_RESET1_MASK, 0, bit, 1); |
| lcd_reset_setb(RESETCTRL_RESET1_LEVEL, 0, bit, 1); |
| udelay(1); |
| lcd_reset_setb(RESETCTRL_RESET1_LEVEL, 1, bit, 1); |
| udelay(1); |
| } |
| |
| static void dptx_phy_reset(int index) |
| { |
| unsigned int bit; |
| |
| if (index) |
| bit = 20; |
| else |
| bit = 19; |
| |
| lcd_reset_setb(RESETCTRL_RESET1_MASK, 0, bit, 1); |
| lcd_reset_setb(RESETCTRL_RESET1_LEVEL, 0, bit, 1); |
| udelay(1); |
| lcd_reset_setb(RESETCTRL_RESET1_LEVEL, 1, bit, 1); |
| udelay(1); |
| } |
| |
| static int dptx_wait_phy_ready(int index) |
| { |
| unsigned int data = 0; |
| unsigned int done = 100; |
| |
| do { |
| data = dptx_reg_read(index, EDP_TX_PHY_STATUS); |
| if (done < 20) { |
| LCDPR("[%d]: dptx wait phy ready: reg_val=0x%x, wait_count=%u\n", |
| index, data, (100 - done)); |
| } |
| done--; |
| udelay(100); |
| }while(((data & 0x7f) != 0x7f) && (done > 0)); |
| |
| if ((data & 0x7f) == 0x7f) |
| return 0; |
| |
| LCDERR("[%d]: edp tx phy error!\n", index); |
| return -1; |
| } |
| |
| #define EDP_HPD_TIMEOUT 1000 |
| static void edp_tx_init(struct aml_lcd_drv_s *pdrv) |
| { |
| unsigned int hpd_state = 0; |
| unsigned char auxdata[2]; |
| unsigned int offset; |
| int i, index, ret; |
| |
| index = pdrv->index; |
| if (index > 1) { |
| LCDERR("[%d]: %s: invalid drv_index\n", index, __func__); |
| return; |
| } |
| |
| offset = pdrv->data->offset_venc_data[pdrv->index]; |
| |
| dptx_phy_reset(index); |
| dptx_reset(index); |
| mdelay(2); |
| |
| lcd_vcbus_write(ENCL_VIDEO_EN + offset, 0); |
| |
| // Set Aux channel clk-div: 24MHz |
| dptx_reg_write(index, EDP_TX_AUX_CLOCK_DIVIDER, 24); |
| |
| // Enable the transmitter |
| // remove the reset on the PHY |
| dptx_reg_write(index, EDP_TX_PHY_RESET, 0); |
| dptx_wait_phy_ready(index); |
| mdelay(2); |
| dptx_reg_write(index, EDP_TX_TRANSMITTER_OUTPUT_ENABLE, 0x1); |
| |
| i = 0; |
| while (i++ < EDP_HPD_TIMEOUT) { |
| hpd_state = dptx_reg_getb(index, EDP_TX_AUX_STATE, 0, 1); |
| if (hpd_state) |
| break; |
| mdelay(2); |
| } |
| LCDPR("[%d]: edp HPD state: %d, i=%d\n", index, hpd_state, i); |
| |
| // tx Link-rate and Lane_count |
| dptx_reg_write(index, EDP_TX_LINK_BW_SET, 0x0a); // Link-rate |
| dptx_reg_write(index, EDP_TX_LINK_COUNT_SET, 0x02); // Number of Lanes |
| |
| // sink Link-rate and Lane_count |
| auxdata[0] = 0x0a; // 2.7GHz //EDP_DPCD_LINK_BANDWIDTH_SET |
| auxdata[1] = 2; //EDP_DPCD_LANE_COUNT_SET |
| ret = dptx_aux_write(index, EDP_DPCD_LINK_BANDWIDTH_SET, 2, auxdata); |
| if (ret) |
| LCDERR("[%d]: edp sink set lane rate & count failed.....\n", index); |
| |
| // Power up link |
| auxdata[0] = 0x1; |
| ret = dptx_aux_write(index, EDP_DPCD_SET_POWER, 1, auxdata); |
| if (ret) |
| LCDERR("[%d]: edp sink power up link failed.....\n", index); |
| |
| dptx_link_fast_training(index); |
| //dptx_dpcd_dump(pdrv); |
| |
| dptx_set_msa(index, &pdrv->config); |
| lcd_vcbus_write(ENCL_VIDEO_EN + offset, 1); |
| |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_ENABLE, 0x1); |
| LCDPR("[%d]: edp enable main stream video\n", index); |
| } |
| |
| static void edp_tx_disable(struct aml_lcd_drv_s *pdrv) |
| { |
| unsigned char auxdata; |
| int index, ret; |
| |
| index = pdrv->index; |
| if (index > 1) { |
| LCDERR("[%d]: %s: invalid drv_index\n", index, __func__); |
| return; |
| } |
| |
| // Power down link |
| auxdata = 0x2; |
| ret = dptx_aux_write(index, EDP_DPCD_SET_POWER, 1, &auxdata); |
| if (ret) |
| LCDERR("[%d]: edp sink power down link failed.....\n", index); |
| |
| dptx_reg_write(index, EDP_TX_MAIN_STREAM_ENABLE, 0x0); |
| LCDPR("[%d]: edp disable main stream video\n", index); |
| |
| // disable the transmitter |
| dptx_reg_write(index, EDP_TX_TRANSMITTER_OUTPUT_ENABLE, 0x0); |
| } |
| |
| static void edp_power_init(int index) |
| { |
| #ifdef CONFIG_SECURE_POWER_CONTROL |
| //#define PM_EDP0 48 |
| //#define PM_EDP1 49 |
| //#define PM_MIPI_DSI1 50 |
| //#define PM_MIPI_DSI0 41 |
| if (index) |
| pwr_ctrl_psci_smc(PM_EDP1, 1); |
| else |
| pwr_ctrl_psci_smc(PM_EDP0, 1); |
| LCDPR("[%d]: edp power domain on\n", index); |
| #endif |
| } |
| |
| void edp_tx_ctrl(struct aml_lcd_drv_s *pdrv, int flag) |
| { |
| if (flag) { |
| edp_power_init(pdrv->index); |
| edp_tx_init(pdrv); |
| } else { |
| edp_tx_disable(pdrv); |
| } |
| } |