summaryrefslogtreecommitdiffstats
path: root/drivers/phy/amlogic/phy-meson8-hdmi-tx.c
blob: 2617f7f6c2ec593b368c9351ca44593be7470461 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// SPDX-License-Identifier: GPL-2.0+
/*
 * Meson8, Meson8b and Meson8m2 HDMI TX PHY.
 *
 * Copyright (C) 2021 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
 */

#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/regmap.h>

/*
 * Unfortunately there is no detailed documentation available for the
 * HHI_HDMI_PHY_CNTL0 register. CTL0 and CTL1 is all we know about.
 * Magic register values in the driver below are taken from the vendor
 * BSP / kernel.
 */
#define HHI_HDMI_PHY_CNTL0				0x3a0
	#define HHI_HDMI_PHY_CNTL0_HDMI_CTL1		GENMASK(31, 16)
	#define HHI_HDMI_PHY_CNTL0_HDMI_CTL0		GENMASK(15, 0)

#define HHI_HDMI_PHY_CNTL1				0x3a4
	#define HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE		BIT(1)
	#define HHI_HDMI_PHY_CNTL1_SOFT_RESET		BIT(0)

#define HHI_HDMI_PHY_CNTL2				0x3a8

struct phy_meson8_hdmi_tx_priv {
	struct regmap		*hhi;
	struct clk		*tmds_clk;
};

static int phy_meson8_hdmi_tx_init(struct phy *phy)
{
	struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);

	return clk_prepare_enable(priv->tmds_clk);
}

static int phy_meson8_hdmi_tx_exit(struct phy *phy)
{
	struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);

	clk_disable_unprepare(priv->tmds_clk);

	return 0;
}

static int phy_meson8_hdmi_tx_power_on(struct phy *phy)
{
	struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);
	unsigned int i;
	u16 hdmi_ctl0;

	if (clk_get_rate(priv->tmds_clk) >= 2970UL * 1000 * 1000)
		hdmi_ctl0 = 0x1e8b;
	else
		hdmi_ctl0 = 0x4d0b;

	regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0,
		     FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x08c3) |
		     FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, hdmi_ctl0));

	regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1, 0x0);

	/* Reset three times, just like the vendor driver does */
	for (i = 0; i < 3; i++) {
		regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1,
			     HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE |
			     HHI_HDMI_PHY_CNTL1_SOFT_RESET);
		usleep_range(1000, 2000);

		regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL1,
			     HHI_HDMI_PHY_CNTL1_CLOCK_ENABLE);
		usleep_range(1000, 2000);
	}

	return 0;
}

static int phy_meson8_hdmi_tx_power_off(struct phy *phy)
{
	struct phy_meson8_hdmi_tx_priv *priv = phy_get_drvdata(phy);

	regmap_write(priv->hhi, HHI_HDMI_PHY_CNTL0,
		     FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL1, 0x0841) |
		     FIELD_PREP(HHI_HDMI_PHY_CNTL0_HDMI_CTL0, 0x8d00));

	return 0;
}

static const struct phy_ops phy_meson8_hdmi_tx_ops = {
	.init		= phy_meson8_hdmi_tx_init,
	.exit		= phy_meson8_hdmi_tx_exit,
	.power_on	= phy_meson8_hdmi_tx_power_on,
	.power_off	= phy_meson8_hdmi_tx_power_off,
	.owner		= THIS_MODULE,
};

static int phy_meson8_hdmi_tx_probe(struct platform_device *pdev)
{
	struct device_node *np = pdev->dev.of_node;
	struct phy_meson8_hdmi_tx_priv *priv;
	struct phy_provider *phy_provider;
	struct resource *res;
	struct phy *phy;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (!res)
		return -EINVAL;

	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->hhi = syscon_node_to_regmap(np->parent);
	if (IS_ERR(priv->hhi))
		return PTR_ERR(priv->hhi);

	priv->tmds_clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(priv->tmds_clk))
		return PTR_ERR(priv->tmds_clk);

	phy = devm_phy_create(&pdev->dev, np, &phy_meson8_hdmi_tx_ops);
	if (IS_ERR(phy))
		return PTR_ERR(phy);

	phy_set_drvdata(phy, priv);

	phy_provider = devm_of_phy_provider_register(&pdev->dev,
						     of_phy_simple_xlate);

	return PTR_ERR_OR_ZERO(phy_provider);
}

static const struct of_device_id phy_meson8_hdmi_tx_of_match[] = {
	{ .compatible = "amlogic,meson8-hdmi-tx-phy" },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, phy_meson8_hdmi_tx_of_match);

static struct platform_driver phy_meson8_hdmi_tx_driver = {
	.probe	= phy_meson8_hdmi_tx_probe,
	.driver	= {
		.name		= "phy-meson8-hdmi-tx",
		.of_match_table	= phy_meson8_hdmi_tx_of_match,
	},
};
module_platform_driver(phy_meson8_hdmi_tx_driver);

MODULE_AUTHOR("Martin Blumenstingl <martin.blumenstingl@googlemail.com>");
MODULE_DESCRIPTION("Meson8, Meson8b and Meson8m2 HDMI TX PHY driver");
MODULE_LICENSE("GPL v2");