1 // SPDX-License-Identifier: GPL-2.0+
3 * Meson8, Meson8b and Meson8m2 HDMI TX PHY.
8 #include <linux/bitfield.h>
9 #include <linux/bits.h>
10 #include <linux/clk.h>
11 #include <linux/mfd/syscon.h>
12 #include <linux/module.h>
13 #include <linux/of_device.h>
14 #include <linux/phy/phy.h>
15 #include <linux/platform_device.h>
16 #include <linux/property.h>
17 #include <linux/regmap.h>
20 * Unfortunately there is no detailed documentation available for the
21 * HHI_HDMI_PHY_CNTL0 register. CTL0 and CTL1 is all we know about.
22 * Magic register values in the driver below are taken from the vendor
25 #define HHI_HDMI_PHY_CNTL0 0x3a0
26 #define HHI_HDMI_PHY_CNTL0_HDMI_CTL1 GENMASK(31, 16)
27 #define HHI_HDMI_PHY_CNTL0_HDMI_CTL0 GENMASK(15, 0)
29 #define HHI_HDMI_PHY_CNTL1 0x3a4
30 #define HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE BIT(1)
31 #define HHI_HDMI_PHY_CNTL1_SOFT_RESET BIT(0)
33 #define HHI_HDMI_PHY_CNTL2 0x3a8
35 struct phy_meson8_hdmi_tx_priv {
40 static int phy_meson8_hdmi_tx_init(struct phy *phy)
42 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
44 return clk_prepare_enable(priv->tmds_clk);
47 static int phy_meson8_hdmi_tx_exit(struct phy *phy)
49 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
51 clk_disable_unprepare(priv->tmds_clk);
56 static int phy_meson8_hdmi_tx_power_on(struct phy *phy)
58 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
62 if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000)
67 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0,
68 FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x08c3) |
69 FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, hdmi_ctl0));
71 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x0);
73 /* Reset three times, just like the vendor driver does */
74 for (i = 0; i < 3; i++) {
75 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1,
76 HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE |
77 HHI_HDMI_PHY_CNTL1_SOFT_RESET);
78 usleep_range(1000, 2000);
80 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1,
81 HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE);
82 usleep_range(1000, 2000);
88 static int phy_meson8_hdmi_tx_power_off(struct phy *phy)
90 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
92 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0,
93 FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x0841) |
94 FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, 0x8d00));
99 static const struct phy_ops phy_meson8_hdmi_tx_ops = {
100 .init = phy_meson8_hdmi_tx_init,
101 .exit = phy_meson8_hdmi_tx_exit,
102 .power_on = phy_meson8_hdmi_tx_power_on,
103 .power_off = phy_meson8_hdmi_tx_power_off,
104 .owner = THIS_MODULE,
107 static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev)
109 struct device_node *np = pdev->dev.of_node;
110 struct phy_meson8_hdmi_tx_priv *priv;
111 struct phy_provider *phy_provider;
112 struct resource *res;
115 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
119 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
123 priv->hhi = syscon_node_to_regmap(np->parent);
124 if (IS_ERR(priv->hhi))
125 return PTR_ERR(priv->hhi);
127 priv->tmds_clk = devm_clk_get(&pdev->dev, NULL);
128 if (IS_ERR(priv->tmds_clk))
129 return PTR_ERR(priv->tmds_clk);
131 phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_ops);
135 phy_set_drvdata(phy, priv);
137 phy_provider = devm_of_phy_provider_register(&pdev->dev,
138 of_phy_simple_xlate);
140 return PTR_ERR_OR_ZERO(phy_provider);
143 static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = {
144 { .compatible = "amlogic,meson8-hdmi-tx-phy" },
147 MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match);
149 static struct platform_driver phy_meson8_hdmi_tx_driver = {
150 .probe = phy_meson8_hdmi_tx_probe,
152 .name = "phy-meson8-hdmi-tx",
153 .of_match_table = phy_meson8_hdmi_tx_of_match,
156 module_platform_driver(phy_meson8_hdmi_tx_driver);
159 MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver");
160 MODULE_LICENSE("GPL v2");