Message ID | 20230625114222.96689-3-dmitry.baryshkov@linaro.org |
---|---|
State | Changes Requested |
Headers | show |
Series | drm/msm/hdmi & phy: use generic PHY framework | expand |
On 25.06.2023 13:42, Dmitry Baryshkov wrote: > Port Qualcomm QMP HDMI PHY to the generic PHY framework. Split the > generic part and the msm8996 part. When adding support for msm8992/4 and > msm8998 (which also employ QMP for HDMI PHY), one will have to provide > the PLL programming part only. > > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> > --- [...] > --- /dev/null > +++ b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-base.c > @@ -0,0 +1,184 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2016, The Linux Foundation. All rights reserved. > + * Copyright (c) 2023, Linaro Ltd. > + */ > + > +#include <linux/of_device.h> > +#include <linux/phy/phy.h> > +#include <linux/platform_device.h> > + > +#include "phy-qcom-qmp-hdmi.h" > + > +int qmp_hdmi_phy_init(struct phy *phy) > +{ > + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); > + > + return pm_runtime_resume_and_get(hdmi_phy->dev); > +} > + > +int qmp_hdmi_phy_configure(struct phy *phy, union phy_configure_opts *opts) > +{ > + const struct phy_configure_opts_hdmi *hdmi_opts = &opts->hdmi; > + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); Misaligned > + int ret = 0; > + > + memcpy(&hdmi_phy->hdmi_opts, hdmi_opts, sizeof(*hdmi_opts)); > + > + return ret; > +} > + > +int qmp_hdmi_phy_exit(struct phy *phy) > +{ > + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); > + > + pm_runtime_put_noidle(hdmi_phy->dev); > + > + return 0; > +} > + > +static int __maybe_unused qmp_hdmi_runtime_resume(struct device *dev) > +{ > + struct qmp_hdmi_phy *hdmi_phy = dev_get_drvdata(dev); > + int ret; > + > + ret = regulator_bulk_enable(ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); > + if (ret) > + return ret; > + > + ret = clk_bulk_prepare_enable(ARRAY_SIZE(hdmi_phy->clks), hdmi_phy->clks); > + if (ret) > + goto out_disable_supplies; > + > + return 0; > + > +out_disable_supplies: > + regulator_bulk_disable(ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); > + > + return ret; > +} > + > +static int __maybe_unused qmp_hdmi_runtime_suspend(struct device *dev) > +{ > + struct qmp_hdmi_phy *hdmi_phy = dev_get_drvdata(dev); > + > + clk_bulk_disable_unprepare(ARRAY_SIZE(hdmi_phy->clks), hdmi_phy->clks); > + regulator_bulk_disable(ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); > + > + return 0; > +} > + > +static int qmp_hdmi_probe(struct platform_device *pdev) > +{ > + struct clk_init_data init = { > + .name = "hdmipll", > + .parent_data = (const struct clk_parent_data[]) { > + { .fw_name = "xo", .name = "xo_board" }, > + }, > + .flags = CLK_GET_RATE_NOCACHE, > + .num_parents = 1, > + }; > + const struct qmp_hdmi_cfg *cfg = of_device_get_match_data(&pdev->dev); > + struct phy_provider *phy_provider; > + struct device *dev = &pdev->dev; > + struct qmp_hdmi_phy *hdmi_phy; > + int ret, i; > + > + hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL); > + if (!hdmi_phy) > + return -ENOMEM; > + > + hdmi_phy->dev = dev; > + > + hdmi_phy->serdes = devm_platform_ioremap_resource(pdev, 0); > + if (IS_ERR(hdmi_phy->serdes)) > + return PTR_ERR(hdmi_phy->serdes); > + > + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { > + hdmi_phy->tx[i] = devm_platform_ioremap_resource(pdev, 1 + i); > + if (IS_ERR(hdmi_phy->tx[i])) > + return PTR_ERR(hdmi_phy->tx[i]); > + } > + > + hdmi_phy->phy_reg = devm_platform_ioremap_resource(pdev, 5); Please create an enum with all the reg indices > + if (IS_ERR(hdmi_phy->phy_reg)) > + return PTR_ERR(hdmi_phy->phy_reg); > + > + hdmi_phy->clks[0].id = "iface"; > + hdmi_phy->clks[1].id = "ref"; > + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(hdmi_phy->clks), hdmi_phy->clks); > + if (ret) > + return ret; > + > + hdmi_phy->supplies[0].supply = "vddio"; > + hdmi_phy->supplies[0].init_load_uA = 100000; > + hdmi_phy->supplies[1].supply = "vcca"; > + hdmi_phy->supplies[1].init_load_uA = 10000; > + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); > + if (ret) > + 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; > + > + init.ops = cfg->pll_ops; > + 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, cfg->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 qmp_hdmi_of_match_table[] = { > + { > + .compatible = "qcom,hdmi-phy-8996", .data = &qmp_hdmi_8996_cfg, > + }, { .compatible ... }, ? > + { }, > +}; > +MODULE_DEVICE_TABLE(of, qmp_hdmi_of_match_table); > + > +DEFINE_RUNTIME_DEV_PM_OPS(qmp_hdmi_pm_ops, > + qmp_hdmi_runtime_suspend, > + qmp_hdmi_runtime_resume, > + NULL); > + > +static struct platform_driver qmp_hdmi_driver = { > + .probe = qmp_hdmi_probe, > + .driver = { > + .name = "qcom-qmp-hdmi-phy", > + .of_match_table = qmp_hdmi_of_match_table, > + .pm = &qmp_hdmi_pm_ops, > + }, > +}; > + > +module_platform_driver(qmp_hdmi_driver); > + > +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>"); > +MODULE_DESCRIPTION("Qualcomm QMP HDMI PHY driver"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c > new file mode 100644 > index 000000000000..27ffa70d0faa > --- /dev/null > +++ b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c > @@ -0,0 +1,441 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2016, The Linux Foundation. All rights reserved. > + * Copyright (c) 2023, Linaro Ltd. > + */ > + > +#include <linux/delay.h> > +#include <linux/iopoll.h> > +#include <linux/phy/phy.h> > + > +#include "phy-qcom-qmp-hdmi.h" > +#include "phy-qcom-qmp-qserdes-com.h" > +#include "phy-qcom-qmp-qserdes-txrx.h" > + > +#define HDMI_VCO_MAX_FREQ 12000000000UL > +#define HDMI_VCO_MIN_FREQ 8000000000UL > + > +#define HDMI_PCLK_MAX_FREQ 600000000UL > +#define HDMI_PCLK_MIN_FREQ 25000000UL > + > +#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL > +#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL > +#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL > +#define HDMI_DEFAULT_REF_CLOCK 19200000 > +#define HDMI_PLL_CMP_CNT 1024 > + > +#define HDMI_PLL_POLL_MAX_READS 100 > +#define HDMI_PLL_POLL_TIMEOUT_US 150 > + > +#define REG_HDMI_8996_PHY_CFG 0x00000000 > +#define REG_HDMI_8996_PHY_PD_CTL 0x00000004 > +#define REG_HDMI_8996_PHY_MODE 0x00000008 > +#define REG_HDMI_8996_PHY_MISR_CLEAR 0x0000000c > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_CFG0 0x00000010 > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_CFG1 0x00000014 > +#define REG_HDMI_8996_PHY_TX0_TX1_PRBS_SEED_BYTE0 0x00000018 > +#define REG_HDMI_8996_PHY_TX0_TX1_PRBS_SEED_BYTE1 0x0000001c > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_PATTERN0 0x00000020 > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_PATTERN1 0x00000024 > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_CFG0 0x00000028 > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_CFG1 0x0000002c > +#define REG_HDMI_8996_PHY_TX2_TX3_PRBS_SEED_BYTE0 0x00000030 > +#define REG_HDMI_8996_PHY_TX2_TX3_PRBS_SEED_BYTE1 0x00000034 > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_PATTERN0 0x00000038 > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_PATTERN1 0x0000003c > +#define REG_HDMI_8996_PHY_DEBUG_BUS_SEL 0x00000040 > +#define REG_HDMI_8996_PHY_TXCAL_CFG0 0x00000044 > +#define REG_HDMI_8996_PHY_TXCAL_CFG1 0x00000048 > +#define REG_HDMI_8996_PHY_TX0_TX1_LANE_CTL 0x0000004c > +#define REG_HDMI_8996_PHY_TX2_TX3_LANE_CTL 0x00000050 > +#define REG_HDMI_8996_PHY_LANE_BIST_CONFIG 0x00000054 > +#define REG_HDMI_8996_PHY_CLOCK 0x00000058 > +#define REG_HDMI_8996_PHY_MISC1 0x0000005c > +#define REG_HDMI_8996_PHY_MISC2 0x00000060 > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_STATUS0 0x00000064 > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_STATUS1 0x00000068 > +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_STATUS2 0x0000006c > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_STATUS0 0x00000070 > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_STATUS1 0x00000074 > +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_STATUS2 0x00000078 > +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS0 0x0000007c > +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS1 0x00000080 > +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS2 0x00000084 > +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS3 0x00000088 > +#define REG_HDMI_8996_PHY_POST_MISR_STATUS0 0x0000008c > +#define REG_HDMI_8996_PHY_POST_MISR_STATUS1 0x00000090 > +#define REG_HDMI_8996_PHY_POST_MISR_STATUS2 0x00000094 > +#define REG_HDMI_8996_PHY_POST_MISR_STATUS3 0x00000098 > +#define REG_HDMI_8996_PHY_STATUS 0x0000009c > +#define REG_HDMI_8996_PHY_MISC3_STATUS 0x000000a0 > +#define REG_HDMI_8996_PHY_MISC4_STATUS 0x000000a4 > +#define REG_HDMI_8996_PHY_DEBUG_BUS0 0x000000a8 > +#define REG_HDMI_8996_PHY_DEBUG_BUS1 0x000000ac > +#define REG_HDMI_8996_PHY_DEBUG_BUS2 0x000000b0 > +#define REG_HDMI_8996_PHY_DEBUG_BUS3 0x000000b4 > +#define REG_HDMI_8996_PHY_PHY_REVISION_ID0 0x000000b8 > +#define REG_HDMI_8996_PHY_PHY_REVISION_ID1 0x000000bc > +#define REG_HDMI_8996_PHY_PHY_REVISION_ID2 0x000000c0 > +#define REG_HDMI_8996_PHY_PHY_REVISION_ID3 0x000000c4 > + > +struct qmp_hdmi_8996_post_divider { > + u64 vco_freq; > + int hsclk_divsel; > + int vco_ratio; > + int tx_band_sel; > +}; > + > +static inline u32 qmp_hdmi_8996_pll_get_pll_cmp(u64 fdata, unsigned long ref_clk) > +{ > + u64 dividend = HDMI_PLL_CMP_CNT * fdata; HDMI_PLL_CMP_CNT should be ULL > + u32 divisor = ref_clk * 10; > + u32 rem; > + > + rem = do_div(dividend, divisor); > + if (rem > (divisor >> 1)) > + dividend++; > + > + return dividend - 1; > +} > + > +static int qmp_hdmi_8996_pll_get_post_div(struct qmp_hdmi_8996_post_divider *pd, u64 bclk) > +{ > + int ratio[] = { 2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35 }; > + int hs_divsel[] = { 0, 4, 8, 12, 1, 5, 2, 9, 3, 13, 10, 7, 14, 11, 15 }; > + int tx_band_sel[] = { 0, 1, 2, 3 }; > + u64 vco_freq[60]; 60 -> ARRAY_SIZE() * ARRAY_SIZE()? > + u64 vco, vco_optimal; > + int half_rate_mode = 0; > + int vco_optimal_index, vco_freq_index; > + int i, j; Reverse-Christmas-tree? > + > +retry: > + vco_optimal = HDMI_VCO_MAX_FREQ; > + vco_optimal_index = -1; > + vco_freq_index = 0; > + for (i = 0; i < 15; i++) { > + for (j = 0; j < 4; j++) { > + u32 ratio_mult = ratio[i] << tx_band_sel[j]; > + > + vco = bclk >> half_rate_mode; > + vco *= ratio_mult; > + vco_freq[vco_freq_index++] = vco; > + } > + } > + > + for (i = 0; i < 60; i++) { > + u64 vco_tmp = vco_freq[i]; > + > + if ((vco_tmp >= HDMI_VCO_MIN_FREQ) && > + (vco_tmp <= vco_optimal)) { > + vco_optimal = vco_tmp; > + vco_optimal_index = i; > + } > + } > + > + if (vco_optimal_index == -1) { > + if (!half_rate_mode) { > + half_rate_mode = 1; > + goto retry; > + } > + > + return -EINVAL; > + } > + > + pd->vco_freq = vco_optimal; > + pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4]; > + pd->vco_ratio = ratio[vco_optimal_index / 4]; > + pd->hsclk_divsel = hs_divsel[vco_optimal_index / 4]; > + > + return 0; > +} > + > +static int qmp_hdmi_8996_phy_set_rate(struct qmp_hdmi_phy *hdmi_phy) > +{ > + unsigned long parent_rate = HDMI_DEFAULT_REF_CLOCK; HDMI_DEFAULT_REF_CLOCK could be UL for completeness > + unsigned long rate = hdmi_phy->hdmi_opts.pixel_clk_rate * 1000; > + struct qmp_hdmi_8996_post_divider pd; > + bool gen_ssc = false; > + u64 bclk; > + u64 dec_start; > + u64 frac_start; > + u64 fdata; > + u32 pll_divisor; > + u32 rem; > + u32 integloop_gain; > + u32 pll_cmp; > + int i, ret; This list is very long, perhaps collapse some common-type var definitions > + > + bclk = ((u64)rate) * 10; 19.2 mil * 10 will not overflow u32 > + ret = qmp_hdmi_8996_pll_get_post_div(&pd, bclk); > + if (ret) { > + dev_err(hdmi_phy->dev, "PLL calculation failed\n"); > + return ret; > + } > + > + dec_start = pd.vco_freq; > + pll_divisor = 4 * parent_rate; > + do_div(dec_start, pll_divisor); > + > + frac_start = pd.vco_freq * (1 << 20); > + > + rem = do_div(frac_start, pll_divisor); > + frac_start -= dec_start * (1 << 20); > + if (rem > (pll_divisor >> 1)) > + frac_start++; > + > + fdata = pd.vco_freq; > + do_div(fdata, pd.vco_ratio); > + > + pll_cmp = qmp_hdmi_8996_pll_get_pll_cmp(fdata, parent_rate); > + > + /* Initially shut down PHY */ > + dev_dbg(hdmi_phy->dev, "Disabling PHY"); > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_PD_CTL, 0x0); > + udelay(500); usleep_range https://www.kernel.org/doc/Documentation/timers/timers-howto.txt > + > + /* Power up sequence */ > + hdmi_pll_write(hdmi_phy, QSERDES_COM_BG_CTRL, 0x04); > + > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_PD_CTL, 0x1); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_RESETSM_CNTRL, 0x20); > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_TX0_TX1_LANE_CTL, 0x0f); > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_TX2_TX3_LANE_CTL, 0x0f); > + > + hdmi_tx_chan_write(hdmi_phy, 0, QSERDES_TX_LANE_MODE, 0x43); > + hdmi_tx_chan_write(hdmi_phy, 2, QSERDES_TX_LANE_MODE, 0x43); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1e); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SYSCLK_EN_SEL, 0x37); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SYS_CLK_CTRL, 0x02); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CLK_ENABLE1, 0x0e); > + > + if (frac_start != 0 || gen_ssc) { > + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_CCTRL_MODE0, 0x28); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CP_CTRL_MODE0, > + 11000000 / (parent_rate/ 20)); > + integloop_gain = (64 * parent_rate) / HDMI_DEFAULT_REF_CLOCK; do_div > + } else { > + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_CCTRL_MODE0, 0x01); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_RCTRL_MODE0, 0x10); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CP_CTRL_MODE0, 0x23); > + integloop_gain = (1022 * parent_rate) / (100 * 1000 * 1000); > + } > + > + /* Bypass VCO calibration */ > + if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) { > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SVS_MODE_CLK_SEL, 1); > + integloop_gain <<= 1; > + } else { > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SVS_MODE_CLK_SEL, 2); > + integloop_gain <<= 2; > + } > + > + integloop_gain = min_t(u32, integloop_gain, 2046); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_BG_TRIM, 0x0f); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_IVCO, 0x0f); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_VCO_TUNE_CTRL, 0); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_BG_CTRL, 0x06); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CLK_SELECT, 0x30); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_HSCLK_SEL, 0x20 | pd.hsclk_divsel); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP_EN, 0x0); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_DEC_START_MODE0, dec_start); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_DIV_FRAC_START1_MODE0, > + frac_start & 0xff); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, > + (frac_start >> 8) & 0xff); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, > + (frac_start >> 16) & 0xf); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_INTEGLOOP_GAIN0_MODE0, > + integloop_gain & 0xff); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_INTEGLOOP_GAIN1_MODE0, > + (integloop_gain >> 8) & 0xff); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP1_MODE0, > + pll_cmp & 0xff); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP2_MODE0, > + (pll_cmp >> 8) & 0xff); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP3_MODE0, > + (pll_cmp >> 16) & 0x3); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_VCO_TUNE_MAP, 0x00); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CORE_CLK_EN, 0x2c); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CORECLK_DIV, 5); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_CMN_CONFIG, 0x02); > + > + hdmi_pll_write(hdmi_phy, QSERDES_COM_RESCODE_DIV_NUM, 0x15); > + > + /* TX lanes setup (TX 0/1/2/3) */ > + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_CLKBUF_ENABLE, 0x03); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_BAND, pd.tx_band_sel + 4); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_RESET_TSYNC_EN, 0x03); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL1, 0x00); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL_OFFSET, 0x00); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_RES_CODE_LANE_OFFSET, 0x00); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TRAN_DRVR_EMP_EN, 0x03); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_PARRATE_REC_DETECT_IDLE_EN, 0x40); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_HP_PD_ENABLES, is this a misspelt 'ENABLED'? > + i != 3 ? 0xc : 0x3); > + } > + > + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { > + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL, > + i != 3 ? 0x25 : 0x22); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_EMP_POST1_LVL, > + i != 3 ? 0x23 : 0x27); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL2, > + i != 3 ? 0x0d : 0x00); > + } > + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { > + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL, 0x25); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_EMP_POST1_LVL, 0x23); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL2, > + i != 3 ? 0x0d : 0x00); > + } > + } else { > + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL, 0x20); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_EMP_POST1_LVL, 0x20); > + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL2, 0x0e); > + } > + } > + > + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_MODE, 0x10); > + else > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_MODE, 0x00); > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_PD_CTL, 0x1f); > + > + return 0; > +} > + > +static int qmp_hdmi_8996_phy_power_on(struct phy *phy) > +{ > + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); > + u32 status; > + int i, ret = 0; Reverse-Christmas-tree? > + > + ret = qmp_hdmi_8996_phy_set_rate(hdmi_phy); > + if (ret) { > + dev_err(hdmi_phy->dev, "Setting pixel clock rate failed\n"); > + return ret; > + } > + > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x1); > + udelay(100); usleep_range > + > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x19); > + udelay(100); usleep_range > + > + ret = readl_poll_timeout(hdmi_phy->serdes + QSERDES_COM_C_READY_STATUS, > + status, status & BIT(0), > + HDMI_PLL_POLL_TIMEOUT_US, > + HDMI_PLL_POLL_MAX_READS * HDMI_PLL_POLL_TIMEOUT_US); > + > + if (ret) { > + dev_warn(hdmi_phy->dev, "HDMI PLL is not locked\n"); > + return ret; > + } > + > + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) > + hdmi_tx_chan_write(hdmi_phy, i, > + QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, > + 0x6f); > + > + /* Disable SSC */ > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_PER1, 0x0); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_PER2, 0x0); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_STEP_SIZE1, 0x0); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_STEP_SIZE2, 0x0); > + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_EN_CENTER, 0x2); > + > + ret = readl_poll_timeout(hdmi_phy->phy_reg + REG_HDMI_8996_PHY_STATUS, > + status, status & BIT(0), > + HDMI_PLL_POLL_TIMEOUT_US, > + HDMI_PLL_POLL_MAX_READS * HDMI_PLL_POLL_TIMEOUT_US); > + if (ret) { > + dev_warn(hdmi_phy->dev, "HDMI PLL is not locked\n"); > + return ret; > + } > + > + /* Restart the retiming buffer */ > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x18); > + udelay(1); > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x19); > + > + return 0; > +} > + > +static int qmp_hdmi_8996_phy_power_off(struct phy *phy) > +{ > + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); > + > + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x6); > + usleep_range(100, 150); > + > + return 0; > +} > + > +static long qmp_hdmi_8996_pll_round_rate(struct clk_hw *hw, > + unsigned long rate, > + unsigned long *parent_rate) > +{ > + return clamp(rate, HDMI_PCLK_MIN_FREQ, HDMI_PCLK_MAX_FREQ); > +} > + > +static unsigned long qmp_hdmi_8996_pll_recalc_rate(struct clk_hw *hw, > + unsigned long parent_rate) > +{ > + struct qmp_hdmi_phy *phy = hw_clk_to_pll(hw); > + u32 cmp1, cmp2, cmp3, pll_cmp; > + > + cmp1 = hdmi_pll_read(phy, QSERDES_COM_LOCK_CMP1_MODE0); > + cmp2 = hdmi_pll_read(phy, QSERDES_COM_LOCK_CMP2_MODE0); > + cmp3 = hdmi_pll_read(phy, QSERDES_COM_LOCK_CMP3_MODE0); > + > + pll_cmp = cmp1 | (cmp2 << 8) | (cmp3 << 16); FIELD_PREP to avoid overflows if hw returns garbage? Or is it supposed to overflow by design? > + > + return mult_frac(pll_cmp + 1, parent_rate, HDMI_PLL_CMP_CNT); > +} > + > +static int qmp_hdmi_8996_pll_is_enabled(struct clk_hw *hw) > +{ > + struct qmp_hdmi_phy *phy = hw_clk_to_pll(hw); > + u32 status; > + int pll_locked; Reverse-Christmas-tree? Konrad > + > + status = hdmi_pll_read(phy, QSERDES_COM_C_READY_STATUS); > + pll_locked = status & BIT(0); > + > + return pll_locked; > +} > + > +static const struct clk_ops qmp_hdmi_8996_pll_ops = { > + .recalc_rate = qmp_hdmi_8996_pll_recalc_rate, > + .round_rate = qmp_hdmi_8996_pll_round_rate, > + .is_enabled = qmp_hdmi_8996_pll_is_enabled, > +}; > + > +static const struct phy_ops qmp_hdmi_8996_phy_ops = { > + .init = qmp_hdmi_phy_init, > + .configure = qmp_hdmi_phy_configure, > + .power_on = qmp_hdmi_8996_phy_power_on, > + .power_off = qmp_hdmi_8996_phy_power_off, > + .exit = qmp_hdmi_phy_exit, > + .owner = THIS_MODULE, > +}; > + > +const struct qmp_hdmi_cfg qmp_hdmi_8996_cfg = { > + .pll_ops = &qmp_hdmi_8996_pll_ops, > + .phy_ops = &qmp_hdmi_8996_phy_ops, > +}; > diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h > new file mode 100644 > index 000000000000..25d307a8f287 > --- /dev/null > +++ b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h > @@ -0,0 +1,75 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2016, The Linux Foundation. All rights reserved. > + * Copyright (c) 2023, Linaro Ltd. > + */ > + > +#ifndef PHY_QCOM_QMP_HDMI_H > +#define PHY_QCOM_QMP_HDMI_H > + > +#include <linux/clk.h> > +#include <linux/clk-provider.h> > +#include <linux/regulator/consumer.h> > +#include <linux/phy/phy-hdmi.h> > + > +#define MAX_CLKS 2 > +#define MAX_SUPPLIES 2 > + > +#define HDMI_NUM_TX_CHANNEL 4 > + > +struct qmp_hdmi_phy { > + struct device *dev; > + struct phy *phy; > + void __iomem *serdes; > + void __iomem *tx[HDMI_NUM_TX_CHANNEL]; > + void __iomem *phy_reg; > + > + struct phy_configure_opts_hdmi hdmi_opts; > + > + struct clk_hw pll_hw; > + struct clk_bulk_data clks[MAX_CLKS]; > + struct regulator_bulk_data supplies[MAX_SUPPLIES]; > +}; > + > +struct qmp_hdmi_cfg { > + const struct clk_ops *pll_ops; > + const struct phy_ops *phy_ops; > +}; > + > +#define hw_clk_to_pll(x) container_of(x, struct qmp_hdmi_phy, pll_hw) > + > +static inline void hdmi_phy_write(struct qmp_hdmi_phy *phy, int offset, > + u32 data) > +{ > + writel(data, phy->phy_reg + offset); > +} > + > +static inline u32 hdmi_phy_read(struct qmp_hdmi_phy *phy, int offset) > +{ > + return readl(phy->phy_reg + offset); > +} > + > +static inline void hdmi_pll_write(struct qmp_hdmi_phy *phy, int offset, > + u32 data) > +{ > + writel(data, phy->serdes + offset); > +} > + > +static inline u32 hdmi_pll_read(struct qmp_hdmi_phy *phy, int offset) > +{ > + return readl(phy->serdes + offset); > +} > + > +static inline void hdmi_tx_chan_write(struct qmp_hdmi_phy *phy, int channel, > + int offset, int data) > +{ > + writel(data, phy->tx[channel] + offset); > +} > + > +int qmp_hdmi_phy_init(struct phy *phy); > +int qmp_hdmi_phy_configure(struct phy *phy, union phy_configure_opts *opts); > +int qmp_hdmi_phy_exit(struct phy *phy); > + > +extern const struct qmp_hdmi_cfg qmp_hdmi_8996_cfg; > + > +#endif
diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig index 97ca5952e34e..4521fc9b9ced 100644 --- a/drivers/phy/qualcomm/Kconfig +++ b/drivers/phy/qualcomm/Kconfig @@ -68,6 +68,13 @@ config PHY_QCOM_QMP_COMBO Enable this to support the QMP Combo PHY transceiver that is used with USB3 and DisplayPort controllers on Qualcomm chips. +config PHY_QCOM_QMP_HDMI + tristate "Qualcomm QMP HDMI PHY Driver" + default PHY_QCOM_QMP && DRM_MSM_HDMI + help + Enable this to support the QMP HDMI PHY transceiver that is used + with HDMI output on Qualcomm MSM8996 chips. + config PHY_QCOM_QMP_PCIE tristate "Qualcomm QMP PCIe PHY Driver" depends on PCI || COMPILE_TEST diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile index b030858e0f8d..32e27ffb7af6 100644 --- a/drivers/phy/qualcomm/Makefile +++ b/drivers/phy/qualcomm/Makefile @@ -7,11 +7,16 @@ obj-$(CONFIG_PHY_QCOM_IPQ806X_SATA) += phy-qcom-ipq806x-sata.o obj-$(CONFIG_PHY_QCOM_PCIE2) += phy-qcom-pcie2.o obj-$(CONFIG_PHY_QCOM_QMP_COMBO) += phy-qcom-qmp-combo.o +obj-$(CONFIG_PHY_QCOM_QMP_HDMI) += phy-qcom-qmp-hdmi.o obj-$(CONFIG_PHY_QCOM_QMP_PCIE) += phy-qcom-qmp-pcie.o obj-$(CONFIG_PHY_QCOM_QMP_PCIE_8996) += phy-qcom-qmp-pcie-msm8996.o obj-$(CONFIG_PHY_QCOM_QMP_UFS) += phy-qcom-qmp-ufs.o obj-$(CONFIG_PHY_QCOM_QMP_USB) += phy-qcom-qmp-usb.o +phy-qcom-qmp-hdmi-y := \ + phy-qcom-qmp-hdmi-base.o \ + phy-qcom-qmp-hdmi-msm8996.o \ + obj-$(CONFIG_PHY_QCOM_QUSB2) += phy-qcom-qusb2.o obj-$(CONFIG_PHY_QCOM_SNPS_EUSB2) += phy-qcom-snps-eusb2.o obj-$(CONFIG_PHY_QCOM_EUSB2_REPEATER) += phy-qcom-eusb2-repeater.o diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-base.c b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-base.c new file mode 100644 index 000000000000..08132b3f82af --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-base.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. + */ + +#include <linux/of_device.h> +#include <linux/phy/phy.h> +#include <linux/platform_device.h> + +#include "phy-qcom-qmp-hdmi.h" + +int qmp_hdmi_phy_init(struct phy *phy) +{ + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + + return pm_runtime_resume_and_get(hdmi_phy->dev); +} + +int qmp_hdmi_phy_configure(struct phy *phy, union phy_configure_opts *opts) +{ + const struct phy_configure_opts_hdmi *hdmi_opts = &opts->hdmi; + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + int ret = 0; + + memcpy(&hdmi_phy->hdmi_opts, hdmi_opts, sizeof(*hdmi_opts)); + + return ret; +} + +int qmp_hdmi_phy_exit(struct phy *phy) +{ + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + + pm_runtime_put_noidle(hdmi_phy->dev); + + return 0; +} + +static int __maybe_unused qmp_hdmi_runtime_resume(struct device *dev) +{ + struct qmp_hdmi_phy *hdmi_phy = dev_get_drvdata(dev); + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); + if (ret) + return ret; + + ret = clk_bulk_prepare_enable(ARRAY_SIZE(hdmi_phy->clks), hdmi_phy->clks); + if (ret) + goto out_disable_supplies; + + return 0; + +out_disable_supplies: + regulator_bulk_disable(ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); + + return ret; +} + +static int __maybe_unused qmp_hdmi_runtime_suspend(struct device *dev) +{ + struct qmp_hdmi_phy *hdmi_phy = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(ARRAY_SIZE(hdmi_phy->clks), hdmi_phy->clks); + regulator_bulk_disable(ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); + + return 0; +} + +static int qmp_hdmi_probe(struct platform_device *pdev) +{ + struct clk_init_data init = { + .name = "hdmipll", + .parent_data = (const struct clk_parent_data[]) { + { .fw_name = "xo", .name = "xo_board" }, + }, + .flags = CLK_GET_RATE_NOCACHE, + .num_parents = 1, + }; + const struct qmp_hdmi_cfg *cfg = of_device_get_match_data(&pdev->dev); + struct phy_provider *phy_provider; + struct device *dev = &pdev->dev; + struct qmp_hdmi_phy *hdmi_phy; + int ret, i; + + hdmi_phy = devm_kzalloc(dev, sizeof(*hdmi_phy), GFP_KERNEL); + if (!hdmi_phy) + return -ENOMEM; + + hdmi_phy->dev = dev; + + hdmi_phy->serdes = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hdmi_phy->serdes)) + return PTR_ERR(hdmi_phy->serdes); + + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { + hdmi_phy->tx[i] = devm_platform_ioremap_resource(pdev, 1 + i); + if (IS_ERR(hdmi_phy->tx[i])) + return PTR_ERR(hdmi_phy->tx[i]); + } + + hdmi_phy->phy_reg = devm_platform_ioremap_resource(pdev, 5); + if (IS_ERR(hdmi_phy->phy_reg)) + return PTR_ERR(hdmi_phy->phy_reg); + + hdmi_phy->clks[0].id = "iface"; + hdmi_phy->clks[1].id = "ref"; + ret = devm_clk_bulk_get(dev, ARRAY_SIZE(hdmi_phy->clks), hdmi_phy->clks); + if (ret) + return ret; + + hdmi_phy->supplies[0].supply = "vddio"; + hdmi_phy->supplies[0].init_load_uA = 100000; + hdmi_phy->supplies[1].supply = "vcca"; + hdmi_phy->supplies[1].init_load_uA = 10000; + ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(hdmi_phy->supplies), hdmi_phy->supplies); + if (ret) + 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; + + init.ops = cfg->pll_ops; + 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, cfg->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 qmp_hdmi_of_match_table[] = { + { + .compatible = "qcom,hdmi-phy-8996", .data = &qmp_hdmi_8996_cfg, + }, + { }, +}; +MODULE_DEVICE_TABLE(of, qmp_hdmi_of_match_table); + +DEFINE_RUNTIME_DEV_PM_OPS(qmp_hdmi_pm_ops, + qmp_hdmi_runtime_suspend, + qmp_hdmi_runtime_resume, + NULL); + +static struct platform_driver qmp_hdmi_driver = { + .probe = qmp_hdmi_probe, + .driver = { + .name = "qcom-qmp-hdmi-phy", + .of_match_table = qmp_hdmi_of_match_table, + .pm = &qmp_hdmi_pm_ops, + }, +}; + +module_platform_driver(qmp_hdmi_driver); + +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>"); +MODULE_DESCRIPTION("Qualcomm QMP HDMI PHY driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c new file mode 100644 index 000000000000..27ffa70d0faa --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c @@ -0,0 +1,441 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. + */ + +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/phy/phy.h> + +#include "phy-qcom-qmp-hdmi.h" +#include "phy-qcom-qmp-qserdes-com.h" +#include "phy-qcom-qmp-qserdes-txrx.h" + +#define HDMI_VCO_MAX_FREQ 12000000000UL +#define HDMI_VCO_MIN_FREQ 8000000000UL + +#define HDMI_PCLK_MAX_FREQ 600000000UL +#define HDMI_PCLK_MIN_FREQ 25000000UL + +#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL +#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL +#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL +#define HDMI_DEFAULT_REF_CLOCK 19200000 +#define HDMI_PLL_CMP_CNT 1024 + +#define HDMI_PLL_POLL_MAX_READS 100 +#define HDMI_PLL_POLL_TIMEOUT_US 150 + +#define REG_HDMI_8996_PHY_CFG 0x00000000 +#define REG_HDMI_8996_PHY_PD_CTL 0x00000004 +#define REG_HDMI_8996_PHY_MODE 0x00000008 +#define REG_HDMI_8996_PHY_MISR_CLEAR 0x0000000c +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_CFG0 0x00000010 +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_CFG1 0x00000014 +#define REG_HDMI_8996_PHY_TX0_TX1_PRBS_SEED_BYTE0 0x00000018 +#define REG_HDMI_8996_PHY_TX0_TX1_PRBS_SEED_BYTE1 0x0000001c +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_PATTERN0 0x00000020 +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_PATTERN1 0x00000024 +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_CFG0 0x00000028 +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_CFG1 0x0000002c +#define REG_HDMI_8996_PHY_TX2_TX3_PRBS_SEED_BYTE0 0x00000030 +#define REG_HDMI_8996_PHY_TX2_TX3_PRBS_SEED_BYTE1 0x00000034 +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_PATTERN0 0x00000038 +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_PATTERN1 0x0000003c +#define REG_HDMI_8996_PHY_DEBUG_BUS_SEL 0x00000040 +#define REG_HDMI_8996_PHY_TXCAL_CFG0 0x00000044 +#define REG_HDMI_8996_PHY_TXCAL_CFG1 0x00000048 +#define REG_HDMI_8996_PHY_TX0_TX1_LANE_CTL 0x0000004c +#define REG_HDMI_8996_PHY_TX2_TX3_LANE_CTL 0x00000050 +#define REG_HDMI_8996_PHY_LANE_BIST_CONFIG 0x00000054 +#define REG_HDMI_8996_PHY_CLOCK 0x00000058 +#define REG_HDMI_8996_PHY_MISC1 0x0000005c +#define REG_HDMI_8996_PHY_MISC2 0x00000060 +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_STATUS0 0x00000064 +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_STATUS1 0x00000068 +#define REG_HDMI_8996_PHY_TX0_TX1_BIST_STATUS2 0x0000006c +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_STATUS0 0x00000070 +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_STATUS1 0x00000074 +#define REG_HDMI_8996_PHY_TX2_TX3_BIST_STATUS2 0x00000078 +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS0 0x0000007c +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS1 0x00000080 +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS2 0x00000084 +#define REG_HDMI_8996_PHY_PRE_MISR_STATUS3 0x00000088 +#define REG_HDMI_8996_PHY_POST_MISR_STATUS0 0x0000008c +#define REG_HDMI_8996_PHY_POST_MISR_STATUS1 0x00000090 +#define REG_HDMI_8996_PHY_POST_MISR_STATUS2 0x00000094 +#define REG_HDMI_8996_PHY_POST_MISR_STATUS3 0x00000098 +#define REG_HDMI_8996_PHY_STATUS 0x0000009c +#define REG_HDMI_8996_PHY_MISC3_STATUS 0x000000a0 +#define REG_HDMI_8996_PHY_MISC4_STATUS 0x000000a4 +#define REG_HDMI_8996_PHY_DEBUG_BUS0 0x000000a8 +#define REG_HDMI_8996_PHY_DEBUG_BUS1 0x000000ac +#define REG_HDMI_8996_PHY_DEBUG_BUS2 0x000000b0 +#define REG_HDMI_8996_PHY_DEBUG_BUS3 0x000000b4 +#define REG_HDMI_8996_PHY_PHY_REVISION_ID0 0x000000b8 +#define REG_HDMI_8996_PHY_PHY_REVISION_ID1 0x000000bc +#define REG_HDMI_8996_PHY_PHY_REVISION_ID2 0x000000c0 +#define REG_HDMI_8996_PHY_PHY_REVISION_ID3 0x000000c4 + +struct qmp_hdmi_8996_post_divider { + u64 vco_freq; + int hsclk_divsel; + int vco_ratio; + int tx_band_sel; +}; + +static inline u32 qmp_hdmi_8996_pll_get_pll_cmp(u64 fdata, unsigned long ref_clk) +{ + u64 dividend = HDMI_PLL_CMP_CNT * fdata; + u32 divisor = ref_clk * 10; + u32 rem; + + rem = do_div(dividend, divisor); + if (rem > (divisor >> 1)) + dividend++; + + return dividend - 1; +} + +static int qmp_hdmi_8996_pll_get_post_div(struct qmp_hdmi_8996_post_divider *pd, u64 bclk) +{ + int ratio[] = { 2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35 }; + int hs_divsel[] = { 0, 4, 8, 12, 1, 5, 2, 9, 3, 13, 10, 7, 14, 11, 15 }; + int tx_band_sel[] = { 0, 1, 2, 3 }; + u64 vco_freq[60]; + u64 vco, vco_optimal; + int half_rate_mode = 0; + int vco_optimal_index, vco_freq_index; + int i, j; + +retry: + vco_optimal = HDMI_VCO_MAX_FREQ; + vco_optimal_index = -1; + vco_freq_index = 0; + for (i = 0; i < 15; i++) { + for (j = 0; j < 4; j++) { + u32 ratio_mult = ratio[i] << tx_band_sel[j]; + + vco = bclk >> half_rate_mode; + vco *= ratio_mult; + vco_freq[vco_freq_index++] = vco; + } + } + + for (i = 0; i < 60; i++) { + u64 vco_tmp = vco_freq[i]; + + if ((vco_tmp >= HDMI_VCO_MIN_FREQ) && + (vco_tmp <= vco_optimal)) { + vco_optimal = vco_tmp; + vco_optimal_index = i; + } + } + + if (vco_optimal_index == -1) { + if (!half_rate_mode) { + half_rate_mode = 1; + goto retry; + } + + return -EINVAL; + } + + pd->vco_freq = vco_optimal; + pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4]; + pd->vco_ratio = ratio[vco_optimal_index / 4]; + pd->hsclk_divsel = hs_divsel[vco_optimal_index / 4]; + + return 0; +} + +static int qmp_hdmi_8996_phy_set_rate(struct qmp_hdmi_phy *hdmi_phy) +{ + unsigned long parent_rate = HDMI_DEFAULT_REF_CLOCK; + unsigned long rate = hdmi_phy->hdmi_opts.pixel_clk_rate * 1000; + struct qmp_hdmi_8996_post_divider pd; + bool gen_ssc = false; + u64 bclk; + u64 dec_start; + u64 frac_start; + u64 fdata; + u32 pll_divisor; + u32 rem; + u32 integloop_gain; + u32 pll_cmp; + int i, ret; + + bclk = ((u64)rate) * 10; + ret = qmp_hdmi_8996_pll_get_post_div(&pd, bclk); + if (ret) { + dev_err(hdmi_phy->dev, "PLL calculation failed\n"); + return ret; + } + + dec_start = pd.vco_freq; + pll_divisor = 4 * parent_rate; + do_div(dec_start, pll_divisor); + + frac_start = pd.vco_freq * (1 << 20); + + rem = do_div(frac_start, pll_divisor); + frac_start -= dec_start * (1 << 20); + if (rem > (pll_divisor >> 1)) + frac_start++; + + fdata = pd.vco_freq; + do_div(fdata, pd.vco_ratio); + + pll_cmp = qmp_hdmi_8996_pll_get_pll_cmp(fdata, parent_rate); + + /* Initially shut down PHY */ + dev_dbg(hdmi_phy->dev, "Disabling PHY"); + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_PD_CTL, 0x0); + udelay(500); + + /* Power up sequence */ + hdmi_pll_write(hdmi_phy, QSERDES_COM_BG_CTRL, 0x04); + + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_PD_CTL, 0x1); + hdmi_pll_write(hdmi_phy, QSERDES_COM_RESETSM_CNTRL, 0x20); + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_TX0_TX1_LANE_CTL, 0x0f); + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_TX2_TX3_LANE_CTL, 0x0f); + + hdmi_tx_chan_write(hdmi_phy, 0, QSERDES_TX_LANE_MODE, 0x43); + hdmi_tx_chan_write(hdmi_phy, 2, QSERDES_TX_LANE_MODE, 0x43); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1e); + hdmi_pll_write(hdmi_phy, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07); + hdmi_pll_write(hdmi_phy, QSERDES_COM_SYSCLK_EN_SEL, 0x37); + hdmi_pll_write(hdmi_phy, QSERDES_COM_SYS_CLK_CTRL, 0x02); + hdmi_pll_write(hdmi_phy, QSERDES_COM_CLK_ENABLE1, 0x0e); + + if (frac_start != 0 || gen_ssc) { + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_CCTRL_MODE0, 0x28); + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); + hdmi_pll_write(hdmi_phy, QSERDES_COM_CP_CTRL_MODE0, + 11000000 / (parent_rate/ 20)); + integloop_gain = (64 * parent_rate) / HDMI_DEFAULT_REF_CLOCK; + } else { + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_CCTRL_MODE0, 0x01); + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_RCTRL_MODE0, 0x10); + hdmi_pll_write(hdmi_phy, QSERDES_COM_CP_CTRL_MODE0, 0x23); + integloop_gain = (1022 * parent_rate) / (100 * 1000 * 1000); + } + + /* Bypass VCO calibration */ + if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) { + hdmi_pll_write(hdmi_phy, QSERDES_COM_SVS_MODE_CLK_SEL, 1); + integloop_gain <<= 1; + } else { + hdmi_pll_write(hdmi_phy, QSERDES_COM_SVS_MODE_CLK_SEL, 2); + integloop_gain <<= 2; + } + + integloop_gain = min_t(u32, integloop_gain, 2046); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_BG_TRIM, 0x0f); + hdmi_pll_write(hdmi_phy, QSERDES_COM_PLL_IVCO, 0x0f); + hdmi_pll_write(hdmi_phy, QSERDES_COM_VCO_TUNE_CTRL, 0); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_BG_CTRL, 0x06); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_CLK_SELECT, 0x30); + hdmi_pll_write(hdmi_phy, QSERDES_COM_HSCLK_SEL, 0x20 | pd.hsclk_divsel); + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP_EN, 0x0); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_DEC_START_MODE0, dec_start); + hdmi_pll_write(hdmi_phy, QSERDES_COM_DIV_FRAC_START1_MODE0, + frac_start & 0xff); + hdmi_pll_write(hdmi_phy, QSERDES_COM_DIV_FRAC_START2_MODE0, + (frac_start >> 8) & 0xff); + hdmi_pll_write(hdmi_phy, QSERDES_COM_DIV_FRAC_START3_MODE0, + (frac_start >> 16) & 0xf); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_INTEGLOOP_GAIN0_MODE0, + integloop_gain & 0xff); + hdmi_pll_write(hdmi_phy, QSERDES_COM_INTEGLOOP_GAIN1_MODE0, + (integloop_gain >> 8) & 0xff); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP1_MODE0, + pll_cmp & 0xff); + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP2_MODE0, + (pll_cmp >> 8) & 0xff); + hdmi_pll_write(hdmi_phy, QSERDES_COM_LOCK_CMP3_MODE0, + (pll_cmp >> 16) & 0x3); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_VCO_TUNE_MAP, 0x00); + hdmi_pll_write(hdmi_phy, QSERDES_COM_CORE_CLK_EN, 0x2c); + hdmi_pll_write(hdmi_phy, QSERDES_COM_CORECLK_DIV, 5); + hdmi_pll_write(hdmi_phy, QSERDES_COM_CMN_CONFIG, 0x02); + + hdmi_pll_write(hdmi_phy, QSERDES_COM_RESCODE_DIV_NUM, 0x15); + + /* TX lanes setup (TX 0/1/2/3) */ + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_CLKBUF_ENABLE, 0x03); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_BAND, pd.tx_band_sel + 4); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_RESET_TSYNC_EN, 0x03); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL1, 0x00); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL_OFFSET, 0x00); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_RES_CODE_LANE_OFFSET, 0x00); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TRAN_DRVR_EMP_EN, 0x03); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_PARRATE_REC_DETECT_IDLE_EN, 0x40); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_HP_PD_ENABLES, + i != 3 ? 0xc : 0x3); + } + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL, + i != 3 ? 0x25 : 0x22); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_EMP_POST1_LVL, + i != 3 ? 0x23 : 0x27); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL2, + i != 3 ? 0x0d : 0x00); + } + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL, 0x25); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_EMP_POST1_LVL, 0x23); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL2, + i != 3 ? 0x0d : 0x00); + } + } else { + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) { + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_DRV_LVL, 0x20); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_TX_EMP_POST1_LVL, 0x20); + hdmi_tx_chan_write(hdmi_phy, i, QSERDES_TX_VMODE_CTRL2, 0x0e); + } + } + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_MODE, 0x10); + else + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_MODE, 0x00); + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_PD_CTL, 0x1f); + + return 0; +} + +static int qmp_hdmi_8996_phy_power_on(struct phy *phy) +{ + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + u32 status; + int i, ret = 0; + + ret = qmp_hdmi_8996_phy_set_rate(hdmi_phy); + if (ret) { + dev_err(hdmi_phy->dev, "Setting pixel clock rate failed\n"); + return ret; + } + + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x1); + udelay(100); + + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x19); + udelay(100); + + ret = readl_poll_timeout(hdmi_phy->serdes + QSERDES_COM_C_READY_STATUS, + status, status & BIT(0), + HDMI_PLL_POLL_TIMEOUT_US, + HDMI_PLL_POLL_MAX_READS * HDMI_PLL_POLL_TIMEOUT_US); + + if (ret) { + dev_warn(hdmi_phy->dev, "HDMI PLL is not locked\n"); + return ret; + } + + for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) + hdmi_tx_chan_write(hdmi_phy, i, + QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6f); + + /* Disable SSC */ + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_PER1, 0x0); + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_PER2, 0x0); + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_STEP_SIZE1, 0x0); + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_STEP_SIZE2, 0x0); + hdmi_pll_write(hdmi_phy, QSERDES_COM_SSC_EN_CENTER, 0x2); + + ret = readl_poll_timeout(hdmi_phy->phy_reg + REG_HDMI_8996_PHY_STATUS, + status, status & BIT(0), + HDMI_PLL_POLL_TIMEOUT_US, + HDMI_PLL_POLL_MAX_READS * HDMI_PLL_POLL_TIMEOUT_US); + if (ret) { + dev_warn(hdmi_phy->dev, "HDMI PLL is not locked\n"); + return ret; + } + + /* Restart the retiming buffer */ + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x18); + udelay(1); + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x19); + + return 0; +} + +static int qmp_hdmi_8996_phy_power_off(struct phy *phy) +{ + struct qmp_hdmi_phy *hdmi_phy = phy_get_drvdata(phy); + + hdmi_phy_write(hdmi_phy, REG_HDMI_8996_PHY_CFG, 0x6); + usleep_range(100, 150); + + return 0; +} + +static long qmp_hdmi_8996_pll_round_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long *parent_rate) +{ + return clamp(rate, HDMI_PCLK_MIN_FREQ, HDMI_PCLK_MAX_FREQ); +} + +static unsigned long qmp_hdmi_8996_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct qmp_hdmi_phy *phy = hw_clk_to_pll(hw); + u32 cmp1, cmp2, cmp3, pll_cmp; + + cmp1 = hdmi_pll_read(phy, QSERDES_COM_LOCK_CMP1_MODE0); + cmp2 = hdmi_pll_read(phy, QSERDES_COM_LOCK_CMP2_MODE0); + cmp3 = hdmi_pll_read(phy, QSERDES_COM_LOCK_CMP3_MODE0); + + pll_cmp = cmp1 | (cmp2 << 8) | (cmp3 << 16); + + return mult_frac(pll_cmp + 1, parent_rate, HDMI_PLL_CMP_CNT); +} + +static int qmp_hdmi_8996_pll_is_enabled(struct clk_hw *hw) +{ + struct qmp_hdmi_phy *phy = hw_clk_to_pll(hw); + u32 status; + int pll_locked; + + status = hdmi_pll_read(phy, QSERDES_COM_C_READY_STATUS); + pll_locked = status & BIT(0); + + return pll_locked; +} + +static const struct clk_ops qmp_hdmi_8996_pll_ops = { + .recalc_rate = qmp_hdmi_8996_pll_recalc_rate, + .round_rate = qmp_hdmi_8996_pll_round_rate, + .is_enabled = qmp_hdmi_8996_pll_is_enabled, +}; + +static const struct phy_ops qmp_hdmi_8996_phy_ops = { + .init = qmp_hdmi_phy_init, + .configure = qmp_hdmi_phy_configure, + .power_on = qmp_hdmi_8996_phy_power_on, + .power_off = qmp_hdmi_8996_phy_power_off, + .exit = qmp_hdmi_phy_exit, + .owner = THIS_MODULE, +}; + +const struct qmp_hdmi_cfg qmp_hdmi_8996_cfg = { + .pll_ops = &qmp_hdmi_8996_pll_ops, + .phy_ops = &qmp_hdmi_8996_phy_ops, +}; diff --git a/drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h new file mode 100644 index 000000000000..25d307a8f287 --- /dev/null +++ b/drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2023, Linaro Ltd. + */ + +#ifndef PHY_QCOM_QMP_HDMI_H +#define PHY_QCOM_QMP_HDMI_H + +#include <linux/clk.h> +#include <linux/clk-provider.h> +#include <linux/regulator/consumer.h> +#include <linux/phy/phy-hdmi.h> + +#define MAX_CLKS 2 +#define MAX_SUPPLIES 2 + +#define HDMI_NUM_TX_CHANNEL 4 + +struct qmp_hdmi_phy { + struct device *dev; + struct phy *phy; + void __iomem *serdes; + void __iomem *tx[HDMI_NUM_TX_CHANNEL]; + void __iomem *phy_reg; + + struct phy_configure_opts_hdmi hdmi_opts; + + struct clk_hw pll_hw; + struct clk_bulk_data clks[MAX_CLKS]; + struct regulator_bulk_data supplies[MAX_SUPPLIES]; +}; + +struct qmp_hdmi_cfg { + const struct clk_ops *pll_ops; + const struct phy_ops *phy_ops; +}; + +#define hw_clk_to_pll(x) container_of(x, struct qmp_hdmi_phy, pll_hw) + +static inline void hdmi_phy_write(struct qmp_hdmi_phy *phy, int offset, + u32 data) +{ + writel(data, phy->phy_reg + offset); +} + +static inline u32 hdmi_phy_read(struct qmp_hdmi_phy *phy, int offset) +{ + return readl(phy->phy_reg + offset); +} + +static inline void hdmi_pll_write(struct qmp_hdmi_phy *phy, int offset, + u32 data) +{ + writel(data, phy->serdes + offset); +} + +static inline u32 hdmi_pll_read(struct qmp_hdmi_phy *phy, int offset) +{ + return readl(phy->serdes + offset); +} + +static inline void hdmi_tx_chan_write(struct qmp_hdmi_phy *phy, int channel, + int offset, int data) +{ + writel(data, phy->tx[channel] + offset); +} + +int qmp_hdmi_phy_init(struct phy *phy); +int qmp_hdmi_phy_configure(struct phy *phy, union phy_configure_opts *opts); +int qmp_hdmi_phy_exit(struct phy *phy); + +extern const struct qmp_hdmi_cfg qmp_hdmi_8996_cfg; + +#endif
Port Qualcomm QMP HDMI PHY to the generic PHY framework. Split the generic part and the msm8996 part. When adding support for msm8992/4 and msm8998 (which also employ QMP for HDMI PHY), one will have to provide the PLL programming part only. Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> --- drivers/phy/qualcomm/Kconfig | 7 + drivers/phy/qualcomm/Makefile | 5 + drivers/phy/qualcomm/phy-qcom-qmp-hdmi-base.c | 184 ++++++++ .../phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c | 441 ++++++++++++++++++ drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h | 75 +++ 5 files changed, 712 insertions(+) create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-hdmi-base.c create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-hdmi-msm8996.c create mode 100644 drivers/phy/qualcomm/phy-qcom-qmp-hdmi.h