| /* SPDX-License-Identifier: GPL-2.0-only */ |
| |
| #include <device/mmio.h> |
| #include <console/console.h> |
| #include <delay.h> |
| #include <device/device.h> |
| #include <edid.h> |
| #include <gpio.h> |
| #include <string.h> |
| #include <soc/addressmap.h> |
| #include <soc/clock.h> |
| #include <soc/display.h> |
| #include <soc/mipi.h> |
| #include <soc/soc.h> |
| #include <types.h> |
| #include <timer.h> |
| |
| static struct rk_mipi_dsi rk_mipi[2] = { |
| { .mipi_regs = (void *)MIPI0_BASE}, |
| { .mipi_regs = (void *)MIPI1_BASE} |
| }; |
| |
| /* |
| * The controller should generate 2 frames before |
| * preparing the peripheral. |
| */ |
| static void rk_mipi_dsi_wait_for_two_frames(struct rk_mipi_dsi *dsi, |
| const struct edid *edid) |
| { |
| int two_frames; |
| unsigned int refresh = edid->mode.refresh; |
| |
| two_frames = DIV_ROUND_UP(MSECS_PER_SEC * 2, refresh); |
| mdelay(two_frames); |
| } |
| |
| static const struct dphy_pll_parameter_map dppa_map[] = { |
| { 89, 0x00, CP_CURRENT_3UA, LPF_RESISTORS_13KOHM}, |
| { 99, 0x10, CP_CURRENT_3UA, LPF_RESISTORS_13KOHM}, |
| { 109, 0x20, CP_CURRENT_3UA, LPF_RESISTORS_13KOHM}, |
| { 129, 0x01, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM}, |
| { 139, 0x11, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM}, |
| { 149, 0x21, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM}, |
| { 169, 0x02, CP_CURRENT_6UA, LPF_RESISTORS_13KOHM}, |
| { 179, 0x12, CP_CURRENT_6UA, LPF_RESISTORS_13KOHM}, |
| { 199, 0x22, CP_CURRENT_6UA, LPF_RESISTORS_13KOHM}, |
| { 219, 0x03, CP_CURRENT_4_5UA, LPF_RESISTORS_13KOHM}, |
| { 239, 0x13, CP_CURRENT_4_5UA, LPF_RESISTORS_13KOHM}, |
| { 249, 0x23, CP_CURRENT_4_5UA, LPF_RESISTORS_13KOHM}, |
| { 269, 0x04, CP_CURRENT_6UA, LPF_RESISTORS_11_5KOHM}, |
| { 299, 0x14, CP_CURRENT_6UA, LPF_RESISTORS_11_5KOHM}, |
| { 329, 0x05, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM}, |
| { 359, 0x15, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM}, |
| { 399, 0x25, CP_CURRENT_3UA, LPF_RESISTORS_15_5KOHM}, |
| { 449, 0x06, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 499, 0x16, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 549, 0x07, CP_CURRENT_7_5UA, LPF_RESISTORS_10_5KOHM}, |
| { 599, 0x17, CP_CURRENT_7_5UA, LPF_RESISTORS_10_5KOHM}, |
| { 649, 0x08, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 699, 0x18, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 749, 0x09, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 799, 0x19, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 849, 0x29, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 899, 0x39, CP_CURRENT_7_5UA, LPF_RESISTORS_11_5KOHM}, |
| { 949, 0x0a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM}, |
| { 999, 0x1a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM}, |
| {1049, 0x2a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM}, |
| {1099, 0x3a, CP_CURRENT_12UA, LPF_RESISTORS_8KOHM}, |
| {1149, 0x0b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1199, 0x1b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1249, 0x2b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1299, 0x3b, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1349, 0x0c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1399, 0x1c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1449, 0x2c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM}, |
| {1500, 0x3c, CP_CURRENT_12UA, LPF_RESISTORS_10_5KOHM} |
| }; |
| |
| static int max_mbps_to_parameter(unsigned int max_mbps) |
| { |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(dppa_map); i++) { |
| if (dppa_map[i].max_mbps >= max_mbps) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| static void rk_mipi_dsi_phy_write(struct rk_mipi_dsi *dsi, |
| u8 test_code, |
| u8 test_data) |
| { |
| /* |
| * With the falling edge on TESTCLK, the TESTDIN[7:0] signal content |
| * is latched internally as the current test code. Test data is |
| * programmed internally by rising edge on TESTCLK. |
| */ |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl0, |
| PHY_TESTCLK | PHY_UNTESTCLR); |
| |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl1, |
| PHY_TESTEN | PHY_TESTDOUT(0) | PHY_TESTDIN(test_code)); |
| |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl0, |
| PHY_UNTESTCLK | PHY_UNTESTCLR); |
| |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl1, |
| PHY_UNTESTEN | PHY_TESTDOUT(0) | PHY_TESTDIN(test_data)); |
| |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl0, |
| PHY_TESTCLK | PHY_UNTESTCLR); |
| } |
| |
| /* bytes_per_ns - Nanoseconds to byte clock cycles */ |
| static inline unsigned int bytes_per_ns(struct rk_mipi_dsi *dsi, int ns) |
| { |
| return DIV_ROUND_UP((u64)ns * dsi->lane_bps, (u64)8 * NSECS_PER_SEC); |
| } |
| |
| /* bits_per_ns - Nanoseconds to bit time periods */ |
| static inline unsigned int bits_per_ns(struct rk_mipi_dsi *dsi, int ns) |
| { |
| return DIV_ROUND_UP((u64)ns * dsi->lane_bps, NSECS_PER_SEC); |
| } |
| |
| static int rk_mipi_dsi_wait_phy_lock(struct rk_mipi_dsi *dsi) |
| { |
| struct stopwatch sw; |
| int val; |
| |
| stopwatch_init_msecs_expire(&sw, 20); |
| do { |
| val = read32(&dsi->mipi_regs->dsi_phy_status); |
| if (val & LOCK) |
| return 0; |
| } while (!stopwatch_expired(&sw)); |
| |
| return -1; |
| } |
| |
| static int rk_mipi_dsi_phy_init(struct rk_mipi_dsi *dsi) |
| { |
| int i, vco, val; |
| int lane_mbps = DIV_ROUND_UP(dsi->lane_bps, USECS_PER_SEC); |
| struct stopwatch sw; |
| |
| vco = (lane_mbps < 200) ? 0 : (lane_mbps + 100) / 200; |
| |
| i = max_mbps_to_parameter(lane_mbps); |
| if (i < 0) { |
| printk(BIOS_DEBUG, |
| "failed to get parameter for %dmbps clock\n", lane_mbps); |
| return i; |
| } |
| |
| /* Start by clearing PHY state */ |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl0, PHY_UNTESTCLR); |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl0, PHY_TESTCLR); |
| write32(&dsi->mipi_regs->dsi_phy_tst_ctrl0, PHY_UNTESTCLR); |
| |
| rk_mipi_dsi_phy_write(dsi, PLL_BIAS_CUR_SEL_CAP_VCO_CONTROL, |
| BYPASS_VCO_RANGE | |
| VCO_RANGE_CON_SEL(vco) | |
| VCO_IN_CAP_CON_LOW | |
| REF_BIAS_CUR_SEL); |
| |
| rk_mipi_dsi_phy_write(dsi, PLL_CP_CONTROL_PLL_LOCK_BYPASS, |
| CP_CURRENT_SEL(dppa_map[i].icpctrl)); |
| rk_mipi_dsi_phy_write(dsi, PLL_LPF_AND_CP_CONTROL, |
| CP_PROGRAM_EN | |
| LPF_PROGRAM_EN | |
| LPF_RESISTORS_SEL(dppa_map[i].lpfctrl)); |
| rk_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_0, |
| HSFREQRANGE_SEL(dppa_map[i].hsfreqrange)); |
| |
| rk_mipi_dsi_phy_write(dsi, PLL_INPUT_DIVIDER_RATIO, |
| INPUT_DIVIDER(dsi->input_div)); |
| rk_mipi_dsi_phy_write(dsi, PLL_LOOP_DIVIDER_RATIO, |
| LOOP_DIV_LOW_SEL(dsi->feedback_div) | |
| LOW_PROGRAM_EN); |
| |
| /* |
| * we need set divider control register immediately to make |
| * the configured LSB effective according to IP simulation |
| * and lab test results. Only in this way can we get correct |
| * mipi phy pll frequency. |
| */ |
| rk_mipi_dsi_phy_write(dsi, PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL, |
| PLL_LOOP_DIV_EN | PLL_INPUT_DIV_EN); |
| rk_mipi_dsi_phy_write(dsi, PLL_LOOP_DIVIDER_RATIO, |
| LOOP_DIV_HIGH_SEL(dsi->feedback_div) | |
| HIGH_PROGRAM_EN); |
| rk_mipi_dsi_phy_write(dsi, PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL, |
| PLL_LOOP_DIV_EN | PLL_INPUT_DIV_EN); |
| rk_mipi_dsi_phy_write(dsi, AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY, |
| LOW_PROGRAM_EN | |
| BIASEXTR_SEL(BIASEXTR_127_7)); |
| rk_mipi_dsi_phy_write(dsi, AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY, |
| HIGH_PROGRAM_EN | |
| BANDGAP_SEL(BANDGAP_96_10)); |
| rk_mipi_dsi_phy_write(dsi, BANDGAP_AND_BIAS_CONTROL, |
| POWER_CONTROL | INTERNAL_REG_CURRENT | |
| BIAS_BLOCK_ON | BANDGAP_ON); |
| rk_mipi_dsi_phy_write(dsi, TERMINATION_RESISTER_CONTROL, |
| TER_RESISTOR_LOW | TER_CAL_DONE | |
| SETRD_MAX | TER_RESISTORS_ON); |
| rk_mipi_dsi_phy_write(dsi, TERMINATION_RESISTER_CONTROL, |
| TER_RESISTOR_HIGH | LEVEL_SHIFTERS_ON | |
| SETRD_MAX | POWER_MANAGE | |
| TER_RESISTORS_ON); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_REQUEST_STATE_TIME_CONTROL, |
| TLP_PROGRAM_EN | bytes_per_ns(dsi, 500)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_PREPARE_STATE_TIME_CONTROL, |
| THS_PRE_PROGRAM_EN | bits_per_ns(dsi, 40)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_HS_ZERO_STATE_TIME_CONTROL, |
| THS_ZERO_PROGRAM_EN | bytes_per_ns(dsi, 300)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_TRAIL_STATE_TIME_CONTROL, |
| THS_PRE_PROGRAM_EN | bits_per_ns(dsi, 100)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_EXIT_STATE_TIME_CONTROL, |
| BIT(5) | bytes_per_ns(dsi, 100)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_CLOCK_LANE_POST_TIME_CONTROL, |
| BIT(5) | (bytes_per_ns(dsi, 60) + 7)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_REQUEST_STATE_TIME_CONTROL, |
| TLP_PROGRAM_EN | bytes_per_ns(dsi, 500)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_PREPARE_STATE_TIME_CONTROL, |
| THS_PRE_PROGRAM_EN | (bits_per_ns(dsi, 50) + 5)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_HS_ZERO_STATE_TIME_CONTROL, |
| THS_ZERO_PROGRAM_EN | |
| (bytes_per_ns(dsi, 140) + 2)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_TRAIL_STATE_TIME_CONTROL, |
| THS_PRE_PROGRAM_EN | (bits_per_ns(dsi, 60) + 8)); |
| rk_mipi_dsi_phy_write(dsi, HS_TX_DATA_LANE_EXIT_STATE_TIME_CONTROL, |
| BIT(5) | bytes_per_ns(dsi, 100)); |
| |
| write32(&dsi->mipi_regs->dsi_phy_rstz, |
| PHY_ENFORCEPLL | PHY_ENABLECLK | |
| PHY_UNRSTZ | PHY_UNSHUTDOWNZ); |
| |
| if (rk_mipi_dsi_wait_phy_lock(dsi)) { |
| printk(BIOS_ERR, "failed to wait for phy lock state\n"); |
| return -1; |
| } |
| |
| stopwatch_init_msecs_expire(&sw, 20); |
| do { |
| val = read32(&dsi->mipi_regs->dsi_phy_status); |
| if (val & STOP_STATE_CLK_LANE) |
| return 0; |
| } while (!stopwatch_expired(&sw)); |
| |
| printk(BIOS_ERR, "failed to wait for phy clk lane stop state"); |
| return -1; |
| } |
| |
| static inline int mipi_dsi_pixel_format_to_bpp(enum mipi_dsi_pixel_format fmt) |
| { |
| switch (fmt) { |
| case MIPI_DSI_FMT_RGB888: |
| case MIPI_DSI_FMT_RGB666: |
| return 24; |
| |
| case MIPI_DSI_FMT_RGB666_PACKED: |
| return 18; |
| |
| case MIPI_DSI_FMT_RGB565: |
| return 16; |
| } |
| |
| return -1; |
| } |
| |
| static int rk_mipi_dsi_get_lane_bps(struct rk_mipi_dsi *dsi, |
| const struct edid *edid, |
| const struct mipi_panel_data *panel_data) |
| { |
| u64 pclk, target_bps; |
| u32 max_bps = dppa_map[ARRAY_SIZE(dppa_map) - 1].max_mbps * MHz; |
| int bpp; |
| u64 best_freq = 0; |
| u64 fvco_min, fvco_max, fref; |
| u32 min_prediv, max_prediv; |
| u32 prediv, best_prediv; |
| u64 fbdiv, best_fbdiv; |
| u32 min_delta; |
| |
| bpp = mipi_dsi_pixel_format_to_bpp(dsi->format); |
| if (bpp < 0) { |
| printk(BIOS_DEBUG, "failed to get bpp for pixel format %d\n", |
| dsi->format); |
| return bpp; |
| } |
| pclk = (u64)edid->mode.pixel_clock * MSECS_PER_SEC; |
| |
| /* take 1 / 0.8, since mbps must bigger than bandwidth of RGB */ |
| target_bps = pclk / panel_data->lanes * bpp / 8 * 10; |
| if (target_bps >= max_bps) { |
| printk(BIOS_DEBUG, "DPHY clock frequency is out of range\n"); |
| return -1; |
| } |
| |
| fref = OSC_HZ; |
| |
| /* constraint: 5Mhz <= Fref / N <= 40MHz */ |
| min_prediv = DIV_ROUND_UP(fref, 40 * MHz); |
| max_prediv = fref / (5 * MHz); |
| |
| /* constraint: 80MHz <= Fvco <= 1500MHz */ |
| fvco_min = 80 * MHz; |
| fvco_max = 1500 * MHz; |
| min_delta = 1500 * MHz; |
| |
| for (prediv = min_prediv; prediv <= max_prediv; prediv++) { |
| u64 freq; |
| int delta; |
| |
| /* Fvco = Fref * M / N */ |
| fbdiv = target_bps * prediv / fref; |
| |
| /* |
| * Due to the use of a "by 2 pre-scaler", the range of the |
| * feedback multiplication value M is limited to even division |
| * numbers, and m must be in 6 <= m <= 512. |
| */ |
| fbdiv += fbdiv % 2; |
| if (fbdiv < 6 || fbdiv > 512) |
| continue; |
| |
| freq = (u64)fbdiv * fref / prediv; |
| if (freq < fvco_min || freq > fvco_max) |
| continue; |
| |
| delta = target_bps - freq; |
| delta = ABS(delta); |
| if (delta >= min_delta) |
| continue; |
| |
| best_prediv = prediv; |
| best_fbdiv = fbdiv; |
| min_delta = delta; |
| best_freq = freq; |
| } |
| |
| if (best_freq) { |
| dsi->lane_bps = best_freq; |
| dsi->input_div = best_prediv; |
| dsi->feedback_div = best_fbdiv; |
| } else { |
| printk(BIOS_ERR, "Can not find best_freq for DPHY\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void rk_mipi_dsi_dpi_config(struct rk_mipi_dsi *dsi) |
| { |
| u32 color = 0; |
| |
| switch (dsi->format) { |
| case MIPI_DSI_FMT_RGB888: |
| color = DPI_COLOR_CODING_24BIT; |
| break; |
| case MIPI_DSI_FMT_RGB666: |
| color = DPI_COLOR_CODING_18BIT_2 | EN18_LOOSELY; |
| break; |
| case MIPI_DSI_FMT_RGB666_PACKED: |
| color = DPI_COLOR_CODING_18BIT_1; |
| break; |
| case MIPI_DSI_FMT_RGB565: |
| color = DPI_COLOR_CODING_16BIT_1; |
| break; |
| } |
| |
| write32(&dsi->mipi_regs->dsi_dpi_vcid, 0); |
| write32(&dsi->mipi_regs->dsi_dpi_color_coding, color); |
| |
| write32(&dsi->mipi_regs->dsi_dpi_cfg_pol, 0); |
| |
| write32(&dsi->mipi_regs->dsi_dpi_lp_cmd_tim, |
| OUTVACT_LPCMD_TIME(4) | INVACT_LPCMD_TIME(4)); |
| } |
| |
| static void rk_mipi_dsi_packet_handler_config(struct rk_mipi_dsi *dsi) |
| { |
| write32(&dsi->mipi_regs->dsi_pckhdl_cfg, |
| EN_CRC_RX | EN_ECC_RX | EN_BTA); |
| } |
| |
| static void rk_mipi_dsi_video_mode_config(struct rk_mipi_dsi *dsi) |
| { |
| write32(&dsi->mipi_regs->dsi_vid_mode_cfg, |
| VID_MODE_TYPE_BURST_SYNC_PULSES | ENABLE_LOW_POWER); |
| } |
| |
| static void rk_mipi_dsi_video_packet_config(struct rk_mipi_dsi *dsi, |
| const struct edid *edid, |
| const struct mipi_panel_data *panel_data) |
| { |
| int pkt_size; |
| |
| if (panel_data->mipi_num > 1) |
| pkt_size = VID_PKT_SIZE(edid->mode.ha / 2 + 4); |
| else |
| pkt_size = VID_PKT_SIZE(edid->mode.ha); |
| |
| write32(&dsi->mipi_regs->dsi_vid_pkt_size, pkt_size); |
| } |
| |
| static void rk_mipi_dsi_command_mode_config(struct rk_mipi_dsi *dsi) |
| { |
| write32(&dsi->mipi_regs->dsi_to_cnt_cfg, |
| HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000)); |
| write32(&dsi->mipi_regs->dsi_bta_to_cnt, 0xd00); |
| write32(&dsi->mipi_regs->dsi_cmd_mode_cfg, CMD_MODE_ALL_LP); |
| write32(&dsi->mipi_regs->dsi_mode_cfg, ENABLE_CMD_MODE); |
| } |
| |
| /* Get lane byte clock cycles. */ |
| static u32 rk_mipi_dsi_get_hcomponent_lbcc(struct rk_mipi_dsi *dsi, |
| u32 hcomponent, |
| const struct edid *edid) |
| { |
| u32 lbcc; |
| u64 lbcc_tmp; |
| |
| lbcc_tmp = hcomponent * dsi->lane_bps / (8 * MSECS_PER_SEC); |
| lbcc = DIV_ROUND_UP(lbcc_tmp, edid->mode.pixel_clock); |
| |
| return lbcc; |
| } |
| |
| static void rk_mipi_dsi_line_timer_config(struct rk_mipi_dsi *dsi, |
| const struct edid *edid) |
| { |
| u32 htotal, hsa, hbp, lbcc; |
| |
| htotal = edid->mode.ha + edid->mode.hbl; |
| hsa = edid->mode.hspw; |
| hbp = edid->mode.hbl - edid->mode.hso - edid->mode.hspw; |
| |
| lbcc = rk_mipi_dsi_get_hcomponent_lbcc(dsi, htotal, edid); |
| write32(&dsi->mipi_regs->dsi_vid_hline_time, lbcc); |
| |
| lbcc = rk_mipi_dsi_get_hcomponent_lbcc(dsi, hsa, edid); |
| write32(&dsi->mipi_regs->dsi_vid_hsa_time, lbcc); |
| lbcc = rk_mipi_dsi_get_hcomponent_lbcc(dsi, hbp, edid); |
| write32(&dsi->mipi_regs->dsi_vid_hbp_time, lbcc); |
| } |
| |
| static void rk_mipi_dsi_vertical_timing_config(struct rk_mipi_dsi *dsi, |
| const struct edid *edid) |
| { |
| u32 vactive, vsa, vfp, vbp; |
| |
| vactive = edid->mode.va; |
| vsa = edid->mode.vspw; |
| vfp = edid->mode.vso; |
| vbp = edid->mode.vbl - edid->mode.vso - edid->mode.vspw; |
| |
| write32(&dsi->mipi_regs->dsi_vid_vactive_lines, vactive); |
| write32(&dsi->mipi_regs->dsi_vid_vsa_lines, vsa); |
| write32(&dsi->mipi_regs->dsi_vid_vfp_lines, vfp); |
| write32(&dsi->mipi_regs->dsi_vid_vbp_lines, vbp); |
| } |
| |
| static void rk_mipi_dsi_dphy_timing_config(struct rk_mipi_dsi *dsi) |
| { |
| /* |
| * HS-PREPARE: 40ns + 4 * UI ~ 85ns + 6 * UI |
| * HS-EXIT: 100ns |
| */ |
| write32(&dsi->mipi_regs->dsi_phy_tmr_cfg, PHY_HS2LP_TIME(0x40) | |
| PHY_LP2HS_TIME(0x40) | |
| MAX_RD_TIME(10000)); |
| |
| write32(&dsi->mipi_regs->dsi_phy_tmr_lpclk_cfg, |
| PHY_CLKHS2LP_TIME(0x40) | PHY_CLKLP2HS_TIME(0x40)); |
| } |
| |
| static void rk_mipi_dsi_clear_err(struct rk_mipi_dsi *dsi) |
| { |
| read32(&dsi->mipi_regs->dsi_int_st0); |
| read32(&dsi->mipi_regs->dsi_int_st1); |
| write32(&dsi->mipi_regs->dsi_int_msk0, 0); |
| write32(&dsi->mipi_regs->dsi_int_msk1, 0); |
| } |
| |
| static void rk_mipi_dsi_dphy_interface_config(struct rk_mipi_dsi *dsi) |
| { |
| write32(&dsi->mipi_regs->dsi_phy_if_cfg, PHY_STOP_WAIT_TIME(0x20) | |
| N_LANES(dsi->lanes)); |
| } |
| |
| static void rk_mipi_dsi_set_mode(struct rk_mipi_dsi *dsi, |
| enum rk_mipi_dsi_mode mode) |
| { |
| write32(&dsi->mipi_regs->dsi_pwr_up, RESET); |
| if (mode == MIPI_DSI_CMD_MODE) { |
| write32(&dsi->mipi_regs->dsi_mode_cfg, ENABLE_CMD_MODE); |
| } else { |
| write32(&dsi->mipi_regs->dsi_mode_cfg, ENABLE_VIDEO_MODE); |
| rk_mipi_dsi_video_mode_config(dsi); |
| write32(&dsi->mipi_regs->dsi_lpclk_ctrl, PHY_TXREQUESTCLKHS); |
| } |
| write32(&dsi->mipi_regs->dsi_pwr_up, POWERUP); |
| } |
| |
| static void rk_mipi_dsi_init(struct rk_mipi_dsi *dsi) |
| { |
| /* |
| * The maximum permitted escape clock is 20MHz and it is derived from |
| * lanebyteclk, which is running at "lane_mbps / 8". Thus we want: |
| * |
| * (lane_mbps >> 3) / esc_clk_division < 20 |
| * which is: |
| * (lane_mbps >> 3) / 20 > esc_clk_division |
| */ |
| u32 esc_clk_division = DIV_ROUND_UP(dsi->lane_bps, |
| 8 * 20 * USECS_PER_SEC); |
| |
| write32(&dsi->mipi_regs->dsi_pwr_up, RESET); |
| write32(&dsi->mipi_regs->dsi_phy_rstz, |
| PHY_DISFORCEPLL | PHY_DISABLECLK | PHY_RSTZ | PHY_SHUTDOWNZ); |
| write32(&dsi->mipi_regs->dsi_clk_cfg, |
| TO_CLK_DIVIDSION(10) | |
| TX_ESC_CLK_DIVIDSION(esc_clk_division)); |
| } |
| |
| static void rk_mipi_message_config(struct rk_mipi_dsi *dsi) |
| { |
| write32(&dsi->mipi_regs->dsi_lpclk_ctrl, 0); |
| write32(&dsi->mipi_regs->dsi_cmd_mode_cfg, CMD_MODE_ALL_LP); |
| } |
| |
| static int rk_mipi_dsi_check_fifo(struct rk_mipi_dsi *dsi, u32 flag) |
| { |
| struct stopwatch sw; |
| int val; |
| |
| stopwatch_init_msecs_expire(&sw, 20); |
| do { |
| val = read32(&dsi->mipi_regs->dsi_cmd_pkt_status); |
| if (!(val & flag)) |
| return 0 ; |
| } while (!stopwatch_expired(&sw)); |
| |
| return -1; |
| } |
| |
| static int rk_mipi_dsi_gen_pkt_hdr_write(struct rk_mipi_dsi *dsi, u32 hdr_val) |
| { |
| int val; |
| struct stopwatch sw; |
| u32 mask; |
| |
| if (rk_mipi_dsi_check_fifo(dsi, GEN_CMD_FULL)) { |
| printk(BIOS_ERR, "failed to get available command FIFO\n"); |
| return -1; |
| } |
| |
| write32(&dsi->mipi_regs->dsi_gen_hdr, hdr_val); |
| |
| mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY; |
| stopwatch_init_msecs_expire(&sw, 20); |
| do { |
| val = read32(&dsi->mipi_regs->dsi_cmd_pkt_status); |
| if ((val & mask) == mask) |
| return 0 ; |
| } while (!stopwatch_expired(&sw)); |
| printk(BIOS_ERR, "failed to write command FIFO\n"); |
| |
| return -1; |
| } |
| |
| static int rk_mipi_dsi_dcs_cmd(struct rk_mipi_dsi *dsi, u8 cmd) |
| { |
| u32 val; |
| |
| rk_mipi_message_config(dsi); |
| |
| val = GEN_HDATA(cmd) | GEN_HTYPE(MIPI_DSI_DCS_SHORT_WRITE); |
| |
| return rk_mipi_dsi_gen_pkt_hdr_write(dsi, val); |
| } |
| |
| static int rk_mipi_dsi_dci_long_write(struct rk_mipi_dsi *dsi, |
| char *data, u32 len) |
| { |
| u32 remainder; |
| int ret = 0; |
| |
| while (len) { |
| if (len < 4) { |
| remainder = 0; |
| memcpy(&remainder, data, len); |
| write32(&dsi->mipi_regs->dsi_gen_pld_data, remainder); |
| len = 0; |
| } else { |
| remainder = *(u32 *)data; |
| write32(&dsi->mipi_regs->dsi_gen_pld_data, remainder); |
| data += 4; |
| len -= 4; |
| } |
| |
| ret = rk_mipi_dsi_check_fifo(dsi, GEN_PLD_W_FULL); |
| if (ret) { |
| printk(BIOS_ERR, "Failed to write fifo\n"); |
| return ret; |
| } |
| } |
| |
| return ret; |
| } |
| |
| static int rk_mipi_dsi_write(struct rk_mipi_dsi *dsi, char *data, int len) |
| { |
| u16 buf = 0; |
| u32 val; |
| int ret = 0; |
| |
| rk_mipi_message_config(dsi); |
| |
| switch (len) { |
| case 0: |
| die("not data!"); |
| case 1: |
| val = GEN_HDATA(*data) | |
| GEN_HTYPE(MIPI_DSI_DCS_SHORT_WRITE); |
| break; |
| case 2: |
| buf = *data++; |
| buf |= *data << 8; |
| val = GEN_HDATA(buf) | |
| GEN_HTYPE(MIPI_DSI_DCS_SHORT_WRITE_PARAM); |
| break; |
| default: |
| ret = rk_mipi_dsi_dci_long_write(dsi, data, len); |
| if (ret) { |
| printk(BIOS_ERR, "error happened during long write\n"); |
| return ret; |
| } |
| val = GEN_HDATA(len) | GEN_HTYPE(MIPI_DSI_DCS_LONG_WRITE); |
| break; |
| } |
| |
| return rk_mipi_dsi_gen_pkt_hdr_write(dsi, val); |
| } |
| |
| static void rk_mipi_enable(struct rk_mipi_dsi *dsi, |
| const struct edid *edid, |
| const struct mipi_panel_data *panel_data) |
| { |
| if (rk_mipi_dsi_get_lane_bps(dsi, edid, panel_data) < 0) |
| return; |
| |
| rk_mipi_dsi_init(dsi); |
| rk_mipi_dsi_dpi_config(dsi); |
| rk_mipi_dsi_packet_handler_config(dsi); |
| rk_mipi_dsi_video_mode_config(dsi); |
| rk_mipi_dsi_video_packet_config(dsi, edid, panel_data); |
| rk_mipi_dsi_command_mode_config(dsi); |
| rk_mipi_dsi_line_timer_config(dsi, edid); |
| rk_mipi_dsi_vertical_timing_config(dsi, edid); |
| rk_mipi_dsi_dphy_timing_config(dsi); |
| rk_mipi_dsi_dphy_interface_config(dsi); |
| rk_mipi_dsi_clear_err(dsi); |
| if (rk_mipi_dsi_phy_init(dsi) < 0) |
| return; |
| rk_mipi_dsi_wait_for_two_frames(dsi, edid); |
| |
| rk_mipi_dsi_set_mode(dsi, MIPI_DSI_CMD_MODE); |
| } |
| |
| void rk_mipi_prepare(const struct edid *edid, |
| const struct mipi_panel_data *panel_data) |
| { |
| int i, num; |
| struct panel_init_command *cmds; |
| |
| for (i = 0; i < panel_data->mipi_num; i++) { |
| rk_mipi[i].lanes = panel_data->lanes / panel_data->mipi_num; |
| rk_mipi[i].format = panel_data->format; |
| rk_mipi_enable(&rk_mipi[i], edid, panel_data); |
| } |
| |
| if (panel_data->init_cmd) { |
| cmds = panel_data->init_cmd; |
| for (num = 0; cmds[num].len != 0; num++) { |
| struct panel_init_command *cmd = &cmds[num]; |
| for (i = 0; i < panel_data->mipi_num; i++) { |
| if (rk_mipi_dsi_write(&rk_mipi[i], cmd->data, |
| cmd->len)) |
| return; |
| |
| /* make sure panel picks up the command */ |
| if (rk_mipi_dsi_dcs_cmd(&rk_mipi[i], |
| MIPI_DCS_NOP)) |
| return; |
| } |
| } |
| } |
| |
| for (i = 0; i < panel_data->mipi_num; i++) { |
| if (rk_mipi_dsi_dcs_cmd(&rk_mipi[i], |
| MIPI_DCS_EXIT_SLEEP_MODE) < 0) |
| return; |
| } |
| udelay(panel_data->display_on_udelay); |
| for (i = 0; i < panel_data->mipi_num; i++) { |
| if (rk_mipi_dsi_dcs_cmd(&rk_mipi[i], |
| MIPI_DCS_SET_DISPLAY_ON) < 0) |
| return; |
| } |
| udelay(panel_data->video_mode_udelay); |
| for (i = 0; i < panel_data->mipi_num; i++) |
| rk_mipi_dsi_set_mode(&rk_mipi[i], MIPI_DSI_VID_MODE); |
| } |