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.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 
phy_meson8_hdmi_tx_init(struct phy * phy)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 
phy_meson8_hdmi_tx_exit(struct phy * phy)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 
phy_meson8_hdmi_tx_power_on(struct phy * phy)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 
phy_meson8_hdmi_tx_power_off(struct phy * phy)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 
phy_meson8_hdmi_tx_probe(struct platform_device * pdev)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