phy-meson8-hdmi-tx.c (4322B)
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 35struct phy_meson8_hdmi_tx_priv { 36 struct regmap *hhi; 37 struct clk *tmds_clk; 38}; 39 40static 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 47static 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 56static 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 88static 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 99static 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 107static 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 143static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = { 144 { .compatible = "amlogic,meson8-hdmi-tx-phy" }, 145 { /* sentinel */ } 146}; 147MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match); 148 149static 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}; 156module_platform_driver(phy_meson8_hdmi_tx_driver); 157 158MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>"); 159MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver"); 160MODULE_LICENSE("GPL v2");