Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  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");