GNU Linux-libre 6.8.9-gnu
[releases.git] / drivers / phy / amlogic / phy-meson8-hdmi-tx.c
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
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");