Port the HDMI PHY driver for Qualcomm MSM8260 / MSM8660 / APQ8060 platforms.

Signed-off-by: Dmitry Baryshkov <dmitry.barysh...@linaro.org>
---
 drivers/phy/qualcomm/Kconfig                 |  11 +
 drivers/phy/qualcomm/Makefile                |   1 +
 drivers/phy/qualcomm/phy-qcom-hdmi-msm8x60.c | 353 +++++++++++++++++++
 3 files changed, 365 insertions(+)
 create mode 100644 drivers/phy/qualcomm/phy-qcom-hdmi-msm8x60.c

diff --git a/drivers/phy/qualcomm/Kconfig b/drivers/phy/qualcomm/Kconfig
index 838f93ad1168..a603d4777a02 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_MSM8X60
+       tristate "Qualcomm MSM8x60 HDMI PHY driver"
+       depends on ARCH_QCOM || COMPILE_TEST
+       depends on OF
+       depends on COMMON_CLK
+       default DRM_MSM_HDMI && ARCH_MSM8X60
+       select GENERIC_PHY
+       help
+         Enable this to support the Qualcomm HDMI PHY presend on MSM8260,
+         MSM8660 and APQ8060 platforms.
+
 config PHY_QCOM_HDMI_MSM8960
        tristate "Qualcomm MSM8960 HDMI PHY driver"
        depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/phy/qualcomm/Makefile b/drivers/phy/qualcomm/Makefile
index 6d7d76c7fee0..cbc730c074ad 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_MSM8X60)    += phy-qcom-hdmi-msm8x60.o
 obj-$(CONFIG_PHY_QCOM_HDMI_MSM8960)    += phy-qcom-hdmi-msm8960.o
 obj-$(CONFIG_PHY_QCOM_PCIE2)           += phy-qcom-pcie2.o
 
diff --git a/drivers/phy/qualcomm/phy-qcom-hdmi-msm8x60.c 
b/drivers/phy/qualcomm/phy-qcom-hdmi-msm8x60.c
new file mode 100644
index 000000000000..a5bacab80909
--- /dev/null
+++ b/drivers/phy/qualcomm/phy-qcom-hdmi-msm8x60.c
@@ -0,0 +1,353 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2013 Red Hat
+ * Author: Rob Clark <robdcl...@gmail.com>
+ * Copyright (c) 2023, Linaro Ltd.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.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_8x60_PHY_REG0                                 0x00000000
+#define HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__MASK                        
0x0000001c
+
+#define REG_HDMI_8x60_PHY_REG1                                 0x00000004
+#define HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__MASK                 0x000000f0
+#define HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__MASK             0x0000000f
+
+#define REG_HDMI_8x60_PHY_REG2                                 0x00000008
+#define HDMI_8x60_PHY_REG2_PD_DESER                            0x00000001
+#define HDMI_8x60_PHY_REG2_PD_DRIVE_1                          0x00000002
+#define HDMI_8x60_PHY_REG2_PD_DRIVE_2                          0x00000004
+#define HDMI_8x60_PHY_REG2_PD_DRIVE_3                          0x00000008
+#define HDMI_8x60_PHY_REG2_PD_DRIVE_4                          0x00000010
+#define HDMI_8x60_PHY_REG2_PD_PLL                              0x00000020
+#define HDMI_8x60_PHY_REG2_PD_PWRGEN                           0x00000040
+#define HDMI_8x60_PHY_REG2_RCV_SENSE_EN                                
0x00000080
+
+#define REG_HDMI_8x60_PHY_REG3                                 0x0000000c
+#define HDMI_8x60_PHY_REG3_PLL_ENABLE                          0x00000001
+
+#define REG_HDMI_8x60_PHY_REG4                                 0x00000010
+
+#define REG_HDMI_8x60_PHY_REG5                                 0x00000014
+
+#define REG_HDMI_8x60_PHY_REG6                                 0x00000018
+
+#define REG_HDMI_8x60_PHY_REG7                                 0x0000001c
+
+#define REG_HDMI_8x60_PHY_REG8                                 0x00000020
+
+#define REG_HDMI_8x60_PHY_REG9                                 0x00000024
+
+#define REG_HDMI_8x60_PHY_REG10                                        
0x00000028
+
+#define REG_HDMI_8x60_PHY_REG11                                        
0x0000002c
+
+#define REG_HDMI_8x60_PHY_REG12                                        
0x00000030
+#define HDMI_8x60_PHY_REG12_RETIMING_EN                                
0x00000001
+#define HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN                 0x00000002
+#define HDMI_8x60_PHY_REG12_FORCE_LOCK                         0x00000010
+
+struct qcom_hdmi_8x60_phy {
+       struct device *dev;
+       struct phy *phy;
+       void __iomem *phy_reg;
+
+       struct phy_configure_opts_hdmi hdmi_opts;
+
+       struct clk *iface_clk;
+       struct regulator *vdda;
+};
+
+static inline void hdmi_phy_write(struct qcom_hdmi_8x60_phy *phy, int offset,
+                                 u32 data)
+{
+       writel(data, phy->phy_reg + offset);
+}
+
+static inline u32 hdmi_phy_read(struct qcom_hdmi_8x60_phy *phy, int offset)
+{
+       return readl(phy->phy_reg + offset);
+}
+
+static int qcom_hdmi_8x60_phy_power_on(struct phy *phy)
+{
+       struct qcom_hdmi_8x60_phy *hdmi_phy = phy_get_drvdata(phy);
+       unsigned long pixclock = hdmi_phy->hdmi_opts.pixel_clk_rate;
+
+       /* De-serializer delay D/C for non-lbk mode: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG0,
+                      FIELD_PREP(HDMI_8x60_PHY_REG0_DESER_DEL_CTRL__MASK, 3));
+
+       if (pixclock == 27000) {
+               /* video_format == HDMI_VFRMT_720x480p60_16_9 */
+               hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG1,
+                              
FIELD_PREP(HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__MASK, 5) |
+                              
FIELD_PREP(HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__MASK, 3));
+       } else {
+               hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG1,
+                              
FIELD_PREP(HDMI_8x60_PHY_REG1_DTEST_MUX_SEL__MASK, 5) |
+                              
FIELD_PREP(HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL__MASK, 4));
+       }
+
+       /* No matter what, start from the power down mode: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_PD_PWRGEN |
+                      HDMI_8x60_PHY_REG2_PD_PLL |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+
+       /* Turn PowerGen on: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_PD_PLL |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+
+       /* Turn PLL power on: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+
+       /* Write to HIGH after PLL power down de-assert: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG3,
+                      HDMI_8x60_PHY_REG3_PLL_ENABLE);
+
+       /* ASIC power on; PHY REG9 = 0 */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG9, 0);
+
+       /* Enable PLL lock detect, PLL lock det will go high after lock
+        * Enable the re-time logic
+        */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG12,
+                      HDMI_8x60_PHY_REG12_RETIMING_EN |
+                      HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN);
+
+       /* Drivers are on: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+
+       /* If the RX detector is needed: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG4, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG5, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG6, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG7, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG8, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG9, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG10, 0);
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG11, 0);
+
+       /* If we want to use lock enable based on counting: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG12,
+                      HDMI_8x60_PHY_REG12_RETIMING_EN |
+                      HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN |
+                      HDMI_8x60_PHY_REG12_FORCE_LOCK);
+
+       return 0;
+}
+
+static int qcom_hdmi_8x60_phy_power_off(struct phy *phy)
+{
+       struct qcom_hdmi_8x60_phy *hdmi_phy = phy_get_drvdata(phy);
+
+       /* Assert RESET PHY from controller */
+#if 0
+       hdmi_phy_write(hdmi_phy, REG_HDMI_PHY_CTRL,
+                      HDMI_PHY_CTRL_SW_RESET);
+       udelay(10);
+       /* De-assert RESET PHY from controller */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_PHY_CTRL, 0);
+#endif
+       /* Turn off Driver */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+       udelay(10);
+       /* Disable PLL */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG3, 0);
+       /* Power down PHY, but keep RX-sense: */
+       hdmi_phy_write(hdmi_phy, REG_HDMI_8x60_PHY_REG2,
+                      HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
+                      HDMI_8x60_PHY_REG2_PD_PWRGEN |
+                      HDMI_8x60_PHY_REG2_PD_PLL |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
+                      HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
+                      HDMI_8x60_PHY_REG2_PD_DESER);
+
+       return 0;
+}
+
+static int qcom_hdmi_8x60_phy_init(struct phy *phy)
+{
+       struct qcom_hdmi_8x60_phy *hdmi_phy = phy_get_drvdata(phy);
+
+       return pm_runtime_resume_and_get(hdmi_phy->dev);
+}
+
+static int qcom_hdmi_8x60_phy_exit(struct phy *phy)
+{
+       struct qcom_hdmi_8x60_phy *hdmi_phy = phy_get_drvdata(phy);
+
+       pm_runtime_put_noidle(hdmi_phy->dev);
+
+       return 0;
+}
+
+static int qcom_hdmi_8x60_phy_configure(struct phy *phy, union 
phy_configure_opts *opts)
+{
+        const struct phy_configure_opts_hdmi *hdmi_opts = &opts->hdmi;
+       struct qcom_hdmi_8x60_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_8x60_runtime_resume(struct device *dev)
+{
+       struct qcom_hdmi_8x60_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_8x60_runtime_suspend(struct device *dev)
+{
+       struct qcom_hdmi_8x60_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_8x60_phy_ops = {
+       .init           = qcom_hdmi_8x60_phy_init,
+       .configure      = qcom_hdmi_8x60_phy_configure,
+       .power_on       = qcom_hdmi_8x60_phy_power_on,
+       .power_off      = qcom_hdmi_8x60_phy_power_off,
+       .exit           = qcom_hdmi_8x60_phy_exit,
+       .owner          = THIS_MODULE,
+};
+
+static int qcom_hdmi_8x60_probe(struct platform_device *pdev)
+{
+       struct phy_provider *phy_provider;
+       struct device *dev = &pdev->dev;
+       struct qcom_hdmi_8x60_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->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->phy = devm_phy_create(dev, pdev->dev.of_node, 
&qcom_hdmi_8x60_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_8x60_of_match_table[] = {
+       {
+               .compatible = "qcom,hdmi-phy-8x60",
+       },
+       { },
+};
+MODULE_DEVICE_TABLE(of, qcom_hdmi_8x60_of_match_table);
+
+DEFINE_RUNTIME_DEV_PM_OPS(qcom_hdmi_8x60_pm_ops,
+                         qcom_hdmi_8x60_runtime_suspend,
+                         qcom_hdmi_8x60_runtime_resume,
+                         NULL);
+
+static struct platform_driver qcom_hdmi_8x60_driver = {
+       .probe          = qcom_hdmi_8x60_probe,
+       .driver = {
+               .name   = "qcom-8x60-hdmi-phy",
+               .of_match_table = qcom_hdmi_8x60_of_match_table,
+               .pm     = &qcom_hdmi_8x60_pm_ops,
+       },
+};
+
+module_platform_driver(qcom_hdmi_8x60_driver);
+
+MODULE_AUTHOR("Dmitry Baryshkov <dmitry.barysh...@linaro.org>");
+MODULE_DESCRIPTION("Qualcomm MSM8x60 HDMI PHY driver");
+MODULE_LICENSE("GPL");
-- 
2.39.2

Reply via email to