Add the driver for pre-QMP Qualcomm HDMI PHYs. Currently it suppports Qualcomm MSM8960 / APQ8064 platforms, other platforms will come later. Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> --- drivers/phy/qualcomm/Kconfig | 14 + drivers/phy/qualcomm/Makefile | 6 + drivers/phy/qualcomm/phy-qcom-hdmi-28lpm.c | 439 ++++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.c | 206 +++++++++ drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.h | 79 ++++ 5 files changed, 744 insertions(+) create mode 100644 drivers/phy/qualcomm/phy-qcom-hdmi-28lpm.c create mode 100644 drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.c create mode 100644 drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.h diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 127fb2f59a57..f31b575a6832 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -42,6 +42,20 @@ config PHY_QCOM_IPQ806X_SATA depends on OF select GENERIC_PHY +config PHY_QCOM_HDMI + tristate "Qualcomm MSM8x60/MSM8960/MSM8974 HDMI PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + depends on COMMON_CLK + default DRM_MSM_HDMI && ARCH_QCOM && ARM + select GENERIC_PHY + help + Enable this to support the Qualcomm HDMI PHY presend on 32-bit platforms: + MSM8260, MSM8660, MSM8960, APQ8060 and APQ8064. + + Note, this driver is not used on MSM899x platforms, which use + PHY_QCOM_QMP_HDMI instead. + config PHY_QCOM_PCIE2 tristate "Qualcomm PCIe Gen2 PHY Driver" depends on OF && COMMON_CLK && (ARCH_QCOM || COMPILE_TEST) diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index c09828eccc89..de9bbaf45c74 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -4,6 +4,12 @@ obj-$(CONFIG_PHY_QCOM_APQ8064_SATA) += phy-qcom-apq8064-sata.o obj-$(CONFIG_PHY_QCOM_EDP) += phy-qcom-edp.o obj-$(CONFIG_PHY_QCOM_IPQ4019_USB) += phy-qcom-ipq4019-usb.o obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o +obj-$(CONFIG_PHY_QCOM_HDMI) += phy-qcom-hdmi.o + +phy-qcom-hdmi-y := \ + phy-qcom-hdmi-preqmp.o \ + phy-qcom-hdmi-28lpm.o \ + obj-$(CONFIG_PHY_QCOM_M31_USB) += phy-qcom-m31.o obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o diff --git a/drivers/phy/qualcomm/phy-qcom-hdmi-28lpm.c b/drivers/phy/qualcomm/phy-qcom-hdmi-28lpm.c new file mode 100644 index 000000000000..64e043f3f0e2 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-hdmi-28lpm.c @@ -0,0 +1,439 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@xxxxxxxxx> + * Copyright (c) 2023, Linaro Ltd. + */ + +#include <linux/delay.h> +#include <linux/iopoll.h> + +#include "phy-qcom-hdmi-preqmp.h" + +#define REG_HDMI_8960_PHY_REG0 0x00000000 + +#define REG_HDMI_8960_PHY_REG1 0x00000004 + +#define REG_HDMI_8960_PHY_REG2 0x00000008 + +#define REG_HDMI_8960_PHY_REG3 0x0000000c + +#define REG_HDMI_8960_PHY_REG4 0x00000010 + +#define REG_HDMI_8960_PHY_REG5 0x00000014 + +#define REG_HDMI_8960_PHY_REG6 0x00000018 + +#define REG_HDMI_8960_PHY_REG7 0x0000001c + +#define REG_HDMI_8960_PHY_REG8 0x00000020 + +#define REG_HDMI_8960_PHY_REG9 0x00000024 + +#define REG_HDMI_8960_PHY_REG10 0x00000028 + +#define REG_HDMI_8960_PHY_REG11 0x0000002c + +#define REG_HDMI_8960_PHY_REG12 0x00000030 +#define HDMI_8960_PHY_REG12_SW_RESET 0x00000020 +#define HDMI_8960_PHY_REG12_PWRDN_B 0x00000080 + +#define REG_HDMI_8960_PHY_REG_BIST_CFG 0x00000034 + +#define REG_HDMI_8960_PHY_DEBUG_BUS_SEL 0x00000038 + +#define REG_HDMI_8960_PHY_REG_MISC0 0x0000003c + +#define REG_HDMI_8960_PHY_REG13 0x00000040 + +#define REG_HDMI_8960_PHY_REG14 0x00000044 + +#define REG_HDMI_8960_PHY_REG15 0x00000048 + +#define REG_HDMI_8960_PHY_PLL_REFCLK_CFG 0x00000000 + +#define REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG 0x00000004 + +#define REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 0x00000008 + +#define REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 0x0000000c + +#define REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG 0x00000010 + +#define REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG 0x00000014 + +#define REG_HDMI_8960_PHY_PLL_PWRDN_B 0x00000018 +#define HDMI_8960_PHY_PLL_PWRDN_B_PD_PLL 0x00000002 +#define HDMI_8960_PHY_PLL_PWRDN_B_PLL_PWRDN_B 0x00000008 + +#define REG_HDMI_8960_PHY_PLL_SDM_CFG0 0x0000001c + +#define REG_HDMI_8960_PHY_PLL_SDM_CFG1 0x00000020 + +#define REG_HDMI_8960_PHY_PLL_SDM_CFG2 0x00000024 + +#define REG_HDMI_8960_PHY_PLL_SDM_CFG3 0x00000028 + +#define REG_HDMI_8960_PHY_PLL_SDM_CFG4 0x0000002c + +#define REG_HDMI_8960_PHY_PLL_SSC_CFG0 0x00000030 + +#define REG_HDMI_8960_PHY_PLL_SSC_CFG1 0x00000034 + +#define REG_HDMI_8960_PHY_PLL_SSC_CFG2 0x00000038 + +#define REG_HDMI_8960_PHY_PLL_SSC_CFG3 0x0000003c + +#define REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 0x00000040 + +#define REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 0x00000044 + +#define REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 0x00000048 + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 0x0000004c + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 0x00000050 + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 0x00000054 + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 0x00000058 + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 0x0000005c + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 0x00000060 + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 0x00000064 + +#define REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 0x00000068 + +#define REG_HDMI_8960_PHY_PLL_DEBUG_SEL 0x0000006c + +#define REG_HDMI_8960_PHY_PLL_MISC0 0x00000070 + +#define REG_HDMI_8960_PHY_PLL_MISC1 0x00000074 + +#define REG_HDMI_8960_PHY_PLL_MISC2 0x00000078 + +#define REG_HDMI_8960_PHY_PLL_MISC3 0x0000007c + +#define REG_HDMI_8960_PHY_PLL_MISC4 0x00000080 + +#define REG_HDMI_8960_PHY_PLL_MISC5 0x00000084 + +#define REG_HDMI_8960_PHY_PLL_MISC6 0x00000088 + +#define REG_HDMI_8960_PHY_PLL_DEBUG_BUS0 0x0000008c + +#define REG_HDMI_8960_PHY_PLL_DEBUG_BUS1 0x00000090 + +#define REG_HDMI_8960_PHY_PLL_DEBUG_BUS2 0x00000094 + +#define REG_HDMI_8960_PHY_PLL_STATUS0 0x00000098 +#define HDMI_8960_PHY_PLL_STATUS0_PLL_LOCK 0x00000001 + +#define REG_HDMI_8960_PHY_PLL_STATUS1 0x0000009c + +#define HDMI_8974_VCO_MAX_FREQ 1125000000UL +#define HDMI_8974_VCO_MIN_FREQ 540000000UL + +#define HDMI_8974_COMMON_DIV 5 + +static unsigned long qcom_28lpm_recalc(struct qcom_hdmi_preqmp_phy *hdmi_phy, unsigned long parent_rate) +{ + unsigned long rate; + u32 refclk_cfg; + u32 dc_offset; + u64 fraq_n; + u32 val; + + refclk_cfg = hdmi_pll_read(hdmi_phy, REG_HDMI_8960_PHY_PLL_REFCLK_CFG); + if (refclk_cfg & BIT(1)) + parent_rate /= 2; + if (refclk_cfg & BIT(3)) + parent_rate *= 2; + + val = hdmi_pll_read(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG0); + if (val & 0x40) { + dc_offset = val & 0x3f; + fraq_n = 0; + } else { + dc_offset = hdmi_pll_read(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG1) & 0x3f; + fraq_n = hdmi_pll_read(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG2) | + (hdmi_pll_read(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG3) << 8); + } + + rate = (dc_offset + 1) * parent_rate; + rate += mult_frac(fraq_n, parent_rate, 0x10000); + + return rate; +} + +static int qcom_28lpm_set_rate(struct qcom_hdmi_preqmp_phy *hdmi_phy, unsigned long parent_rate, + unsigned long vco_freq, u32 div_idx) +{ + unsigned int pixclk = hdmi_phy->hdmi_opts.pixel_clk_rate; + unsigned int int_ref_freq; + unsigned int div; + unsigned int dc_offset; + unsigned int sdm_freq_seed; + unsigned int val; + bool sdm_mode = false; + u32 refclk_cfg; + u32 lf_cfg0; + u32 lf_cfg1; + + dev_dbg(hdmi_phy->dev, "rate=%u, div = %d, vco = %lu", pixclk, div, vco_freq); + + if (vco_freq % (parent_rate / 2) == 0) { + refclk_cfg = 0x2; + int_ref_freq = parent_rate / 2; + } else { + refclk_cfg = 0x8; + int_ref_freq = parent_rate * 2; + sdm_mode = true; + } + + dc_offset = vco_freq / int_ref_freq - 1; + sdm_freq_seed = vco_freq - (dc_offset + 1) * int_ref_freq; + sdm_freq_seed = mult_frac(sdm_freq_seed, 0x10000, int_ref_freq); + + val = (div_idx << 4) | refclk_cfg; + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_REFCLK_CFG, val); + + lf_cfg0 = dc_offset >= 30 ? 0 : (dc_offset >= 16 ? 0x10 : 0x20); + lf_cfg0 += sdm_mode ? 0 : 1; + + /* XXX: 0xc3 instead of 0x33 for qcs404 */ + lf_cfg1 = dc_offset >= 30 ? 0x33 : (dc_offset >= 16 ? 0xbb : 0xf9); + + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0, lf_cfg0); + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1, lf_cfg1); + + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG0, + (sdm_mode ? 0 : 0x40) | dc_offset); + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG1, + 0x40 | dc_offset); + + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG2, + sdm_freq_seed & 0xff); + + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG3, + (sdm_freq_seed >> 8) & 0xff); + + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_SDM_CFG4, + sdm_freq_seed >> 16); + + vco_freq /= 1000; + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0, vco_freq & 0xff); + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1, vco_freq >> 8); + + hdmi_pll_write(hdmi_phy, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2, 0x3b); + + return 0; +} + +static const unsigned int qcom_hdmi_8974_divs[] = {1, 2, 4, 6}; + +static unsigned long qcom_hdmi_8960_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = hw_clk_to_phy(hw); + u32 div_idx = hdmi_pll_read(hdmi_phy, REG_HDMI_8960_PHY_PLL_REFCLK_CFG); + unsigned long rate = qcom_28lpm_recalc(hdmi_phy, parent_rate); + + return rate / HDMI_8974_COMMON_DIV / qcom_hdmi_8974_divs[div_idx >> 4]; +} + +static long qcom_hdmi_8960_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + return clamp(rate, + HDMI_8974_VCO_MIN_FREQ / HDMI_8974_COMMON_DIV / 6, + HDMI_8974_VCO_MAX_FREQ / HDMI_8974_COMMON_DIV / 1); +} + +static const struct clk_ops qcom_hdmi_8960_pll_ops = { + .recalc_rate = qcom_hdmi_8960_pll_recalc_rate, + .round_rate = qcom_hdmi_8960_pll_round_rate, +}; + +static int qcom_hdmi_msm8960_phy_pll_enable(struct qcom_hdmi_preqmp_phy *phy) +{ + int pll_lock_retry = 10; + unsigned int val; + int ret; + + /* Assert PLL S/W reset */ + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x8d); + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0, 0x10); + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1, 0x1a); + + /* Wait for a short time before de-asserting + * to allow the hardware to complete its job. + * This much of delay should be fine for hardware + * to assert and de-assert. + */ + udelay(10); + + /* De-assert PLL S/W reset */ + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x0d); + + val = hdmi_phy_read(phy, REG_HDMI_8960_PHY_REG12); + val |= HDMI_8960_PHY_REG12_SW_RESET; + /* Assert PHY S/W reset */ + hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val); + val &= ~HDMI_8960_PHY_REG12_SW_RESET; + /* + * Wait for a short time before de-asserting to allow the hardware to + * complete its job. This much of delay should be fine for hardware to + * assert and de-assert. + */ + udelay(10); + /* De-assert PHY S/W reset */ + hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val); + hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG2, 0x3f); + + val = hdmi_phy_read(phy, REG_HDMI_8960_PHY_REG12); + val |= HDMI_8960_PHY_REG12_PWRDN_B; + hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val); + /* Wait 10 us for enabling global power for PHY */ + mb(); + udelay(10); + + val = hdmi_pll_read(phy, REG_HDMI_8960_PHY_PLL_PWRDN_B); + val |= HDMI_8960_PHY_PLL_PWRDN_B_PLL_PWRDN_B; + val &= ~HDMI_8960_PHY_PLL_PWRDN_B_PD_PLL; + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_PWRDN_B, val); + hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG2, 0x80); + + while (--pll_lock_retry > 0) { + ret = readl_poll_timeout(phy->pll_reg + REG_HDMI_8960_PHY_PLL_STATUS0, + val, val & HDMI_8960_PHY_PLL_STATUS0_PLL_LOCK, + 1, 1000); + if (!ret) + break; + + /* + * PLL has still not locked. + * Do a software reset and try again + * Assert PLL S/W reset first + */ + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x8d); + udelay(10); + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x0d); + + /* + * Wait for a short duration for the PLL calibration + * before checking if the PLL gets locked + */ + udelay(350); + } + + return ret; +} + +static int qcom_hdmi_msm8974_phy_find_div(unsigned int pixclk) +{ + int i; + unsigned int min_freq = HDMI_8974_VCO_MIN_FREQ / HDMI_8974_COMMON_DIV / 1000; + + if (pixclk > HDMI_8974_VCO_MAX_FREQ / HDMI_8974_COMMON_DIV / 1000) + return -E2BIG; + + for (i = 0; i < ARRAY_SIZE(qcom_hdmi_8974_divs); i++) { + if (pixclk >= min_freq / qcom_hdmi_8974_divs[i]) + return i; + } + + return -EINVAL; +} + +static int qcom_hdmi_msm8960_phy_set_rate(struct qcom_hdmi_preqmp_phy *hdmi_phy) +{ + unsigned int pixclk = hdmi_phy->hdmi_opts.pixel_clk_rate; + /* XXX: 19.2 for qcs404 */ + unsigned long parent_rate = 27000; + unsigned long vco_freq; + int div_idx; + u32 div; + + div_idx = qcom_hdmi_msm8974_phy_find_div(pixclk); + if (WARN_ON(div_idx < 0)) + return div_idx; + + div = qcom_hdmi_8974_divs[div_idx]; + vco_freq = pixclk * HDMI_8974_COMMON_DIV * div; + + return qcom_28lpm_set_rate(hdmi_phy, parent_rate, vco_freq, div_idx); +} + +static void qcom_hdmi_msm8960_phy_pll_disable(struct qcom_hdmi_preqmp_phy *phy) +{ + unsigned int val; + + val = hdmi_phy_read(phy, REG_HDMI_8960_PHY_REG12); + val &= ~HDMI_8960_PHY_REG12_PWRDN_B; + hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val); + + val = hdmi_pll_read(phy, REG_HDMI_8960_PHY_PLL_PWRDN_B); + val |= HDMI_8960_PHY_REG12_SW_RESET; + val &= ~HDMI_8960_PHY_REG12_PWRDN_B; + hdmi_pll_write(phy, REG_HDMI_8960_PHY_PLL_PWRDN_B, val); + /* Make sure HDMI PHY/PLL are powered down */ + mb(); +} + +static int qcom_hdmi_msm8960_phy_power_on(struct qcom_hdmi_preqmp_phy *hdmi_phy) +{ + int ret; + + ret = qcom_hdmi_msm8960_phy_set_rate(hdmi_phy); + if (ret) + return ret; + + ret = qcom_hdmi_msm8960_phy_pll_enable(hdmi_phy); + if (ret) + return ret; + + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG2, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG0, 0x1b); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG1, 0xf2); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG4, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG5, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG6, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG7, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG8, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG9, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG10, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG11, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG3, 0x20); + + return 0; +} + +static int qcom_hdmi_msm8960_phy_power_off(struct qcom_hdmi_preqmp_phy *hdmi_phy) +{ + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG2, 0x7f); + + qcom_hdmi_msm8960_phy_pll_disable(hdmi_phy); + + return 0; +} + +const struct clk_parent_data msm8960_hdmi_pll_parent = { + .fw_name = "pxo", .name = "pxo_board", +}; + +const struct qcom_hdmi_preqmp_cfg msm8960_hdmi_phy_cfg = { + .clk_names = { "slave_iface" }, + .num_clks = 1, + + .reg_names = { "core-vdda" }, + .num_regs = 1, + + .power_on = qcom_hdmi_msm8960_phy_power_on, + .power_off = qcom_hdmi_msm8960_phy_power_off, + + .pll_ops = &qcom_hdmi_8960_pll_ops, + .pll_parent = &msm8960_hdmi_pll_parent, +}; diff --git a/drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.c b/drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.c new file mode 100644 index 000000000000..6885f587d7b4 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@xxxxxxxxx> + * Copyright (c) 2023, Linaro Ltd. + */ + +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#include "phy-qcom-hdmi-preqmp.h" + +static int qcom_hdmi_preqmp_phy_init(struct phy *phy) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = phy_get_drvdata(phy); + + return pm_runtime_resume_and_get(hdmi_phy->dev); +} + +static int qcom_hdmi_preqmp_phy_exit(struct phy *phy) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = phy_get_drvdata(phy); + + pm_runtime_put_noidle(hdmi_phy->dev); + + return 0; +} + +static int qcom_hdmi_preqmp_phy_power_on(struct phy *phy) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = phy_get_drvdata(phy); + + return hdmi_phy->power_on(hdmi_phy); +}; + +static int qcom_hdmi_preqmp_phy_power_off(struct phy *phy) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = phy_get_drvdata(phy); + + return hdmi_phy->power_off(hdmi_phy); +}; + +static int qcom_hdmi_preqmp_phy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + const struct phy_configure_opts_hdmi *hdmi_opts = &opts->hdmi; + struct qcom_hdmi_preqmp_phy *hdmi_phy = phy_get_drvdata(phy); + int ret = 0; + + memcpy(&hdmi_phy->hdmi_opts, hdmi_opts, sizeof(*hdmi_opts)); + + return ret; +} + +static int __maybe_unused qcom_hdmi_preqmp_runtime_resume(struct device *dev) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(hdmi_phy->num_regs, hdmi_phy->regs); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(hdmi_phy->num_clks, hdmi_phy->clks); + if (ret) + goto out_disable_supplies; + + return 0; + +out_disable_supplies: + regulator_bulk_disable(hdmi_phy->num_regs, hdmi_phy->regs); + + return ret; +} + +static int __maybe_unused qcom_hdmi_preqmp_runtime_suspend(struct device *dev) +{ + struct qcom_hdmi_preqmp_phy *hdmi_phy = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(hdmi_phy->num_clks, hdmi_phy->clks); + regulator_bulk_disable(hdmi_phy->num_regs, hdmi_phy->regs); + + return 0; +} + +static const struct phy_ops qcom_hdmi_preqmp_phy_ops = { + .init = qcom_hdmi_preqmp_phy_init, + .configure = qcom_hdmi_preqmp_phy_configure, + .power_on = qcom_hdmi_preqmp_phy_power_on, + .power_off = qcom_hdmi_preqmp_phy_power_off, + .exit = qcom_hdmi_preqmp_phy_exit, + .owner = THIS_MODULE, +}; + +static int qcom_hdmi_preqmp_probe(struct platform_device *pdev) +{ + struct clk_init_data init; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct qcom_hdmi_preqmp_phy *hdmi_phy; + const struct qcom_hdmi_preqmp_cfg *cfg; + int i, ret; + + cfg = of_device_get_match_data(dev); + if (!cfg) + return -EINVAL; + + hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL); + if (!hdmi_phy) + return -ENOMEM; + + hdmi_phy->dev = dev; + + hdmi_phy->phy_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hdmi_phy->phy_reg)) + return PTR_ERR(hdmi_phy->phy_reg); + + hdmi_phy->pll_reg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(hdmi_phy->pll_reg)) + return PTR_ERR(hdmi_phy->pll_reg); + + hdmi_phy->num_clks = cfg->num_clks; + for (i = 0; i < cfg->num_clks; i++) + hdmi_phy->clks[i].id = cfg->clk_names[i]; + ret = devm_clk_bulk_get(dev, hdmi_phy->num_clks, hdmi_phy->clks); + if (ret) + return ret; + + hdmi_phy->num_regs = cfg->num_regs; + for (i = 0; i < cfg->num_regs; i++) { + hdmi_phy->regs[i].supply = cfg->reg_names[i]; + hdmi_phy->regs[i].init_load_uA = cfg->reg_init_load[i]; + } + ret = devm_regulator_bulk_get(dev, hdmi_phy->num_regs, hdmi_phy->regs); + if (ret) + return ret; + + hdmi_phy->power_on = cfg->power_on; + hdmi_phy->power_off = cfg->power_off; + + platform_set_drvdata(pdev, hdmi_phy); + + ret = devm_pm_runtime_enable(&pdev->dev); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret) + return ret; + + init.name = "hdmipll"; + init.ops = cfg->pll_ops; + init.flags = CLK_GET_RATE_NOCACHE; + init.parent_data = cfg->pll_parent; + init.num_parents = 1; + + hdmi_phy->pll_hw.init = &init; + ret = devm_clk_hw_register(hdmi_phy->dev, &hdmi_phy->pll_hw); + if (ret) + goto err; + + ret = devm_of_clk_add_hw_provider(hdmi_phy->dev, of_clk_hw_simple_get, &hdmi_phy->pll_hw); + if (ret) + goto err; + + hdmi_phy->phy = devm_phy_create(dev, pdev->dev.of_node, &qcom_hdmi_preqmp_phy_ops); + if (IS_ERR(hdmi_phy->phy)) { + ret = PTR_ERR(hdmi_phy->phy); + goto err; + } + + phy_set_drvdata(hdmi_phy->phy, hdmi_phy); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + pm_runtime_put_noidle(&pdev->dev); + return PTR_ERR_OR_ZERO(phy_provider); + +err: + pm_runtime_put_noidle(&pdev->dev); + return ret; +} + +static const struct of_device_id qcom_hdmi_preqmp_of_match_table[] = { + { .compatible = "qcom,hdmi-phy-8960", .data = &msm8960_hdmi_phy_cfg, }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_hdmi_preqmp_of_match_table); + +DEFINE_RUNTIME_DEV_PM_OPS(qcom_hdmi_preqmp_pm_ops, + qcom_hdmi_preqmp_runtime_suspend, + qcom_hdmi_preqmp_runtime_resume, + NULL); + +static struct platform_driver qcom_hdmi_preqmp_driver = { + .probe = qcom_hdmi_preqmp_probe, + .driver = { + .name = "qcom-preqmp-hdmi-phy", + .of_match_table = qcom_hdmi_preqmp_of_match_table, + .pm = &qcom_hdmi_preqmp_pm_ops, + }, +}; + +module_platform_driver(qcom_hdmi_preqmp_driver); + +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Qualcomm MSMpreqmp HDMI PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.h b/drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.h new file mode 100644 index 000000000000..dca9be78d8fc --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-hdmi-preqmp.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@xxxxxxxxx> + * Copyright (c) 2023, Linaro Ltd. + */ + +#ifndef PHY_QCOM_HDMI_PREQMP_H +#define PHY_QCOM_HDMI_PREQMP_H + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/phy/phy-hdmi.h> +#include <linux/regulator/consumer.h> + +#define MAX_CLKS 2 +#define MAX_SUPPLIES 2 + +struct qcom_hdmi_preqmp_phy { + struct device *dev; + struct phy *phy; + void __iomem *pll_reg; + void __iomem *phy_reg; + + struct phy_configure_opts_hdmi hdmi_opts; + + struct clk_hw pll_hw; + struct clk_bulk_data clks[MAX_CLKS]; + int num_clks; + + struct regulator_bulk_data regs[MAX_SUPPLIES]; + int num_regs; + + int (*power_on)(struct qcom_hdmi_preqmp_phy *phy); + int (*power_off)(struct qcom_hdmi_preqmp_phy *phy); +}; + +#define hw_clk_to_phy(x) container_of(x, struct qcom_hdmi_preqmp_phy, pll_hw) + +struct qcom_hdmi_preqmp_cfg { + const char * const clk_names[MAX_CLKS]; + int num_clks; + + const char * const reg_names[MAX_SUPPLIES]; + int reg_init_load[MAX_SUPPLIES]; + int num_regs; + + int (*power_on)(struct qcom_hdmi_preqmp_phy *phy); + int (*power_off)(struct qcom_hdmi_preqmp_phy *phy); + + const struct clk_ops *pll_ops; + const struct clk_parent_data *pll_parent; +}; + +static inline void hdmi_phy_write(struct qcom_hdmi_preqmp_phy *phy, int offset, + u32 data) +{ + writel(data, phy->phy_reg + offset); +} + +static inline u32 hdmi_phy_read(struct qcom_hdmi_preqmp_phy *phy, int offset) +{ + return readl(phy->phy_reg + offset); +} + +static inline void hdmi_pll_write(struct qcom_hdmi_preqmp_phy *phy, int offset, + u32 data) +{ + writel(data, phy->pll_reg + offset); +} + +static inline u32 hdmi_pll_read(struct qcom_hdmi_preqmp_phy *phy, int offset) +{ + return readl(phy->pll_reg + offset); +} + +extern const struct qcom_hdmi_preqmp_cfg msm8960_hdmi_phy_cfg; + +#endif -- 2.39.2