diff --git a/msm/dp/dp_catalog_v500.c b/msm/dp/dp_catalog_v500.c new file mode 100644 index 000000000000..217c4b7f9f1b --- /dev/null +++ b/msm/dp/dp_catalog_v500.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include "dp_catalog.h" +#include "dp_reg.h" +#include "dp_debug.h" + +#define MMSS_DP_M_OFF (0x8) +#define MMSS_DP_N_OFF (0xC) + +#define dp_catalog_get_priv_v500(x) ({ \ + struct dp_catalog *catalog; \ + catalog = container_of(x, struct dp_catalog, x); \ + container_of(catalog->sub, \ + struct dp_catalog_private_v500, sub); \ +}) + +#define dp_read(x) ({ \ + catalog->sub.read(catalog->dpc, io_data, x); \ +}) + +#define dp_write(x, y) ({ \ + catalog->sub.write(catalog->dpc, io_data, x, y); \ +}) + +#define MAX_VOLTAGE_LEVELS 4 +#define MAX_PRE_EMP_LEVELS 4 + +enum { + TX_DRIVE_MODE_LOW_SWING_LOW_HBR = 0, + TX_DRIVE_MODE_HIGH_SWING_LOW_HBR, + TX_DRIVE_MODE_LOW_SWING_HIGH_HBR, + TX_DRIVE_MODE_HIGH_SWING_HIGH_HBR, + TX_DRIVE_MODE_DP, + TX_DRIVE_MODE_MINIDP, + TX_DRIVE_MODE_MAX, +}; + +static u8 const ldo_config[TX_DRIVE_MODE_MAX] = { + 0x81, /* 600mV */ + 0x00, /* off */ + 0x41, /* 650mV */ + 0x00, /* off */ + 0x00, /* off */ + 0x00, /* off */ +}; + +static u8 const vm_pre_emphasis + [TX_DRIVE_MODE_MAX][MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = { + /* pe0, 0 db; pe1, 2.0 db; pe2, 3.6 db; pe3, 6.0 db */ + { // Low swing/pre-emphasis, low HBR + {0x05, 0x11, 0x17, 0x1D}, /* sw0, 0.2v */ + {0x05, 0x11, 0x18, 0xFF}, /* sw1, 0.25 v */ + {0x06, 0x11, 0xFF, 0xFF}, /* sw2, 0.3 v */ + {0x00, 0xFF, 0xFF, 0xFF} /* sw3, 0.35 v */ + }, + { // High swing/pre-emphasis, low HBR + {0x02, 0x0F, 0x17, 0x1D}, /* sw0, 0.3v */ + {0x02, 0x0F, 0x17, 0xFF}, /* sw1, 0.35v */ + {0x02, 0x0E, 0xFF, 0xFF}, /* sw2, 0.4v */ + {0x00, 0xFF, 0xFF, 0xFF} /* sw3, 0.45v */ + }, + { // Low swing/pre-emphasis, high HBR + {0x0C, 0x15, 0x19, 0x1E}, /* sw0, 0.2v */ + {0x09, 0x14, 0x19, 0xFF}, /* sw1, 0.25v */ + {0x0F, 0x14, 0xFF, 0xFF}, /* sw2, 0.3v */ + {0x0D, 0xFF, 0xFF, 0xFF} /* sw3, 0.35v */ + }, + { // High swing/pre-emphasis, high HBR + {0x08, 0x11, 0x16, 0x1B}, /* sw0, 0.3v */ + {0x00, 0x0C, 0x13, 0xFF}, /* sw1, 0.35v */ + {0x05, 0x10, 0xFF, 0xFF}, /* sw2, 0.4v */ + {0x00, 0xFF, 0xFF, 0xFF} /* sw3, 0.45v */ + }, + { // DP-only, DP/USB + {0x20, 0x2E, 0x35, 0xFF}, /* sw0, 0.4v */ + {0x20, 0x2E, 0x35, 0xFF}, /* sw1, 0.6v */ + {0x20, 0x2E, 0xFF, 0xFF}, /* sw2, 0.8v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw3, 1.2 v, optional */ + }, + { // MiniDP-only + {0x00, 0x0E, 0x17, 0xFF}, /* sw0, 0.4v */ + {0x00, 0x0D, 0x16, 0xFF}, /* sw1, 0.6v */ + {0x00, 0x0D, 0xFF, 0xFF}, /* sw2, 0.8v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw3, 1.2 v, optional */ + }, +}; + +/* voltage swing, 0.2v and 1.0v are not support */ +static u8 const vm_voltage_swing + [TX_DRIVE_MODE_MAX][MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = { + /* pe0, 0 db; pe1, 2.0 db; pe2, 3.6 db; pe3, 6.0 db */ + { // Low swing/pre-emphasis, low HBR + {0x07, 0x0F, 0x16, 0x1F}, /* sw0, 0.2v */ + {0x0D, 0x16, 0x1E, 0xFF}, /* sw1, 0.25 v */ + {0x11, 0x1B, 0xFF, 0xFF}, /* sw1, 0.3 v */ + {0x16, 0xFF, 0xFF, 0xFF} /* sw1, 0.35 v */ + }, + { // High swing/pre-emphasis, low HBR + {0x05, 0x0C, 0x14, 0x1D}, /* sw0, 0.3v */ + {0x08, 0x13, 0x1B, 0xFF}, /* sw1, 0.35 v */ + {0x0C, 0x17, 0xFF, 0xFF}, /* sw1, 0.4 v */ + {0x14, 0xFF, 0xFF, 0xFF} /* sw1, 0.45 v */ + }, + { // Low swing/pre-emphasis, high HBR + {0x06, 0x11, 0x16, 0x1B}, /* sw0, 0.2v */ + {0x0B, 0x19, 0x1F, 0xFF}, /* sw1, 0.25 v */ + {0x18, 0x1F, 0xFF, 0xFF}, /* sw1, 0.3 v */ + {0x1F, 0xFF, 0xFF, 0xFF} /* sw1, 0.35 v */ + }, + { // High swing/pre-emphasis, high HBR + {0x0A, 0x11, 0x17, 0x1F}, /* sw0, 0.3v */ + {0x0C, 0x14, 0x1D, 0xFF}, /* sw1, 0.35 v */ + {0x15, 0x1F, 0xFF, 0xFF}, /* sw1, 0.4 v */ + {0x17, 0xFF, 0xFF, 0xFF} /* sw1, 0.45 v */ + }, + { // DP-only, DP/USB + {0x27, 0x2F, 0x36, 0xFF}, /* sw0, 0.4v */ + {0x31, 0x3E, 0x3F, 0xFF}, /* sw1, 0.6v */ + {0x3A, 0x3F, 0xFF, 0xFF}, /* sw2, 0.8v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw3, 1.2 v, optional */ + }, + { // MiniDP-only + {0x09, 0x17, 0x1F, 0xFF}, /* sw0, 0.4v */ + {0x11, 0x1D, 0x1F, 0xFF}, /* sw1, 0.6v */ + {0x1C, 0x1F, 0xFF, 0xFF}, /* sw2, 0.8v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw3, 1.2 v, optional */ + }, +}; + +struct dp_catalog_private_v500 { + struct device *dev; + struct dp_catalog_sub sub; + struct dp_catalog_io *io; + struct dp_catalog *dpc; + + struct dp_catalog_ctrl dp_ctrl; + struct dp_catalog_audio dp_audio; +}; + +static void dp_catalog_aux_setup_v500(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg) +{ + struct dp_catalog_private_v500 *catalog; + struct dp_io_data *io_data; + u32 revision_id = 0; + int i = 0; + + if (!aux || !cfg) { + DP_ERR("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v500(aux); + + io_data = catalog->io->dp_phy; + /* PHY will not work if DP_PHY_MODE is not set */ + revision_id = (dp_read(DP_PHY_REVISION_ID3) & 0xFF) << 8; + revision_id |= (dp_read(DP_PHY_REVISION_ID2) & 0xFF); + DP_DEBUG("DP phy revision_id: 0x%X\n", revision_id); + if (revision_id > 0x5000) + dp_write(DP_PHY_MODE_V500, 0xfc); + else + dp_write(DP_PHY_MODE, 0xfc); + + dp_write(DP_PHY_PD_CTL_V500, 0x7d); + wmb(); /* make sure PD programming happened */ + + /* Turn on BIAS current for PHY/PLL */ + io_data = catalog->io->dp_pll; + dp_write(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, + 0x17); + wmb(); /* make sure BIAS programming happened */ + + io_data = catalog->io->dp_phy; + /* DP AUX CFG register programming */ + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { + DP_DEBUG("%s: offset=0x%08x, value=0x%08x\n", + dp_phy_aux_config_type_to_string(i), + cfg[i].offset, cfg[i].lut[cfg[i].current_index]); + dp_write(cfg[i].offset, + cfg[i].lut[cfg[i].current_index]); + } + wmb(); /* make sure DP AUX CFG programming happened */ + + dp_write(DP_PHY_AUX_INTERRUPT_MASK_V500, + 0x1F); +} + +static void dp_catalog_aux_clear_hw_interrupts_v500(struct dp_catalog_aux *aux) +{ + struct dp_catalog_private_v500 *catalog; + struct dp_io_data *io_data; + u32 data = 0; + + if (!aux) { + DP_ERR("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v500(aux); + io_data = catalog->io->dp_phy; + + data = dp_read(DP_PHY_AUX_INTERRUPT_STATUS_V500); + + dp_write(DP_PHY_AUX_INTERRUPT_CLEAR_V500, 0x1f); + wmb(); /* make sure 0x1f is written before next write */ + dp_write(DP_PHY_AUX_INTERRUPT_CLEAR_V500, 0x9f); + wmb(); /* make sure 0x9f is written before next write */ + dp_write(DP_PHY_AUX_INTERRUPT_CLEAR_V500, 0); + wmb(); /* make sure register is cleared */ +} + +static void dp_catalog_panel_config_msa_v500(struct dp_catalog_panel *panel, + u32 rate, u32 stream_rate_khz) +{ + u32 pixel_m, pixel_n; + u32 mvid, nvid, reg_off = 0, mvid_off = 0, nvid_off = 0; + u32 const nvid_fixed = 0x8000; + u32 const link_rate_hbr2 = 540000; + u32 const link_rate_hbr3 = 810000; + struct dp_catalog_private_v500 *catalog; + struct dp_io_data *io_data; + + if (!panel || !rate) { + DP_ERR("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + DP_ERR("invalid stream id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv_v500(panel); + io_data = catalog->io->dp_mmss_cc; + + if (panel->stream_id == DP_STREAM_1) + reg_off = catalog->dpc->parser->pixel_base_off[1]; + else + reg_off = catalog->dpc->parser->pixel_base_off[0]; + + pixel_m = dp_read(reg_off + MMSS_DP_M_OFF); + pixel_n = dp_read(reg_off + MMSS_DP_N_OFF); + DP_DEBUG("pixel_m=0x%x, pixel_n=0x%x\n", pixel_m, pixel_n); + + mvid = (pixel_m & 0xFFFF) * 5; + nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF); + + if (nvid < nvid_fixed) { + u32 temp; + + temp = (nvid_fixed / nvid) * nvid; + mvid = (nvid_fixed / nvid) * mvid; + nvid = temp; + } + + DP_DEBUG("rate = %d\n", rate); + + if (panel->widebus_en) + mvid <<= 1; + + if (link_rate_hbr2 == rate) + nvid *= 2; + + if (link_rate_hbr3 == rate) + nvid *= 3; + + io_data = catalog->io->dp_link; + + if (panel->stream_id == DP_STREAM_1) { + mvid_off = DP1_SOFTWARE_MVID - DP_SOFTWARE_MVID; + nvid_off = DP1_SOFTWARE_NVID - DP_SOFTWARE_NVID; + } + + DP_DEBUG("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + dp_write(DP_SOFTWARE_MVID + mvid_off, mvid); + dp_write(DP_SOFTWARE_NVID + nvid_off, nvid); +} + +static void dp_catalog_ctrl_phy_lane_cfg_v500(struct dp_catalog_ctrl *ctrl, + bool flipped, u8 ln_cnt) +{ + u32 info = 0x0; + struct dp_catalog_private_v500 *catalog; + u8 orientation = BIT(!!flipped); + struct dp_io_data *io_data; + + if (!ctrl) { + DP_ERR("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v500(ctrl); + io_data = catalog->io->dp_phy; + + info |= (ln_cnt & 0x0F); + info |= ((orientation & 0x0F) << 4); + DP_DEBUG("Shared Info = 0x%x\n", info); + + dp_write(DP_PHY_SPARE0_V500, info); +} + +static void dp_catalog_ctrl_update_vx_px_v500(struct dp_catalog_ctrl *ctrl, + u8 v_level, u8 p_level, bool high) +{ + struct dp_catalog *dp_catalog; + struct dp_catalog_private_v500 *catalog; + struct dp_io_data *io_data; + u8 value0, value1, ldo_cfg; + int index; + + if (!ctrl || !((v_level < MAX_VOLTAGE_LEVELS) + && (p_level < MAX_PRE_EMP_LEVELS))) { + DP_ERR("invalid input\n"); + return; + } + + dp_catalog = container_of(ctrl, struct dp_catalog, ctrl); + + catalog = dp_catalog_get_priv_v500(ctrl); + + DP_DEBUG("hw: v=%d p=%d\n", v_level, p_level); + + io_data = catalog->io->dp_phy; + + switch (dp_catalog->parser->hw_cfg.phy_mode) { + case DP_PHY_MODE_DP: + case DP_PHY_MODE_UNKNOWN: + index = TX_DRIVE_MODE_DP; + break; + case DP_PHY_MODE_MINIDP: + index = TX_DRIVE_MODE_MINIDP; + break; + case DP_PHY_MODE_EDP: + default: + if (!high) + index = TX_DRIVE_MODE_LOW_SWING_LOW_HBR; + else + index = TX_DRIVE_MODE_LOW_SWING_HIGH_HBR; + break; + case DP_PHY_MODE_EDP_HIGH_SWING: + if (!high) + index = TX_DRIVE_MODE_HIGH_SWING_LOW_HBR; + else + index = TX_DRIVE_MODE_HIGH_SWING_HIGH_HBR; + break; + } + + value0 = vm_voltage_swing[index][v_level][p_level]; + value1 = vm_pre_emphasis[index][v_level][p_level]; + ldo_cfg = ldo_config[index]; + + /* program default setting first */ + io_data = catalog->io->dp_ln_tx0; + dp_write(TXn_LDO_CONFIG_V500, 0x01); + dp_write(TXn_TX_DRV_LVL_V500, 0x17); + dp_write(TXn_TX_EMP_POST1_LVL_V500, 0x00); + + io_data = catalog->io->dp_ln_tx1; + dp_write(TXn_LDO_CONFIG_V500, 0x01); + dp_write(TXn_TX_DRV_LVL_V500, 0x2A); + dp_write(TXn_TX_EMP_POST1_LVL_V500, 0x20); + + /* Configure host and panel only if both values are allowed */ + if (value0 != 0xFF && value1 != 0xFF) { + io_data = catalog->io->dp_ln_tx0; + dp_write(TXn_LDO_CONFIG_V500, + ldo_cfg); + dp_write(TXn_TX_DRV_LVL_V500, + value0); + dp_write(TXn_TX_EMP_POST1_LVL_V500, + value1); + + io_data = catalog->io->dp_ln_tx1; + dp_write(TXn_LDO_CONFIG_V500, + ldo_cfg); + dp_write(TXn_TX_DRV_LVL_V500, + value0); + dp_write(TXn_TX_EMP_POST1_LVL_V500, + value1); + + DP_DEBUG("hw: vx_value=0x%x px_value=0x%x ldo_value=0x%x\n", + value0, value1, ldo_cfg); + } else { + DP_ERR("invalid vx (0x%x=0x%x), px (0x%x=0x%x)\n", + v_level, value0, p_level, value1); + } +} + +static void dp_catalog_ctrl_lane_pnswap_v500(struct dp_catalog_ctrl *ctrl, + u8 ln_pnswap) +{ + struct dp_catalog_private_v500 *catalog; + struct dp_io_data *io_data; + u32 cfg0, cfg1; + + catalog = dp_catalog_get_priv_v500(ctrl); + + cfg0 = 0x00; + cfg1 = 0x00; + + cfg0 |= ((ln_pnswap >> 0) & 0x1) << 0; + cfg0 |= ((ln_pnswap >> 1) & 0x1) << 1; + cfg1 |= ((ln_pnswap >> 2) & 0x1) << 0; + cfg1 |= ((ln_pnswap >> 3) & 0x1) << 1; + + io_data = catalog->io->dp_ln_tx0; + dp_write(TXn_TX_POL_INV_V500, cfg0); + + io_data = catalog->io->dp_ln_tx1; + dp_write(TXn_TX_POL_INV_V500, cfg1); +} + +static void dp_catalog_ctrl_usb_reset_v500(struct dp_catalog_ctrl *ctrl, + bool flip) +{ + // USB isn't available +} + +static int dp_catalog_ctrl_late_phy_init_v500(struct dp_catalog_ctrl *ctrl, + u8 lane_cnt, bool flipped) +{ + // No late init + return 0; +} + +static void dp_catalog_put_v500(struct dp_catalog *catalog) +{ + struct dp_catalog_private_v500 *catalog_priv; + + if (!catalog) + return; + + catalog_priv = container_of(catalog->sub, + struct dp_catalog_private_v500, sub); +} + +struct dp_catalog_sub *dp_catalog_get_v500(struct device *dev, + struct dp_catalog *catalog, struct dp_catalog_io *io) +{ + struct dp_catalog_private_v500 *catalog_priv; + + if (!dev || !catalog) { + DP_ERR("invalid input\n"); + return ERR_PTR(-EINVAL); + } + + catalog_priv = devm_kzalloc(dev, sizeof(*catalog_priv), GFP_KERNEL); + if (!catalog_priv) + return ERR_PTR(-ENOMEM); + + catalog_priv->dev = dev; + catalog_priv->io = io; + catalog_priv->dpc = catalog; + catalog_priv->dp_ctrl = catalog->ctrl; + catalog_priv->dp_audio = catalog->audio; + catalog_priv->sub.put = dp_catalog_put_v500; + + catalog->aux.setup = dp_catalog_aux_setup_v500; + catalog->aux.clear_hw_interrupts = + dp_catalog_aux_clear_hw_interrupts_v500; + catalog->panel.config_msa = dp_catalog_panel_config_msa_v500; + + catalog->ctrl.phy_lane_cfg = dp_catalog_ctrl_phy_lane_cfg_v500; + catalog->ctrl.update_vx_px = dp_catalog_ctrl_update_vx_px_v500; + catalog->ctrl.lane_pnswap = dp_catalog_ctrl_lane_pnswap_v500; + catalog->ctrl.usb_reset = dp_catalog_ctrl_usb_reset_v500; + catalog->ctrl.late_phy_init = dp_catalog_ctrl_late_phy_init_v500; + + return &catalog_priv->sub; +} diff --git a/msm/dp/edp_pll_7nm.c b/msm/dp/edp_pll_7nm.c new file mode 100644 index 000000000000..13e15c82035a --- /dev/null +++ b/msm/dp/edp_pll_7nm.c @@ -0,0 +1,922 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +/* + * Display Port PLL driver block diagram for branch clocks + * + * +------------------------+ +------------------------+ + * | dp_phy_pll_link_clk | | dp_phy_pll_vco_div_clk | + * +------------------------+ +------------------------+ + * | | + * | | + * V V + * dp_link_clk dp_pixel_clk + * + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "clk-regmap-mux.h" +#include "dp_hpd.h" +#include "dp_debug.h" +#include "dp_pll.h" + +#define DP_PHY_CFG 0x0010 +#define DP_PHY_CFG_1 0x0014 +#define DP_PHY_PD_CTL 0x001C +#define DP_PHY_MODE 0x0020 + +#define DP_PHY_AUX_CFG2 0x002C + +#define DP_PHY_VCO_DIV 0x0074 +#define DP_PHY_TX0_TX1_LANE_CTL 0x007C +#define DP_PHY_TX2_TX3_LANE_CTL 0x00A0 + +#define DP_PHY_SPARE0 0x00CC +#define DP_PHY_STATUS 0x00E0 + +/* Tx registers */ +#define TXn_CLKBUF_ENABLE 0x0000 +#define TXn_TX_EMP_POST1_LVL 0x0004 +#define TXn_TX_DRV_LVL 0x0014 +#define TXn_TX_DRV_LVL_OFFSET 0x0018 +#define TXn_RESET_TSYNC_EN 0x001C +#define TXn_LDO_CONFIG 0x0084 +#define TXn_TX_BAND 0x0028 +#define TXn_INTERFACE_SELECT 0x0024 +#define TXn_RES_CODE_LANE_OFFSET_TX0 0x0044 +#define TXn_RES_CODE_LANE_OFFSET_TX1 0x0048 +#define TXn_TRANSCEIVER_BIAS_EN 0x0054 +#define TXn_HIGHZ_DRVR_EN 0x0058 +#define TXn_TX_POL_INV 0x005C +#define TXn_PARRATE_REC_DETECT_IDLE_EN 0x0060 +#define TXn_LANE_MODE_1 0x0064 +#define TXn_TRAN_DRVR_EMP_EN 0x0078 +#define TXn_VMODE_CTRL1 0x007C + +/* PLL register offset */ +#define QSERDES_COM_BG_TIMER 0x000C +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x0044 +#define QSERDES_COM_CLK_ENABLE1 0x0048 +#define QSERDES_COM_SYS_CLK_CTRL 0x004C +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x0050 +#define QSERDES_COM_PLL_IVCO 0x0058 + +#define QSERDES_COM_CP_CTRL_MODE0 0x0074 +#define QSERDES_COM_PLL_RCTRL_MODE0 0x007C +#define QSERDES_COM_PLL_CCTRL_MODE0 0x0084 +#define QSERDES_COM_SYSCLK_EN_SEL 0x0094 +#define QSERDES_COM_RESETSM_CNTRL 0x009C +#define QSERDES_COM_LOCK_CMP_EN 0x00A4 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x00AC +#define QSERDES_COM_LOCK_CMP2_MODE0 0x00B0 + +#define QSERDES_COM_DEC_START_MODE0 0x00BC +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x00CC +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x00D0 +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x00D4 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x00EC +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x00F0 +#define QSERDES_COM_VCO_TUNE_CTRL 0x0108 +#define QSERDES_COM_VCO_TUNE_MAP 0x010C +#define QSERDES_COM_VCO_TUNE1_MODE0 0x0110 +#define QSERDES_COM_VCO_TUNE2_MODE0 0x0114 +#define QSERDES_COM_CMN_STATUS 0x0140 + +#define QSERDES_COM_CLK_SEL 0x0154 +#define QSERDES_COM_HSCLK_SEL 0x0158 + +#define QSERDES_COM_CORECLK_DIV_MODE0 0x0168 + +#define QSERDES_COM_CORE_CLK_EN 0x0174 +#define QSERDES_COM_C_READY_STATUS 0x0178 +#define QSERDES_COM_CMN_CONFIG 0x017C + +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x0184 + +#define QSERDES_COM_SSC_EN_CENTER 0x0010 +#define QSERDES_COM_SSC_ADJ_PER1 0x0014 +#define QSERDES_COM_SSC_ADJ_PER2 0x0018 +#define QSERDES_COM_SSC_PER1 0x001C +#define QSERDES_COM_SSC_PER2 0x0020 +#define QSERDES_COM_SSC_STEP_SIZE1_MODE0 0x0024 +#define QSERDES_COM_SSC_STEP_SIZE2_MODE0 0x0028 + +#define DP_PHY_PLL_POLL_SLEEP_US 500 +#define DP_PHY_PLL_POLL_TIMEOUT_US 10000 + +#define DP_VCO_RATE_8100MHZDIV1000 8100000UL +#define DP_VCO_RATE_9720MHZDIV1000 9720000UL +#define DP_VCO_RATE_10800MHZDIV1000 10800000UL + +#define DP_7NM_C_READY BIT(0) +#define DP_7NM_FREQ_DONE BIT(0) +#define DP_7NM_PLL_LOCKED BIT(1) +#define DP_7NM_PHY_READY BIT(1) +#define DP_7NM_TSYNC_DONE BIT(0) + +static int edp_vco_pll_init_db_7nm(struct dp_pll_db *pdb, + unsigned long rate) +{ + struct dp_pll *pll = pdb->pll; + u32 spare_value = 0; + + spare_value = dp_pll_read(dp_phy, DP_PHY_SPARE0); + pdb->lane_cnt = spare_value & 0x0F; + pdb->orientation = (spare_value & 0xF0) >> 4; + + DP_DEBUG("spare_value=0x%x, ln_cnt=0x%x, orientation=0x%x\n", + spare_value, pdb->lane_cnt, pdb->orientation); + + pdb->div_frac_start1_mode0 = 0x00; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + + switch (rate) { + case DP_VCO_HSCLK_RATE_1620MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_9720MHZDIV1000); + pdb->hsclk_sel = 0x05; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->lock_cmp1_mode0 = 0x6f; + pdb->lock_cmp2_mode0 = 0x08; + pdb->phy_vco_div = 0x02; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0x45; + pdb->ssc_step_size2_mode0 = 0x06; + break; + case DP_VCO_HSCLK_RATE_2160MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x04; + pdb->dec_start_mode0 = 0x70; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x08; + pdb->lock_cmp1_mode0 = 0x3f; + pdb->lock_cmp2_mode0 = 0x0b; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0xb0; + pdb->ssc_step_size2_mode0 = 0x06; + break; + case DP_VCO_HSCLK_RATE_2430MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x04; + pdb->dec_start_mode0 = 0x7e; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x09; + pdb->lock_cmp1_mode0 = 0xa7; + pdb->lock_cmp2_mode0 = 0x0c; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0x86; + pdb->ssc_step_size2_mode0 = 0x07; + break; + case DP_VCO_HSCLK_RATE_2700MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x03; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->lock_cmp1_mode0 = 0x0f; + pdb->lock_cmp2_mode0 = 0x0e; + pdb->phy_vco_div = 0x01; + pdb->lock_cmp_en = 0x08; + pdb->ssc_step_size1_mode0 = 0x45; + pdb->ssc_step_size2_mode0 = 0x06; + break; + case DP_VCO_HSCLK_RATE_3240MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x03; + pdb->dec_start_mode0 = 0x7e; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x09; + pdb->lock_cmp1_mode0 = 0xdf; + pdb->lock_cmp2_mode0 = 0x10; + pdb->phy_vco_div = 0x2; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0x86; + pdb->ssc_step_size2_mode0 = 0x07; + break; + case DP_VCO_HSCLK_RATE_4320MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x01; + pdb->dec_start_mode0 = 0x70; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x08; + pdb->lock_cmp1_mode0 = 0x7f; + pdb->lock_cmp2_mode0 = 0x16; + pdb->phy_vco_div = 0x2; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0xb0; + pdb->ssc_step_size2_mode0 = 0x06; + break; + case DP_VCO_HSCLK_RATE_5400MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x01; + pdb->dec_start_mode0 = 0x8c; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x0a; + pdb->lock_cmp1_mode0 = 0x1f; + pdb->lock_cmp2_mode0 = 0x1c; + pdb->phy_vco_div = 0x02; + pdb->lock_cmp_en = 0x08; + pdb->ssc_step_size1_mode0 = 0x5c; + pdb->ssc_step_size2_mode0 = 0x08; + break; + case DP_VCO_HSCLK_RATE_5940MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_8100MHZDIV1000); + pdb->hsclk_sel = 0x01; + pdb->dec_start_mode0 = 0x9a; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x0b; + pdb->lock_cmp1_mode0 = 0xef; + pdb->lock_cmp2_mode0 = 0x1e; + pdb->phy_vco_div = 0x00; + pdb->lock_cmp_en = 0x04; + pdb->ssc_step_size1_mode0 = 0x33; + pdb->ssc_step_size2_mode0 = 0x09; + break; + case DP_VCO_HSCLK_RATE_8100MHZDIV1000: + DP_DEBUG("VCO rate: %ld\n", DP_VCO_RATE_8100MHZDIV1000); + pdb->hsclk_sel = 0x00; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->lock_cmp1_mode0 = 0x2f; + pdb->lock_cmp2_mode0 = 0x2a; + pdb->phy_vco_div = 0x00; + pdb->lock_cmp_en = 0x08; + pdb->ssc_step_size1_mode0 = 0x45; + pdb->ssc_step_size2_mode0 = 0x06; + break; + default: + DP_ERR("unsupported rate %ld\n", rate); + return -EINVAL; + } + return 0; +} + +static int edp_config_vco_rate_7nm(struct dp_pll *pll, + unsigned long rate) +{ + int rc; + struct dp_pll_db *pdb = &pll->pll_db; + u32 status; + + rc = edp_vco_pll_init_db_7nm(pdb, rate); + if (rc < 0) { + DP_ERR("VCO Init DB failed\n"); + return rc; + } + + dp_pll_write(dp_phy, DP_PHY_PD_CTL, 0x7d); + dp_pll_write(dp_phy, DP_PHY_MODE, 0xfc); + /* Make sure the PLL register writes are done */ + wmb(); + + if (readl_poll_timeout_atomic((dp_pll_get_base(dp_pll) + + QSERDES_COM_CMN_STATUS), + status, ((status & BIT(7)) > 0), + 5, 100)) { + DP_ERR("refgen not ready. Status=%x\n", status); + } + + dp_pll_write(dp_ln_tx0, TXn_LDO_CONFIG, 0x01); + dp_pll_write(dp_ln_tx1, TXn_LDO_CONFIG, 0x01); + dp_pll_write(dp_ln_tx0, TXn_LANE_MODE_1, 0x00); + dp_pll_write(dp_ln_tx1, TXn_LANE_MODE_1, 0x00); + + if (pll->ssc_en) { + dp_pll_write(dp_pll, QSERDES_COM_SSC_EN_CENTER, 0x01); + dp_pll_write(dp_pll, QSERDES_COM_SSC_ADJ_PER1, 0x00); + dp_pll_write(dp_pll, QSERDES_COM_SSC_PER1, 0x36); + dp_pll_write(dp_pll, QSERDES_COM_SSC_PER2, 0x01); + dp_pll_write(dp_pll, QSERDES_COM_SSC_STEP_SIZE1_MODE0, + pdb->ssc_step_size1_mode0); + dp_pll_write(dp_pll, QSERDES_COM_SSC_STEP_SIZE2_MODE0, + pdb->ssc_step_size2_mode0); + } + + dp_pll_write(dp_pll, QSERDES_COM_SVS_MODE_CLK_SEL, 0x01); + dp_pll_write(dp_pll, QSERDES_COM_SYSCLK_EN_SEL, 0x0b); + dp_pll_write(dp_pll, QSERDES_COM_SYS_CLK_CTRL, 0x02); + dp_pll_write(dp_pll, QSERDES_COM_CLK_ENABLE1, 0x0c); + dp_pll_write(dp_pll, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x06); + dp_pll_write(dp_pll, QSERDES_COM_CLK_SEL, 0x30); + dp_pll_write(dp_pll, + QSERDES_COM_HSCLK_SEL, pdb->hsclk_sel); + dp_pll_write(dp_pll, QSERDES_COM_PLL_IVCO, 0x07); + dp_pll_write(dp_pll, + QSERDES_COM_LOCK_CMP_EN, pdb->lock_cmp_en); + dp_pll_write(dp_pll, QSERDES_COM_PLL_CCTRL_MODE0, 0x36); + dp_pll_write(dp_pll, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); + dp_pll_write(dp_pll, QSERDES_COM_CP_CTRL_MODE0, 0x06); + dp_pll_write(dp_pll, + QSERDES_COM_DEC_START_MODE0, pdb->dec_start_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_DIV_FRAC_START1_MODE0, pdb->div_frac_start1_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_DIV_FRAC_START2_MODE0, pdb->div_frac_start2_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_DIV_FRAC_START3_MODE0, pdb->div_frac_start3_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_CMN_CONFIG, 0x02); + dp_pll_write(dp_pll, + QSERDES_COM_INTEGLOOP_GAIN0_MODE0, pdb->integloop_gain0_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_INTEGLOOP_GAIN1_MODE0, pdb->integloop_gain1_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_VCO_TUNE_MAP, 0x00); + dp_pll_write(dp_pll, + QSERDES_COM_LOCK_CMP1_MODE0, pdb->lock_cmp1_mode0); + dp_pll_write(dp_pll, + QSERDES_COM_LOCK_CMP2_MODE0, pdb->lock_cmp2_mode0); + dp_pll_write(dp_phy, DP_PHY_VCO_DIV, pdb->phy_vco_div); + /* Make sure the PLL register writes are done */ + wmb(); + + dp_pll_write(dp_pll, QSERDES_COM_BG_TIMER, 0x0a); + dp_pll_write(dp_pll, QSERDES_COM_CORECLK_DIV_MODE0, 0x14); + dp_pll_write(dp_pll, QSERDES_COM_VCO_TUNE_CTRL, 0x00); + dp_pll_write(dp_pll, + QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x17); + dp_pll_write(dp_pll, QSERDES_COM_CORE_CLK_EN, 0x0f); + /* Make sure the PHY register writes are done */ + wmb(); + + /* TX Lane configuration */ + dp_pll_write(dp_phy, DP_PHY_TX0_TX1_LANE_CTL, 0x05); + dp_pll_write(dp_phy, DP_PHY_TX2_TX3_LANE_CTL, 0x05); + + /* TX-0 register configuration */ + dp_pll_write(dp_ln_tx0, TXn_TRANSCEIVER_BIAS_EN, 0x03); + dp_pll_write(dp_ln_tx0, TXn_CLKBUF_ENABLE, 0x0f); + dp_pll_write(dp_ln_tx0, TXn_RESET_TSYNC_EN, 0x03); + dp_pll_write(dp_ln_tx0, TXn_TRAN_DRVR_EMP_EN, 0x01); + dp_pll_write(dp_ln_tx0, TXn_TX_BAND, 0x4); + + /* TX-1 register configuration */ + dp_pll_write(dp_ln_tx1, TXn_TRANSCEIVER_BIAS_EN, 0x03); + dp_pll_write(dp_ln_tx1, TXn_CLKBUF_ENABLE, 0x0f); + dp_pll_write(dp_ln_tx1, TXn_RESET_TSYNC_EN, 0x03); + dp_pll_write(dp_ln_tx1, TXn_TRAN_DRVR_EMP_EN, 0x01); + dp_pll_write(dp_ln_tx1, TXn_TX_BAND, 0x4); + /* Make sure the PHY register writes are done */ + wmb(); + + return 0; +} + +enum edp_7nm_pll_status { + C_READY, + FREQ_DONE, + PLL_LOCKED, + PHY_READY, + TSYNC_DONE, +}; + +static char *edp_7nm_pll_get_status_name(enum edp_7nm_pll_status status) +{ + switch (status) { + case C_READY: + return "C_READY"; + case FREQ_DONE: + return "FREQ_DONE"; + case PLL_LOCKED: + return "PLL_LOCKED"; + case PHY_READY: + return "PHY_READY"; + case TSYNC_DONE: + return "TSYNC_DONE"; + default: + return "unknown"; + } +} + +static bool edp_7nm_pll_get_status(struct dp_pll *pll, + enum edp_7nm_pll_status status) +{ + u32 reg, state, bit; + void __iomem *base; + bool success = true; + + switch (status) { + case C_READY: + base = dp_pll_get_base(dp_pll); + reg = QSERDES_COM_C_READY_STATUS; + bit = DP_7NM_C_READY; + break; + case FREQ_DONE: + base = dp_pll_get_base(dp_pll); + reg = QSERDES_COM_CMN_STATUS; + bit = DP_7NM_FREQ_DONE; + break; + case PLL_LOCKED: + base = dp_pll_get_base(dp_pll); + reg = QSERDES_COM_CMN_STATUS; + bit = DP_7NM_PLL_LOCKED; + break; + case PHY_READY: + base = dp_pll_get_base(dp_phy); + reg = DP_PHY_STATUS; + bit = DP_7NM_PHY_READY; + break; + case TSYNC_DONE: + base = dp_pll_get_base(dp_phy); + reg = DP_PHY_STATUS; + bit = DP_7NM_TSYNC_DONE; + break; + default: + return false; + } + + if (readl_poll_timeout_atomic((base + reg), state, + ((state & bit) > 0), + DP_PHY_PLL_POLL_SLEEP_US, + DP_PHY_PLL_POLL_TIMEOUT_US)) { + DP_ERR("%s failed, status=%x\n", + edp_7nm_pll_get_status_name(status), state); + + success = false; + } + + return success; +} + +static int edp_pll_enable_7nm(struct dp_pll *pll) +{ + int rc = 0; + u32 bias_en0, drvr_en0, bias_en1, drvr_en1, phy_cfg_1; + + pll->aux->state &= ~DP_STATE_PLL_LOCKED; + + dp_pll_write(dp_phy, DP_PHY_CFG, 0x01); + dp_pll_write(dp_phy, DP_PHY_CFG, 0x05); + dp_pll_write(dp_phy, DP_PHY_CFG, 0x01); + dp_pll_write(dp_phy, DP_PHY_CFG, 0x09); + dp_pll_write(dp_pll, QSERDES_COM_RESETSM_CNTRL, 0x20); + wmb(); /* Make sure the PLL register writes are done */ + + if (!edp_7nm_pll_get_status(pll, C_READY)) { + rc = -EINVAL; + goto lock_err; + } + + if (!edp_7nm_pll_get_status(pll, FREQ_DONE)) { + rc = -EINVAL; + goto lock_err; + } + + if (!edp_7nm_pll_get_status(pll, PLL_LOCKED)) { + rc = -EINVAL; + goto lock_err; + } + + dp_pll_write(dp_phy, DP_PHY_CFG, 0x19); + /* Make sure the PHY register writes are done */ + wmb(); + + if (pll->pll_db.lane_cnt == 1) { + bias_en0 = 0x01; + bias_en1 = 0x00; + drvr_en0 = 0x06; + drvr_en1 = 0x07; + phy_cfg_1 = 0x01; + } else if (pll->pll_db.lane_cnt == 2) { + bias_en0 = 0x03; + bias_en1 = 0x00; + drvr_en0 = 0x04; + drvr_en1 = 0x07; + phy_cfg_1 = 0x03; + } else { + bias_en0 = 0x03; + bias_en1 = 0x03; + drvr_en0 = 0x04; + drvr_en1 = 0x04; + phy_cfg_1 = 0x0f; + } + + dp_pll_write(dp_ln_tx0, TXn_HIGHZ_DRVR_EN, drvr_en0); + dp_pll_write(dp_ln_tx0, TXn_TX_POL_INV, 0x00); + dp_pll_write(dp_ln_tx1, TXn_HIGHZ_DRVR_EN, drvr_en1); + dp_pll_write(dp_ln_tx1, TXn_TX_POL_INV, 0x00); + dp_pll_write(dp_ln_tx0, TXn_TX_DRV_LVL_OFFSET, 0x10); + dp_pll_write(dp_ln_tx1, TXn_TX_DRV_LVL_OFFSET, 0x10); + dp_pll_write(dp_ln_tx0, + TXn_RES_CODE_LANE_OFFSET_TX0, 0x11); + dp_pll_write(dp_ln_tx0, + TXn_RES_CODE_LANE_OFFSET_TX1, 0x11); + dp_pll_write(dp_ln_tx1, + TXn_RES_CODE_LANE_OFFSET_TX0, 0x11); + dp_pll_write(dp_ln_tx1, + TXn_RES_CODE_LANE_OFFSET_TX1, 0x11); + + dp_pll_write(dp_ln_tx0, TXn_TX_EMP_POST1_LVL, 0x10); + dp_pll_write(dp_ln_tx1, TXn_TX_EMP_POST1_LVL, 0x10); + dp_pll_write(dp_ln_tx0, TXn_TX_DRV_LVL, 0x1f); + dp_pll_write(dp_ln_tx1, TXn_TX_DRV_LVL, 0x1f); + + /* Make sure the PHY register writes are done */ + wmb(); + + dp_pll_write(dp_ln_tx0, TXn_TRANSCEIVER_BIAS_EN, + bias_en0); + dp_pll_write(dp_ln_tx1, TXn_TRANSCEIVER_BIAS_EN, + bias_en1); + dp_pll_write(dp_phy, DP_PHY_CFG_1, phy_cfg_1); + + if (!edp_7nm_pll_get_status(pll, PHY_READY)) { + rc = -EINVAL; + goto lock_err; + } + + dp_pll_write(dp_phy, DP_PHY_CFG, 0x18); + udelay(100); + + dp_pll_write(dp_phy, DP_PHY_CFG, 0x19); + /* Make sure the PHY register writes are done */ + wmb(); + + if (!edp_7nm_pll_get_status(pll, TSYNC_DONE)) { + rc = -EINVAL; + goto lock_err; + } + + if (!edp_7nm_pll_get_status(pll, PHY_READY)) { + rc = -EINVAL; + goto lock_err; + } + + pll->aux->state |= DP_STATE_PLL_LOCKED; + DP_DEBUG("PLL is locked\n"); + +lock_err: + return rc; +} + +static void edp_pll_disable_7nm(struct dp_pll *pll) +{ + /* Assert DP PHY power down */ + dp_pll_write(dp_phy, DP_PHY_PD_CTL, 0x2); + /* + * Make sure all the register writes to disable PLL are + * completed before doing any other operation + */ + wmb(); +} + +static int edp_vco_clk_set_div(struct dp_pll *pll, unsigned int div) +{ + u32 val = 0; + + if (!pll) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + if (is_gdsc_disabled(pll)) + return -EINVAL; + + val = dp_pll_read(dp_phy, DP_PHY_VCO_DIV); + val &= ~0x03; + + switch (div) { + case 2: + val |= 1; + break; + case 4: + val |= 2; + break; + case 6: + /* When div = 6, val is 0, so do nothing here */ + ; + break; + case 8: + val |= 3; + break; + default: + DP_DEBUG("unsupported div value %d\n", div); + return -EINVAL; + } + + dp_pll_write(dp_phy, DP_PHY_VCO_DIV, val); + /* Make sure the PHY registers writes are done */ + wmb(); + + DP_DEBUG("val=%d div=%x\n", val, div); + return 0; +} + +static int edp_vco_set_rate_7nm(struct dp_pll *pll, unsigned long rate) +{ + int rc = 0; + + if (!pll) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + DP_DEBUG("DP lane CLK rate=%ld\n", rate); + + rc = edp_config_vco_rate_7nm(pll, rate); + if (rc < 0) { + DP_ERR("Failed to set clk rate\n"); + return rc; + } + + return rc; +} + +static int edp_pll_configure(struct dp_pll *pll, unsigned long rate) +{ + int rc = 0; + + if (!pll || !rate) { + DP_ERR("invalid input parameters rate = %lu\n", rate); + return -EINVAL; + } + + rate = rate * 10; + + if (rate <= DP_VCO_HSCLK_RATE_1620MHZDIV1000) + rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000; + else if (rate <= DP_VCO_HSCLK_RATE_2700MHZDIV1000) + rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (rate <= DP_VCO_HSCLK_RATE_5400MHZDIV1000) + rate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000; + + rc = edp_vco_set_rate_7nm(pll, rate); + if (rc < 0) { + DP_ERR("pll rate %s set failed\n", rate); + return rc; + } + + pll->vco_cached_rate = rate; + DP_DEBUG("pll rate %lu set success\n", rate); + return rc; +} + +static int edp_pll_prepare(struct dp_pll *pll) +{ + int rc = 0; + + if (!pll) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + rc = edp_pll_enable_7nm(pll); + if (rc < 0) + DP_ERR("ndx=%d failed to enable dp pll\n", pll->index); + + return rc; +} + +static int edp_pll_unprepare(struct dp_pll *pll) +{ + int rc = 0; + + if (!pll) { + DP_ERR("invalid input parameter\n"); + return -EINVAL; + } + + edp_pll_disable_7nm(pll); + + return rc; +} + +static unsigned long edp_pll_link_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dp_pll *pll = NULL; + struct dp_pll_vco_clk *pll_link = NULL; + unsigned long rate = 0; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + pll_link = to_dp_vco_hw(hw); + pll = pll_link->priv; + + rate = pll->vco_cached_rate; + rate = pll->vco_cached_rate * 100; + + return rate; +} + +static long edp_pll_link_clk_round(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + struct dp_pll *pll = NULL; + struct dp_pll_vco_clk *pll_link = NULL; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + pll_link = to_dp_vco_hw(hw); + pll = pll_link->priv; + + rate = pll->vco_cached_rate * 100; + + return rate; +} + +static unsigned long edp_pll_vco_div_clk_get_rate(struct dp_pll *pll) +{ + if (pll->vco_cached_rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000) + return (pll->vco_cached_rate / 6 * 1000); + else if (pll->vco_cached_rate == DP_VCO_HSCLK_RATE_5400MHZDIV1000) + return (pll->vco_cached_rate / 4 * 1000); + else + return (pll->vco_cached_rate / 2 * 1000); +} + +static unsigned long edp_pll_vco_div_clk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dp_pll *pll = NULL; + struct dp_pll_vco_clk *pll_link = NULL; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + pll_link = to_dp_vco_hw(hw); + pll = pll_link->priv; + + return edp_pll_vco_div_clk_get_rate(pll); +} + +static long edp_pll_vco_div_clk_round(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return edp_pll_vco_div_clk_recalc_rate(hw, *parent_rate); +} + +static int edp_pll_vco_div_clk_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct dp_pll *pll = NULL; + struct dp_pll_vco_clk *pll_link = NULL; + int rc = 0; + + if (!hw) { + DP_ERR("invalid input parameters\n"); + return -EINVAL; + } + + pll_link = to_dp_vco_hw(hw); + pll = pll_link->priv; + + if (rate != edp_pll_vco_div_clk_get_rate(pll)) { + DP_ERR("unsupported rate %lu failed\n", rate); + return rc; + } + + rate /= 1000; + + rc = edp_vco_clk_set_div(pll, pll->vco_cached_rate / rate); + if (rc < 0) { + DP_DEBUG("set rate %lu failed\n", rate); + return rc; + } + + DP_DEBUG("set rate %lu success\n", rate); + return 0; +} + +static const struct clk_ops edp_pll_link_clk_ops = { + .recalc_rate = edp_pll_link_clk_recalc_rate, + .round_rate = edp_pll_link_clk_round, +}; + +static const struct clk_ops edp_pll_vco_div_clk_ops = { + .recalc_rate = edp_pll_vco_div_clk_recalc_rate, + .round_rate = edp_pll_vco_div_clk_round, + .set_rate = edp_pll_vco_div_clk_set_rate, +}; + +static struct clk_init_data edp_phy_pll_clks[DP_PLL_NUM_CLKS] = { + { + .name = "_phy_pll_link_clk", + .ops = &edp_pll_link_clk_ops, + }, + { + .name = "_phy_pll_vco_div_clk", + .ops = &edp_pll_vco_div_clk_ops, + }, +}; + +static struct dp_pll_vco_clk *edp_pll_get_clks(struct dp_pll *pll) +{ + int i; + + for (i = 0; i < DP_PLL_NUM_CLKS; i++) { + snprintf(pll->pll_clks[i].name, DP_PLL_NAME_MAX_SIZE, + "%s%s", pll->name, edp_phy_pll_clks[i].name); + pll->pll_clks[i].init_data.name = pll->pll_clks[i].name; + pll->pll_clks[i].init_data.ops = edp_phy_pll_clks[i].ops; + pll->pll_clks[i].hw.init = &pll->pll_clks[i].init_data; + } + + return pll->pll_clks; +} + +static int dp_pll_clock_register_helper(struct dp_pll *pll, + struct dp_pll_vco_clk *clks, int num_clks) +{ + int rc = 0, i = 0; + struct platform_device *pdev; + struct clk *clk; + + if (!pll || !clks) { + DP_ERR("input not initialized\n"); + return -EINVAL; + } + + pdev = pll->pdev; + + for (i = 0; i < num_clks; i++) { + clks[i].priv = pll; + + clk = clk_register(&pdev->dev, &clks[i].hw); + if (IS_ERR(clk)) { + DP_ERR("%s registration failed for DP: %d\n", + clk_hw_get_name(&clks[i].hw), pll->index); + return -EINVAL; + } + pll->clk_data->clks[i] = clk; + } + + return rc; +} + +int edp_pll_clock_register_7nm(struct dp_pll *pll) +{ + int rc = 0; + struct platform_device *pdev; + struct dp_pll_vco_clk *pll_clks; + + if (!pll) { + DP_ERR("pll data not initialized\n"); + return -EINVAL; + } + pdev = pll->pdev; + + pll->clk_data = kzalloc(sizeof(*pll->clk_data), GFP_KERNEL); + if (!pll->clk_data) + return -ENOMEM; + + pll->clk_data->clks = kcalloc(DP_PLL_NUM_CLKS, sizeof(struct clk *), + GFP_KERNEL); + if (!pll->clk_data->clks) { + kfree(pll->clk_data); + return -ENOMEM; + } + + pll->clk_data->clk_num = DP_PLL_NUM_CLKS; + pll->pll_db.pll = pll; + + pll->pll_cfg = edp_pll_configure; + pll->pll_prepare = edp_pll_prepare; + pll->pll_unprepare = edp_pll_unprepare; + + pll_clks = edp_pll_get_clks(pll); + + rc = dp_pll_clock_register_helper(pll, pll_clks, DP_PLL_NUM_CLKS); + if (rc) { + DP_ERR("Clock register failed rc=%d\n", rc); + goto clk_reg_fail; + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, pll->clk_data); + if (rc) { + DP_ERR("Clock add provider failed rc=%d\n", rc); + goto clk_reg_fail; + } + + DP_DEBUG("success\n"); + return rc; + +clk_reg_fail: + dp_pll_clock_unregister_5nm(pll); + return rc; +}