From fe122d4dfc130be1e87b367b0dc9b39044b262bd Mon Sep 17 00:00:00 2001 From: Nickey Yang Date: Thu, 27 Apr 2017 09:38:06 +0800 Subject: rockchip/rk3399: Add MIPI driver This patch configures clock for mipi and then adds mipi driver for support innolux-p079zca mipi panel in rk3399 scarlet. Change-Id: I02475eefb187c619c614b1cd20e97074bc8d917f Signed-off-by: Nickey Yang Reviewed-on: https://review.coreboot.org/19477 Tested-by: build bot (Jenkins) Reviewed-by: Julius Werner --- src/soc/rockchip/common/include/soc/vop.h | 1 + src/soc/rockchip/common/vop.c | 16 +- src/soc/rockchip/rk3399/Makefile.inc | 1 + src/soc/rockchip/rk3399/chip.h | 12 + src/soc/rockchip/rk3399/clock.c | 10 + src/soc/rockchip/rk3399/display.c | 50 ++- src/soc/rockchip/rk3399/include/soc/addressmap.h | 1 + src/soc/rockchip/rk3399/include/soc/clock.h | 1 + src/soc/rockchip/rk3399/include/soc/mipi.h | 288 +++++++++++++++ src/soc/rockchip/rk3399/mipi.c | 436 +++++++++++++++++++++++ 10 files changed, 800 insertions(+), 16 deletions(-) create mode 100644 src/soc/rockchip/rk3399/include/soc/mipi.h create mode 100644 src/soc/rockchip/rk3399/mipi.c (limited to 'src/soc/rockchip') diff --git a/src/soc/rockchip/common/include/soc/vop.h b/src/soc/rockchip/common/include/soc/vop.h index 98ad08255a..c5c542583d 100644 --- a/src/soc/rockchip/common/include/soc/vop.h +++ b/src/soc/rockchip/common/include/soc/vop.h @@ -119,6 +119,7 @@ enum vop_modes { */ VOP_MODE_EDP = 0, VOP_MODE_HDMI, + VOP_MODE_MIPI, VOP_MODE_NONE, VOP_MODE_AUTO_DETECT, VOP_MODE_UNKNOWN, diff --git a/src/soc/rockchip/common/vop.c b/src/soc/rockchip/common/vop.c index 629072e22e..70d59bd6a8 100644 --- a/src/soc/rockchip/common/vop.c +++ b/src/soc/rockchip/common/vop.c @@ -24,7 +24,6 @@ #include #include - static struct rockchip_vop_regs * const vop_regs[] = { (struct rockchip_vop_regs *)VOP_BIG_BASE, (struct rockchip_vop_regs *)VOP_LIT_BASE @@ -109,6 +108,7 @@ void rkvop_mode_set(u32 vop_id, const struct edid *edid, u32 mode) u32 vfront_porch = edid->mode.vso; u32 vsync_len = edid->mode.vspw; u32 vback_porch = edid->mode.vbl - edid->mode.vso - edid->mode.vspw; + u32 dsp_out_mode; struct rockchip_vop_regs *preg = vop_regs[vop_id]; switch (mode) { @@ -116,17 +116,25 @@ void rkvop_mode_set(u32 vop_id, const struct edid *edid, u32 mode) case VOP_MODE_HDMI: clrsetbits_le32(&preg->sys_ctrl, M_ALL_OUT_EN, V_HDMI_OUT_EN(1)); + dsp_out_mode = 15; + break; + case VOP_MODE_MIPI: + clrsetbits_le32(&preg->sys_ctrl, + M_ALL_OUT_EN, V_MIPI_OUT_EN(1)); + dsp_out_mode = 0; break; - case VOP_MODE_EDP: default: clrsetbits_le32(&preg->sys_ctrl, M_ALL_OUT_EN, V_EDP_OUT_EN(1)); + dsp_out_mode = 15; break; } + clrsetbits_le32(&preg->dsp_ctrl0, - M_DSP_OUT_MODE | M_DSP_VSYNC_POL | M_DSP_HSYNC_POL, - V_DSP_OUT_MODE(15) | + M_DSP_OUT_MODE | M_DSP_VSYNC_POL | + M_DSP_HSYNC_POL, + V_DSP_OUT_MODE(dsp_out_mode) | V_DSP_HSYNC_POL(edid->mode.phsync == '+') | V_DSP_VSYNC_POL(edid->mode.pvsync == '+')); diff --git a/src/soc/rockchip/rk3399/Makefile.inc b/src/soc/rockchip/rk3399/Makefile.inc index 8448ff242e..54c51152af 100644 --- a/src/soc/rockchip/rk3399/Makefile.inc +++ b/src/soc/rockchip/rk3399/Makefile.inc @@ -66,6 +66,7 @@ ramstage-$(CONFIG_DRIVERS_UART) += ../common/uart.c ramstage-y += clock.c ramstage-$(CONFIG_MAINBOARD_DO_NATIVE_VGA_INIT) += display.c ramstage-$(CONFIG_MAINBOARD_DO_NATIVE_VGA_INIT) += ../common/edp.c +ramstage-$(CONFIG_MAINBOARD_DO_NATIVE_VGA_INIT) += mipi.c ramstage-y += ../common/gpio.c ramstage-y += gpio.c ramstage-y += ../common/i2c.c diff --git a/src/soc/rockchip/rk3399/chip.h b/src/soc/rockchip/rk3399/chip.h index 1f9462a009..4b2ccc5b52 100644 --- a/src/soc/rockchip/rk3399/chip.h +++ b/src/soc/rockchip/rk3399/chip.h @@ -26,6 +26,18 @@ struct soc_rockchip_rk3399_config { u32 bl_pwm_to_enable_udelay; u32 framebuffer_bits_per_pixel; u32 vop_mode; + u32 panel_pixel_clock; /* below only be considered for MIPI displays */ + u32 panel_refresh; + u32 panel_ha; + u32 panel_hbl; + u32 panel_hso; + u32 panel_hspw; + u32 panel_va; + u32 panel_vbl; + u32 panel_vso; + u32 panel_vspw; + u32 panel_display_on_mdelay; + u32 panel_video_mode_mdelay; }; #endif /* __SOC_ROCKCHIP_RK3399_CHIP_H__ */ diff --git a/src/soc/rockchip/rk3399/clock.c b/src/soc/rockchip/rk3399/clock.c index 7e205d2ba4..35c96bcf37 100644 --- a/src/soc/rockchip/rk3399/clock.c +++ b/src/soc/rockchip/rk3399/clock.c @@ -953,3 +953,13 @@ void rkclk_configure_edp(unsigned int hz) (src_clk_div - 1) << CLK_PCLK_EDP_DIV_CON_SHIFT)); } + +void rkclk_configure_mipi(void) +{ + /* Enable clk_mipidphy_ref and clk_mipidphy_cfg */ + write32(&cru_ptr->clkgate_con[11], + RK_CLRBITS(1 << 14 | 1 << 15)); + /* Enable pclk_mipi_dsi0 */ + write32(&cru_ptr->clkgate_con[29], + RK_CLRBITS(1 << 1)); +} diff --git a/src/soc/rockchip/rk3399/display.c b/src/soc/rockchip/rk3399/display.c index c7ca8916ba..379e1bfec8 100644 --- a/src/soc/rockchip/rk3399/display.c +++ b/src/soc/rockchip/rk3399/display.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -47,6 +48,21 @@ static void reset_edp(void) printk(BIOS_WARNING, "Retrying epd initialization.\n"); } +static void rk_get_mipi_mode(struct edid *edid, device_t dev) +{ + struct soc_rockchip_rk3399_config *conf = dev->chip_info; + + edid->mode.pixel_clock = conf->panel_pixel_clock; + edid->mode.refresh = conf->panel_refresh; + edid->mode.ha = conf->panel_ha; + edid->mode.hbl = conf->panel_hbl; + edid->mode.hso = conf->panel_hso; + edid->mode.hspw = conf->panel_hspw; + edid->mode.va = conf->panel_va; + edid->mode.vbl = conf->panel_vbl; + edid->mode.vso = conf->panel_vso; + edid->mode.vspw = conf->panel_vspw; +} void rk_display_init(device_t dev) { struct edid edid; @@ -60,8 +76,6 @@ void rk_display_init(device_t dev) switch (conf->vop_mode) { case VOP_MODE_NONE: return; - case VOP_MODE_AUTO_DETECT: - /* try EDP first, then HDMI */ case VOP_MODE_EDP: printk(BIOS_DEBUG, "Attempting to set up EDP display.\n"); rkclk_configure_vop_aclk(vop_id, 200 * MHz); @@ -91,11 +105,22 @@ retry_edp: } } break; - case VOP_MODE_HDMI: - printk(BIOS_WARNING, "HDMI display is NOT supported yet.\n"); - return; + case VOP_MODE_MIPI: + printk(BIOS_DEBUG, "Attempting to setup MIPI display.\n"); + + rkclk_configure_mipi(); + rkclk_configure_vop_aclk(vop_id, 200 * MHz); + + /* disable turnrequest turndisable forcetxstop forcerxmode */ + write32(&rk3399_grf->soc_con22, RK_CLRBITS(0xffff)); + /* select mipi-dsi0 signal from vop0 */ + write32(&rk3399_grf->soc_con20, RK_CLRBITS(1 << 0)); + + rk_get_mipi_mode(&edid, dev); + detected_mode = VOP_MODE_MIPI; + break; default: - printk(BIOS_WARNING, "Cannot read any EDID info, aborting.\n"); + printk(BIOS_WARNING, "Unsupported vop_mode, aborting.\n"); return; } @@ -112,20 +137,21 @@ retry_edp: rkvop_prepare(vop_id, &edid); switch (detected_mode) { - case VOP_MODE_HDMI: - /* should not be here before HDMI supported */ - return; + case VOP_MODE_MIPI: + rk_mipi_prepare(&edid, conf->panel_display_on_mdelay, conf->panel_video_mode_mdelay); + break; case VOP_MODE_EDP: - default: /* will enable edp in depthcharge */ if (rk_edp_prepare()) { reset_edp(); goto retry_edp; /* Rerun entire init sequence */ } - mainboard_power_on_backlight(); + break; + default: break; } - + mainboard_power_on_backlight(); set_vbe_mode_info_valid(&edid, (uintptr_t)0); + return; } diff --git a/src/soc/rockchip/rk3399/include/soc/addressmap.h b/src/soc/rockchip/rk3399/include/soc/addressmap.h index 1762a8d277..7a365adb86 100644 --- a/src/soc/rockchip/rk3399/include/soc/addressmap.h +++ b/src/soc/rockchip/rk3399/include/soc/addressmap.h @@ -59,6 +59,7 @@ #define SARADC_BASE 0xff100000 #define RK_PWM_BASE 0xff420000 #define EDP_BASE 0xff970000 +#define MIPI_BASE 0xff960000 #define VOP_BIG_BASE 0xff900000 /* corresponds to vop_id 0 */ #define VOP_LIT_BASE 0xff8f0000 /* corresponds to vop_id 1 */ diff --git a/src/soc/rockchip/rk3399/include/soc/clock.h b/src/soc/rockchip/rk3399/include/soc/clock.h index 3047f738b0..37a4c09012 100644 --- a/src/soc/rockchip/rk3399/include/soc/clock.h +++ b/src/soc/rockchip/rk3399/include/soc/clock.h @@ -120,5 +120,6 @@ void rkclk_ddr_reset(u32 ch, u32 ctl, u32 phy); int rkclk_was_watchdog_reset(void); uint32_t rkclk_i2c_clock_for_bus(unsigned bus); void rkclk_configure_edp(unsigned int hz); +void rkclk_configure_mipi(void); #endif /* __SOC_ROCKCHIP_RK3399_CLOCK_H__ */ diff --git a/src/soc/rockchip/rk3399/include/soc/mipi.h b/src/soc/rockchip/rk3399/include/soc/mipi.h new file mode 100644 index 0000000000..2dfbc521d2 --- /dev/null +++ b/src/soc/rockchip/rk3399/include/soc/mipi.h @@ -0,0 +1,288 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2017 Rockchip Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __RK_MIPI_H +#define __RK_MIPI_H + +#include + +struct rk_mipi_regs { + u32 dsi_version; + u32 dsi_pwr_up; + u32 dsi_clk_cfg; + u32 dsi_dpi_vcid; + u32 dsi_dpi_color_coding; + u32 dsi_dpi_cfg_pol; + u32 dsi_dpi_lp_cmd_tim; + u8 reserved0[0x28 - 0x18]; + u32 dsi_pckhdl_cfg; + u8 reserved1[0x30 - 0x2c]; + u32 dsi_mode_cfg; + u32 dsi_vid_mode_cfg; + u32 dsi_vid_pkt_size; + u32 dsi_vid_num_chumks; + u32 dsi_vid_null_pkt_size; + u32 dsi_vid_hsa_time; + u32 dsi_vid_hbp_time; + u32 dsi_vid_hline_time; + u32 dsi_vid_vsa_lines; + u32 dsi_vid_vbp_lines; + u32 dsi_vid_vfp_lines; + u32 dsi_vid_vactive_lines; + u32 dsi_edpi_cmd_size; + u32 dsi_cmd_mode_cfg; + u32 dsi_gen_hdr; + u32 dsi_gen_pld_data; + u32 dsi_cmd_pkt_status; + u32 dsi_to_cnt_cfg; + u8 reserved2[0x88 - 0x78]; + u32 dsi_bta_to_cnt; + u32 reserved3; + u32 dsi_lpclk_ctrl; + u32 dsi_phy_tmr_lpclk_cfg; + u32 dsi_phy_tmr_cfg; + u32 dsi_phy_rstz; + u32 dsi_phy_if_cfg; + u8 reserved4[0xac - 0xa4]; + u32 dsi_phy_status; + u32 dsi_phy_tst_ctrl0; + u32 dsi_phy_tst_ctrl1; + u32 dsi_int_st0; + u32 dsi_int_st1; + u32 dsi_int_msk0; + u32 dsi_int_msk1; +}; +check_member(rk_mipi_regs, dsi_int_msk1, 0xc8); + +#define RESET 0 +#define POWERUP BIT(0) + +#define TO_CLK_DIVIDSION(div) (((div) & 0xff) << 8) +#define TX_ESC_CLK_DIVIDSION(div) (((div) & 0xff) << 0) + +#define EN18_LOOSELY BIT(8) +#define DPI_COLOR_CODING_16BIT_1 0x0 +#define DPI_COLOR_CODING_16BIT_2 0x1 +#define DPI_COLOR_CODING_16BIT_3 0x2 +#define DPI_COLOR_CODING_18BIT_1 0x3 +#define DPI_COLOR_CODING_18BIT_2 0x4 +#define DPI_COLOR_CODING_24BIT 0x5 + +#define COLORM_ACTIVE_LOW BIT(4) +#define SHUTD_ACTIVE_LOW BIT(3) +#define HSYNC_ACTIVE_LOW BIT(2) +#define VSYNC_ACTIVE_LOW BIT(1) +#define DATAEN_ACTIVE_LOW BIT(0) + +#define OUTVACT_LPCMD_TIME(p) (((p) & 0xff) << 16) +#define INVACT_LPCMD_TIME(p) ((p) & 0xff) + +#define EN_CRC_RX BIT(4) +#define EN_ECC_RX BIT(3) +#define EN_BTA BIT(2) +#define EN_EOTP_RX BIT(1) +#define EN_EOTP_TX BIT(0) + +#define ENABLE_VIDEO_MODE 0 +#define ENABLE_CMD_MODE BIT(0) + +#define FRAME_BTA_ACK BIT(14) +#define ENABLE_LOW_POWER (0x3f << 8) +#define ENABLE_LOW_POWER_MASK (0x3f << 8) +#define VID_MODE_TYPE_BURST_SYNC_PULSES 0x0 +#define VID_MODE_TYPE_BURST_SYNC_EVENTS 0x1 +#define VID_MODE_TYPE_BURST 0x2 + +#define VID_PKT_SIZE(p) (((p) & 0x3fff) << 0) +#define VID_PKT_MAX_SIZE 0x3fff + +#define MAX_RD_PKT_SIZE_LP BIT(24) +#define DCS_LW_TX_LP BIT(19) +#define DCS_SR_0P_TX_LP BIT(18) +#define DCS_SW_1P_TX_LP BIT(17) +#define DCS_SW_0P_TX_LP BIT(16) +#define GEN_LW_TX_LP BIT(14) +#define GEN_SR_2P_TX_LP BIT(13) +#define GEN_SR_1P_TX_LP BIT(12) +#define GEN_SR_0P_TX_LP BIT(11) +#define GEN_SW_2P_TX_LP BIT(10) +#define GEN_SW_1P_TX_LP BIT(9) +#define GEN_SW_0P_TX_LP BIT(8) +#define EN_ACK_RQST BIT(1) +#define EN_TEAR_FX BIT(0) + +#define CMD_MODE_ALL_LP (MAX_RD_PKT_SIZE_LP | \ + DCS_LW_TX_LP | \ + DCS_SR_0P_TX_LP | \ + DCS_SW_1P_TX_LP | \ + DCS_SW_0P_TX_LP | \ + GEN_LW_TX_LP | \ + GEN_SR_2P_TX_LP | \ + GEN_SR_1P_TX_LP | \ + GEN_SR_0P_TX_LP | \ + GEN_SW_2P_TX_LP | \ + GEN_SW_1P_TX_LP | \ + GEN_SW_0P_TX_LP) + +#define GEN_HDATA(data) (((data) & 0xffff) << 8) +#define GEN_HDATA_MASK (0xffff << 8) +#define GEN_HTYPE(type) (((type) & 0xff) << 0) +#define GEN_HTYPE_MASK 0xff + +#define HSTX_TO_CNT(p) (((p) & 0xffff) << 16) +#define LPRX_TO_CNT(p) ((p) & 0xffff) + +#define AUTO_CLKLANE_CTRL BIT(1) +#define PHY_TXREQUESTCLKHS BIT(0) + +#define PHY_CLKHS2LP_TIME(lbcc) (((lbcc) & 0x3ff) << 16) +#define PHY_CLKLP2HS_TIME(lbcc) ((lbcc) & 0x3ff) + +#define PHY_HS2LP_TIME(lbcc) (((lbcc) & 0xff) << 24) +#define PHY_LP2HS_TIME(lbcc) (((lbcc) & 0xff) << 16) +#define MAX_RD_TIME(lbcc) ((lbcc) & 0x7fff) + +#define PHY_DISFORCEPLL 0 +#define PHY_ENFORCEPLL BIT(3) +#define PHY_DISABLECLK 0 +#define PHY_ENABLECLK BIT(2) +#define PHY_RSTZ 0 +#define PHY_UNRSTZ BIT(1) +#define PHY_SHUTDOWNZ 0 +#define PHY_UNSHUTDOWNZ BIT(0) + +#define N_LANES(n) ((((n) - 1) & 0x3) << 0) +#define PHY_STOP_WAIT_TIME(cycle) (((cycle) & 0xff) << 8) + +#define LOCK BIT(0) +#define STOP_STATE_CLK_LANE BIT(2) + +#define PHY_TESTCLK BIT(1) +#define PHY_UNTESTCLK 0 +#define PHY_TESTCLR BIT(0) +#define PHY_UNTESTCLR 0 + +#define PHY_TESTEN BIT(16) +#define PHY_UNTESTEN 0 +#define PHY_TESTDOUT(n) (((n) & 0xff) << 8) +#define PHY_TESTDIN(n) (((n) & 0xff) << 0) + +#define BYPASS_VCO_RANGE BIT(7) +#define VCO_RANGE_CON_SEL(val) (((val) & 0x7) << 3) +#define VCO_IN_CAP_CON_DEFAULT (0x0 << 1) +#define VCO_IN_CAP_CON_LOW (0x1 << 1) +#define VCO_IN_CAP_CON_HIGH (0x2 << 1) +#define REF_BIAS_CUR_SEL BIT(0) + +#define CP_CURRENT_3MA BIT(3) +#define CP_PROGRAM_EN BIT(7) +#define LPF_PROGRAM_EN BIT(6) +#define LPF_RESISTORS_20_KOHM 0 + +#define HSFREQRANGE_SEL(val) (((val) & 0x3f) << 1) + +#define INPUT_DIVIDER(val) ((val - 1) & 0x7f) +#define LOW_PROGRAM_EN 0 +#define HIGH_PROGRAM_EN BIT(7) +#define LOOP_DIV_LOW_SEL(val) ((val - 1) & 0x1f) +#define LOOP_DIV_HIGH_SEL(val) (((val - 1) >> 5) & 0x1f) +#define PLL_LOOP_DIV_EN BIT(5) +#define PLL_INPUT_DIV_EN BIT(4) + +#define POWER_CONTROL BIT(6) +#define INTERNAL_REG_CURRENT BIT(3) +#define BIAS_BLOCK_ON BIT(2) +#define BANDGAP_ON BIT(0) + +#define TER_RESISTOR_HIGH BIT(7) +#define TER_RESISTOR_LOW 0 +#define LEVEL_SHIFTERS_ON BIT(6) +#define TER_CAL_DONE BIT(5) +#define SETRD_MAX (0x7 << 2) +#define POWER_MANAGE BIT(1) +#define TER_RESISTORS_ON BIT(0) + +#define BIASEXTR_SEL(val) ((val) & 0x7) +#define BANDGAP_SEL(val) ((val) & 0x7) +#define TLP_PROGRAM_EN BIT(7) +#define THS_PRE_PROGRAM_EN BIT(7) +#define THS_ZERO_PROGRAM_EN BIT(6) + +#define PLL_BIAS_CUR_SEL_CAP_VCO_CONTROL 0x10 +#define PLL_CP_CONTROL_PLL_LOCK_BYPASS 0x11 +#define PLL_LPF_AND_CP_CONTROL 0x12 +#define PLL_INPUT_DIVIDER_RATIO 0x17 +#define PLL_LOOP_DIVIDER_RATIO 0x18 +#define PLL_INPUT_AND_LOOP_DIVIDER_RATIOS_CONTROL 0x19 +#define BANDGAP_AND_BIAS_CONTROL 0x20 +#define TERMINATION_RESISTER_CONTROL 0x21 +#define AFE_BIAS_BANDGAP_ANALOG_PROGRAMMABILITY 0x22 +#define HS_RX_CONTROL_OF_LANE_0 0x44 + +enum mipi_dsi_pixel_format { + MIPI_DSI_FMT_RGB888, + MIPI_DSI_FMT_RGB666, + MIPI_DSI_FMT_RGB666_PACKED, + MIPI_DSI_FMT_RGB565, +}; + +enum { + BANDGAP_97_07, + BANDGAP_98_05, + BANDGAP_99_02, + BANDGAP_100_00, + BANDGAP_93_17, + BANDGAP_94_15, + BANDGAP_95_12, + BANDGAP_96_10, +}; + +enum { + BIASEXTR_87_1, + BIASEXTR_91_5, + BIASEXTR_95_9, + BIASEXTR_100, + BIASEXTR_105_94, + BIASEXTR_111_88, + BIASEXTR_118_8, + BIASEXTR_127_7, +}; + +enum rk_mipi_dsi_mode { + MIPI_DSI_CMD_MODE, + MIPI_DSI_VID_MODE, +}; + +enum { + MIPI_DCS_NOP = 0x00, + MIPI_DCS_EXIT_SLEEP_MODE = 0x11, + MIPI_DCS_SET_DISPLAY_ON = 0x29, +}; + +struct dphy_pll_testdin_map { + unsigned int max_mbps; + u8 testdin; +}; + +struct rk_mipi_dsi { + u64 lane_bps; /* per lane */ + u32 lanes; + u32 format; + u16 input_div; + u16 feedback_div; +}; + +void rk_mipi_prepare(const struct edid *edid, u32 display_on_mdelay, u32 video_mode_mdelay); +#endif diff --git a/src/soc/rockchip/rk3399/mipi.c b/src/soc/rockchip/rk3399/mipi.c new file mode 100644 index 0000000000..5c8f4c2e33 --- /dev/null +++ b/src/soc/rockchip/rk3399/mipi.c @@ -0,0 +1,436 @@ +/* + * This file is part of the coreboot project. + * + * Copyright 2017 Rockchip Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static struct rk_mipi_dsi rk_mipi; +static struct rk_mipi_regs *mipi_regs = (void *)MIPI_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_testdin_map dptdin_map[] = { + { 90, 0x00}, { 100, 0x10}, { 110, 0x20}, { 130, 0x01}, + { 140, 0x11}, { 150, 0x21}, { 170, 0x02}, { 180, 0x12}, + { 200, 0x22}, { 220, 0x03}, { 240, 0x13}, { 250, 0x23}, + { 270, 0x04}, { 300, 0x14}, { 330, 0x05}, { 360, 0x15}, + { 400, 0x25}, { 450, 0x06}, { 500, 0x16}, { 550, 0x07}, + { 600, 0x17}, { 650, 0x08}, { 700, 0x18}, { 750, 0x09}, + { 800, 0x19}, { 850, 0x29}, { 900, 0x39}, { 950, 0x0a}, + {1000, 0x1a}, {1050, 0x2a}, {1100, 0x3a}, {1150, 0x0b}, + {1200, 0x1b}, {1250, 0x2b}, {1300, 0x3b}, {1350, 0x0c}, + {1400, 0x1c}, {1450, 0x2c}, {1500, 0x3c} +}; + +static int max_mbps_to_testdin(unsigned int max_mbps) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dptdin_map); i++) + if (dptdin_map[i].max_mbps > max_mbps) + return dptdin_map[i].testdin; + + 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(&mipi_regs->dsi_phy_tst_ctrl0, PHY_TESTCLK | PHY_UNTESTCLR); + + write32(&mipi_regs->dsi_phy_tst_ctrl1, PHY_TESTEN | PHY_TESTDOUT(0) | + PHY_TESTDIN(test_code)); + + write32(&mipi_regs->dsi_phy_tst_ctrl0, PHY_UNTESTCLK | PHY_UNTESTCLR); + + write32(&mipi_regs->dsi_phy_tst_ctrl1, PHY_UNTESTEN | PHY_TESTDOUT(0) | + PHY_TESTDIN(test_data)); + + write32(&mipi_regs->dsi_phy_tst_ctrl0, PHY_TESTCLK | PHY_UNTESTCLR); +} + +static int rk_mipi_dsi_phy_init(struct rk_mipi_dsi *dsi) +{ + int ret, testdin, vco; + + int lane_mbps = div_round_up(dsi->lane_bps, USECS_PER_SEC); + vco = (lane_mbps < 200) ? 0 : (lane_mbps + 100) / 200; + + testdin = max_mbps_to_testdin(lane_mbps); + if (testdin < 0) { + printk(BIOS_DEBUG, "failed to get testdin for %dmbps\n", + lane_mbps); + return testdin; + } + + /* Start by clearing PHY state */ + write32(&mipi_regs->dsi_phy_tst_ctrl0, PHY_UNTESTCLR); + write32(&mipi_regs->dsi_phy_tst_ctrl0, PHY_TESTCLR); + write32(&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_3MA); + rk_mipi_dsi_phy_write(dsi, PLL_LPF_AND_CP_CONTROL, + CP_PROGRAM_EN | + LPF_PROGRAM_EN | + LPF_RESISTORS_20_KOHM); + + rk_mipi_dsi_phy_write(dsi, HS_RX_CONTROL_OF_LANE_0, + HSFREQRANGE_SEL(testdin)); + 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); + 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); + + write32(&mipi_regs->dsi_phy_rstz, PHY_ENFORCEPLL | PHY_ENABLECLK | + PHY_UNRSTZ | PHY_UNSHUTDOWNZ); + return ret; +} + +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) +{ + u32 i, pre; + u64 pclk, pllref, tmp, target_bps; + u32 m = 1, n = 1; + u32 max_bps = 1500 * MHz; + int bpp; + + 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 = edid->mode.pixel_clock * MSECS_PER_SEC; + /* take 1 / 0.8, since mbps must bigger than bandwidth of RGB */ + target_bps = pclk / dsi->lanes * bpp / 8 * 10; + if (target_bps >= max_bps) { + printk(BIOS_DEBUG, "DPHY clock frequency is out of range\n"); + return -1; + } + pllref = OSC_HZ; + tmp = pllref; + /* + * The limits on the PLL divisor are: + * + * 5MHz <= (pllref / n) <= 40MHz + */ + for (i = pllref / (5 * MHz); i > div_round_up(pllref, 40 * MHz); i--) { + pre = pllref / i; + if ((tmp > (target_bps % pre)) && (target_bps / pre < 512)) { + tmp = target_bps % pre; + n = i; + m = target_bps / pre; + } + if (tmp == 0) + break; + } + dsi->lane_bps = pllref / n * m; + dsi->input_div = n; + dsi->feedback_div = m; + + 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(&mipi_regs->dsi_dpi_vcid, 0); + write32(&mipi_regs->dsi_dpi_color_coding, color); + + write32(&mipi_regs->dsi_dpi_cfg_pol, 0); + + write32(&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(&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(&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) +{ + write32(&mipi_regs->dsi_vid_pkt_size, VID_PKT_SIZE(0x300)); +} + +static void rk_mipi_dsi_command_mode_config(struct rk_mipi_dsi *dsi) +{ + write32(&mipi_regs->dsi_to_cnt_cfg, + HSTX_TO_CNT(1000) | LPRX_TO_CNT(1000)); + write32(&mipi_regs->dsi_bta_to_cnt, 0xd00); + write32(&mipi_regs->dsi_cmd_mode_cfg, CMD_MODE_ALL_LP); + write32(&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(&mipi_regs->dsi_vid_hline_time, lbcc); + + lbcc = rk_mipi_dsi_get_hcomponent_lbcc(dsi, hsa, edid); + write32(&mipi_regs->dsi_vid_hsa_time, lbcc); + lbcc = rk_mipi_dsi_get_hcomponent_lbcc(dsi, hbp, edid); + write32(&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(&mipi_regs->dsi_vid_vactive_lines, vactive); + write32(&mipi_regs->dsi_vid_vsa_lines, vsa); + write32(&mipi_regs->dsi_vid_vfp_lines, vfp); + write32(&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(&mipi_regs->dsi_phy_tmr_cfg, PHY_HS2LP_TIME(0x40) | + PHY_LP2HS_TIME(0x40) | + MAX_RD_TIME(10000)); + + write32(&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(&mipi_regs->dsi_int_st0); + read32(&mipi_regs->dsi_int_st1); + write32(&mipi_regs->dsi_int_msk0, 0); + write32(&mipi_regs->dsi_int_msk1, 0); +} + +static void rk_mipi_dsi_dphy_interface_config(struct rk_mipi_dsi *dsi) +{ + write32(&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(&mipi_regs->dsi_pwr_up, RESET); + if (mode == MIPI_DSI_CMD_MODE) { + write32(&mipi_regs->dsi_mode_cfg, ENABLE_CMD_MODE); + } else { + write32(&mipi_regs->dsi_mode_cfg, ENABLE_VIDEO_MODE); + rk_mipi_dsi_video_mode_config(dsi); + write32(&mipi_regs->dsi_lpclk_ctrl, PHY_TXREQUESTCLKHS); + } + write32(&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(&mipi_regs->dsi_pwr_up, RESET); + write32(&mipi_regs->dsi_phy_rstz, PHY_DISFORCEPLL | PHY_DISABLECLK | + PHY_RSTZ | PHY_SHUTDOWNZ); + write32(&mipi_regs->dsi_clk_cfg, + TO_CLK_DIVIDSION(10) | + TX_ESC_CLK_DIVIDSION(esc_clk_division)); +} + +static int rk_mipi_dsi_dcs_transfer(struct rk_mipi_dsi *dsi, u32 hdr_val) +{ + int ret; + + hdr_val = GEN_HDATA(hdr_val) | GEN_HTYPE(0x05); + ret = read32(&mipi_regs->dsi_cmd_pkt_status); + if (ret < 0) { + printk(BIOS_DEBUG, "failed to get available command FIFO\n"); + return ret; + } + + write32(&mipi_regs->dsi_lpclk_ctrl, 0); + write32(&mipi_regs->dsi_cmd_mode_cfg, CMD_MODE_ALL_LP); + write32(&mipi_regs->dsi_gen_hdr, hdr_val); + + return 0; +} + +void rk_mipi_prepare(const struct edid *edid, u32 display_on_mdelay, u32 video_mode_mdelay) +{ + rk_mipi.lanes = 4; + rk_mipi.format = MIPI_DSI_FMT_RGB888; + if (rk_mipi_dsi_get_lane_bps(&rk_mipi, edid) < 0) + return; + + rk_mipi_dsi_init(&rk_mipi); + rk_mipi_dsi_dpi_config(&rk_mipi); + rk_mipi_dsi_packet_handler_config(&rk_mipi); + rk_mipi_dsi_video_mode_config(&rk_mipi); + rk_mipi_dsi_video_packet_config(&rk_mipi); + rk_mipi_dsi_command_mode_config(&rk_mipi); + rk_mipi_dsi_line_timer_config(&rk_mipi, edid); + rk_mipi_dsi_vertical_timing_config(&rk_mipi, edid); + rk_mipi_dsi_dphy_timing_config(&rk_mipi); + rk_mipi_dsi_dphy_interface_config(&rk_mipi); + rk_mipi_dsi_clear_err(&rk_mipi); + rk_mipi_dsi_phy_init(&rk_mipi); + rk_mipi_dsi_wait_for_two_frames(&rk_mipi, edid); + + rk_mipi_dsi_set_mode(&rk_mipi, MIPI_DSI_CMD_MODE); + if (rk_mipi_dsi_dcs_transfer(&rk_mipi, MIPI_DCS_EXIT_SLEEP_MODE) < 0) + return; + mdelay(display_on_mdelay); + if (rk_mipi_dsi_dcs_transfer(&rk_mipi, MIPI_DCS_SET_DISPLAY_ON) < 0) + return; + mdelay(video_mode_mdelay); + + rk_mipi_dsi_set_mode(&rk_mipi, MIPI_DSI_VID_MODE); +} -- cgit v1.2.3