-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
phy: amlogic: Add a new driver for the HDMI TX PHY on Meson8/8b/8m2
Amlogic Meson8/8b/8m2 have a built-in HDMI PHY in the HHI register region. Unfortunately only few register bits are documented. For HHI_HDMI_PHY_CNTL0 the magic numbers are taken from the 3.10 vendor kernel. Signed-off-by: Martin Blumenstingl <martin.blumenstingl@googlemail.com> Link: https://lore.kernel.org/r/20211020195107.1564533-3-martin.blumenstingl@googlemail.com Signed-off-by: Vinod Koul <vkoul@kernel.org>
- Loading branch information
Martin Blumenstingl
authored and
Vinod Koul
committed
Nov 23, 2021
1 parent
3870a48
commit e45dbd3
Showing
3 changed files
with
171 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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_device.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"); |