Port the HDMI PHY driver for Qualcomm MSM8960 / APQ8064 platforms. Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> --- drivers/phy/qualcomm/Kconfig | 11 + drivers/phy/qualcomm/Makefile | 1 + drivers/phy/qualcomm/phy-qcom-hdmi-msm8960.c | 744 +++++++++++++++++++ 3 files changed, 756 insertions(+) create mode 100644 drivers/phy/qualcomm/phy-qcom-hdmi-msm8960.c diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 94fb5679df4a..838f93ad1168 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -42,6 +42,17 @@ config PHY_QCOM_IPQ806X_SATA depends on OF select GENERIC_PHY +config PHY_QCOM_HDMI_MSM8960 + tristate "Qualcomm MSM8960 HDMI PHY driver" + depends on ARCH_QCOM || COMPILE_TEST + depends on OF + depends on COMMON_CLK + default DRM_MSM_HDMI && ARCH_MSM8960 + select GENERIC_PHY + help + Enable this to support the Qualcomm HDMI PHY presend on MSM8960 and + APQ8064 platforms. + 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 b877d86ea0b1..6d7d76c7fee0 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -4,6 +4,7 @@ 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_MSM8960) += phy-qcom-hdmi-msm8960.o obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o phy-qcom-qmp-hdmi-y := \ diff --git a/drivers/phy/qualcomm/phy-qcom-hdmi-msm8960.c b/drivers/phy/qualcomm/phy-qcom-hdmi-msm8960.c new file mode 100644 index 000000000000..338d132dc8c8 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-hdmi-msm8960.c @@ -0,0 +1,744 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@xxxxxxxxx> + * Copyright (c) 2023, Linaro Ltd. + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.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 + +struct qcom_hdmi_8960_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 *iface_clk; + struct regulator *vdda; +}; + +#define hw_clk_to_phy(x) container_of(x, struct qcom_hdmi_8960_phy, pll_hw) + +static inline void hdmi_phy_write(struct qcom_hdmi_8960_phy *phy, int offset, + u32 data) +{ + writel(data, phy->phy_reg + offset); +} + +static inline u32 hdmi_phy_read(struct qcom_hdmi_8960_phy *phy, int offset) +{ + return readl(phy->phy_reg + offset); +} + +static inline void hdmi_pll_write(struct qcom_hdmi_8960_phy *phy, int offset, + u32 data) +{ + writel(data, phy->pll_reg + offset); +} + +static inline u32 hdmi_pll_read(struct qcom_hdmi_8960_phy *phy, int offset) +{ + return readl(phy->pll_reg + offset); +} + +struct pll_conf { + u32 val; + u32 reg; +}; + +struct pll_rate { + unsigned long rate; + size_t num_reg; + const struct pll_conf *conf; +}; + +static const struct pll_conf pll_conf_154M[] = { + { 0x08, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x0d, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x4d, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x5e, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0x42, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, +}; + +static const struct pll_conf pll_conf_148M5[] = { + { 0x02, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG }, + { 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG }, + { 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG }, + { 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B }, + { 0x76, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x01, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, + { 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG3 }, + { 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 }, + { 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 }, + { 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 }, + { 0xe6, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 }, +}; + +static const struct pll_conf pll_conf_108M[] = { + { 0x08, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x21, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x1c, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x49, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x49, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, +}; + +static const struct pll_conf pll_conf_74M25[] = { + { 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B }, + { 0x12, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x76, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0xe6, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, +}; + +static const struct pll_conf pll_conf_74M176[] = { + { 0x18, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0xe5, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x0c, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x7d, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0xbc, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, +}; + +static const struct pll_conf pll_conf_65M[] = { + { 0x18, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x8a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x0b, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x4b, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x7b, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0x09, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, +}; + +static const struct pll_conf pll_conf_27M03[] = { + { 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B }, + { 0x38, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG }, + { 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0xff, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x4e, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0xd7, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0x03, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, + { 0x2a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 }, +}; + +static const struct pll_conf pll_conf_27M[] = { + { 0x32, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG }, + { 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG }, + { 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG }, + { 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B }, + { 0x7b, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x01, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, + { 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG3 }, + { 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 }, + { 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 }, + { 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 }, + { 0x2a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 }, +}; + +static const struct pll_conf pll_conf_25M2[] = { + { 0x32, REG_HDMI_8960_PHY_PLL_REFCLK_CFG }, + { 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG }, + { 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 }, + { 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 }, + { 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG }, + { 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG }, + { 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B }, + { 0x77, REG_HDMI_8960_PHY_PLL_SDM_CFG0 }, + { 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG1 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG2 }, + { 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 }, + { 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 }, + { 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 }, + { 0x20, REG_HDMI_8960_PHY_PLL_SSC_CFG3 }, + { 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 }, + { 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 }, + { 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 }, + { 0xf4, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 }, + { 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 }, + { 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 }, + { 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 }, + { 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 }, + { 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 }, +}; + + +/* NOTE: keep sorted highest freq to lowest: */ +static const struct pll_rate freqtbl[] = { + { 154000, ARRAY_SIZE(pll_conf_154M), pll_conf_154M }, + /* 1080p60/1080p50 case */ + { 148500, ARRAY_SIZE(pll_conf_148M5), pll_conf_148M5 }, + { 108000, ARRAY_SIZE(pll_conf_108M), pll_conf_108M }, + /* 720p60/720p50/1080i60/1080i50/1080p24/1080p30/1080p25 */ + { 74250, ARRAY_SIZE(pll_conf_74M25), pll_conf_74M25 }, + { 74176, ARRAY_SIZE(pll_conf_74M176), pll_conf_74M176 }, + { 65000, ARRAY_SIZE(pll_conf_65M), pll_conf_65M }, + /* 480p60/480i60 */ + { 27030, ARRAY_SIZE(pll_conf_27M03), pll_conf_27M03 }, + /* 576p50/576i50 */ + { 27000, ARRAY_SIZE(pll_conf_27M), pll_conf_27M }, + /* 640x480p60 */ + { 25200, ARRAY_SIZE(pll_conf_25M2), pll_conf_25M2 }, +}; + +static const struct pll_rate *find_rate(unsigned long rate) +{ + int i; + + for (i = 1; i < ARRAY_SIZE(freqtbl); i++) + if (rate > freqtbl[i].rate) + return &freqtbl[i - 1]; + + return &freqtbl[i - 1]; +} + +static unsigned long qcom_hdmi_8960_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = hw_clk_to_phy(hw); + + return hdmi_phy->hdmi_opts.pixel_clk_rate * 1000UL; +} + +static long qcom_hdmi_8960_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + const struct pll_rate *pll_rate = find_rate(rate / 1000); + + return pll_rate->rate * 1000UL; +} + +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_8960_phy_pll_enable(struct qcom_hdmi_8960_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_8960_phy_set_rate(struct qcom_hdmi_8960_phy *phy) +{ + unsigned int pixclk = phy->hdmi_opts.pixel_clk_rate; + const struct pll_rate *pll_rate = find_rate(pixclk); + int i; + + dev_dbg(phy->dev, "rate=%u", pixclk); + + for (i = 0; i < pll_rate->num_reg; i++) + hdmi_pll_write(phy, pll_rate->conf[i].reg, pll_rate->conf[i].val); + + return 0; +} + +static void qcom_hdmi_8960_phy_pll_disable(struct qcom_hdmi_8960_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_8960_phy_power_on(struct phy *phy) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = phy_get_drvdata(phy); + int ret; + + ret = qcom_hdmi_8960_phy_pll_enable(hdmi_phy); + if (ret) + return ret; + + ret = qcom_hdmi_8960_phy_set_rate(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_8960_phy_power_off(struct phy *phy) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = phy_get_drvdata(phy); + + hdmi_phy_write(hdmi_phy, REG_HDMI_8960_PHY_REG2, 0x7f); + + qcom_hdmi_8960_phy_pll_disable(hdmi_phy); + + return 0; +} + +static int qcom_hdmi_8960_phy_init(struct phy *phy) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = phy_get_drvdata(phy); + + return pm_runtime_resume_and_get(hdmi_phy->dev); +} + +static int qcom_hdmi_8960_phy_exit(struct phy *phy) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = phy_get_drvdata(phy); + + pm_runtime_put_noidle(hdmi_phy->dev); + + return 0; +} + +static int qcom_hdmi_8960_phy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + const struct phy_configure_opts_hdmi *hdmi_opts = &opts->hdmi; + struct qcom_hdmi_8960_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_8960_runtime_resume(struct device *dev) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(hdmi_phy->vdda); + if (ret) + return ret; + + ret = clk_prepare_enable(hdmi_phy->iface_clk); + if (ret) + goto out_disable_supplies; + + return 0; + +out_disable_supplies: + regulator_disable(hdmi_phy->vdda); + + return ret; +} + +static int __maybe_unused qcom_hdmi_8960_runtime_suspend(struct device *dev) +{ + struct qcom_hdmi_8960_phy *hdmi_phy = dev_get_drvdata(dev); + + clk_disable_unprepare(hdmi_phy->iface_clk); + regulator_disable(hdmi_phy->vdda); + + return 0; +} + +static const struct phy_ops qcom_hdmi_8960_phy_ops = { + .init = qcom_hdmi_8960_phy_init, + .configure = qcom_hdmi_8960_phy_configure, + .power_on = qcom_hdmi_8960_phy_power_on, + .power_off = qcom_hdmi_8960_phy_power_off, + .exit = qcom_hdmi_8960_phy_exit, + .owner = THIS_MODULE, +}; + +static int qcom_hdmi_8960_probe(struct platform_device *pdev) +{ + struct clk_init_data init = { + .name = "hdmipll", + .ops = &qcom_hdmi_8960_pll_ops, + .parent_data = (const struct clk_parent_data[]) { + { .fw_name = "pxo", .name = "pxo_board" }, + }, + .flags = CLK_GET_RATE_NOCACHE, + .num_parents = 1, + }; + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct qcom_hdmi_8960_phy *hdmi_phy; + int ret; + + 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->iface_clk = devm_clk_get(dev, "slave_iface"); + if (IS_ERR(hdmi_phy->iface_clk)) { + ret = PTR_ERR(hdmi_phy->iface_clk); + return ret; + } + + hdmi_phy->vdda = devm_regulator_get(dev, "core-vdda"); + if (IS_ERR(hdmi_phy->vdda)) { + ret = PTR_ERR(hdmi_phy->vdda); + return ret; + } + + 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; + + 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_8960_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_8960_of_match_table[] = { + { + .compatible = "qcom,hdmi-phy-8960", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, qcom_hdmi_8960_of_match_table); + +DEFINE_RUNTIME_DEV_PM_OPS(qcom_hdmi_8960_pm_ops, + qcom_hdmi_8960_runtime_suspend, + qcom_hdmi_8960_runtime_resume, + NULL); + +static struct platform_driver qcom_hdmi_8960_driver = { + .probe = qcom_hdmi_8960_probe, + .driver = { + .name = "qcom-8960-hdmi-phy", + .of_match_table = qcom_hdmi_8960_of_match_table, + .pm = &qcom_hdmi_8960_pm_ops, + }, +}; + +module_platform_driver(qcom_hdmi_8960_driver); + +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx>"); +MODULE_DESCRIPTION("Qualcomm MSM8960 HDMI PHY driver"); +MODULE_LICENSE("GPL"); -- 2.39.2