1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Meson8, Meson8b and Meson8m2 HDMI TX PHY. 4 * 5 * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com> 6 */ 7 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> 18 19 /* 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 23 * BSP / kernel. 24 */ 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) 28 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) 32 33 #define HHI_HDMI_PHY_CNTL2 0x3a8 34 35 struct phy_meson8_hdmi_tx_priv { 36 struct regmap *hhi; 37 struct clk *tmds_clk; 38 }; 39 40 static int phy_meson8_hdmi_tx_init(struct phy *phy) 41 { 42 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); 43 44 return clk_prepare_enable(priv->tmds_clk); 45 } 46 47 static int phy_meson8_hdmi_tx_exit(struct phy *phy) 48 { 49 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); 50 51 clk_disable_unprepare(priv->tmds_clk); 52 53 return 0; 54 } 55 56 static int phy_meson8_hdmi_tx_power_on(struct phy *phy) 57 { 58 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); 59 unsigned int i; 60 u16 hdmi_ctl0; 61 62 if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000) 63 hdmi_ctl0 = 0x1e8b; 64 else 65 hdmi_ctl0 = 0x4d0b; 66 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)); 70 71 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x0); 72 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); 79 80 regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 81 HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE); 82 usleep_range(1000, 2000); 83 } 84 85 return 0; 86 } 87 88 static int phy_meson8_hdmi_tx_power_off(struct phy *phy) 89 { 90 struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy); 91 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)); 95 96 return 0; 97 } 98 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, 105 }; 106 107 static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev) 108 { 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; 113 struct phy *phy; 114 115 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 116 if (!res) 117 return -EINVAL; 118 119 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 120 if (!priv) 121 return -ENOMEM; 122 123 priv->hhi = syscon_node_to_regmap(np->parent); 124 if (IS_ERR(priv->hhi)) 125 return PTR_ERR(priv->hhi); 126 127 priv->tmds_clk = devm_clk_get(&pdev->dev, NULL); 128 if (IS_ERR(priv->tmds_clk)) 129 return PTR_ERR(priv->tmds_clk); 130 131 phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_ops); 132 if (IS_ERR(phy)) 133 return PTR_ERR(phy); 134 135 phy_set_drvdata(phy, priv); 136 137 phy_provider = devm_of_phy_provider_register(&pdev->dev, 138 of_phy_simple_xlate); 139 140 return PTR_ERR_OR_ZERO(phy_provider); 141 } 142 143 static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = { 144 { .compatible = "amlogic,meson8-hdmi-tx-phy" }, 145 { /* sentinel */ } 146 }; 147 MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match); 148 149 static struct platform_driver phy_meson8_hdmi_tx_driver = { 150 .probe = phy_meson8_hdmi_tx_probe, 151 .driver = { 152 .name = "phy-meson8-hdmi-tx", 153 .of_match_table = phy_meson8_hdmi_tx_of_match, 154 }, 155 }; 156 module_platform_driver(phy_meson8_hdmi_tx_driver); 157 158 MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); 159 MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver"); 160 MODULE_LICENSE("GPL v2"); 161