Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Phy drivers for STMicro platforms
#
config PHY_MIPHY28LP
tristate "STMicroelectronics MIPHY28LP PHY driver for STiH407"
depends on ARCH_STI || COMPILE_TEST
select GENERIC_PHY
help
Enable this to support the miphy transceiver (for SATA/PCIE/USB3)
that is part of STMicroelectronics STiH407 SoC.
config PHY_ST_SPEAR1310_MIPHY
tristate "ST SPEAR1310-MIPHY driver"
select GENERIC_PHY
depends on MACH_SPEAR1310 || COMPILE_TEST
help
Support for ST SPEAr1310 MIPHY which can be used for PCIe and SATA.
config PHY_ST_SPEAR1340_MIPHY
tristate "ST SPEAR1340-MIPHY driver"
select GENERIC_PHY
depends on MACH_SPEAR1340 || COMPILE_TEST
help
Support for ST SPEAr1340 MIPHY which can be used for PCIe and SATA.
config PHY_STIH407_USB
tristate "STMicroelectronics USB2 picoPHY driver for STiH407 family"
depends on RESET_CONTROLLER
depends on ARCH_STI || COMPILE_TEST
select GENERIC_PHY
help
Enable this support to enable the picoPHY device used by USB2
and USB3 controllers on STMicroelectronics STiH407 SoC families.
config PHY_STM32_COMBOPHY
tristate "STMicroelectronics COMBOPHY driver for STM32MP25"
depends on ARCH_STM32 || COMPILE_TEST
select GENERIC_PHY
help
Enable this to support the COMBOPHY device used by USB3 or PCIe
controllers on STMicroelectronics STM32MP25 SoC.
This driver controls the COMBOPHY block to generate the PCIe 100Mhz
reference clock from either the external clock generator or HSE
internal SoC clock source.
config PHY_STM32_USBPHYC
tristate "STMicroelectronics STM32 USB HS PHY Controller driver"
depends on ARCH_STM32 || COMPILE_TEST
depends on COMMON_CLK
select GENERIC_PHY
help
Enable this to support the High-Speed USB transceivers that are part
of some STMicroelectronics STM32 SoCs.
This driver controls the entire USB PHY block: the USB PHY controller
(USBPHYC) and the two 8-bit wide UTMI+ interfaces. First interface is
used by an HS USB Host controller, and the second one is shared
between an HS USB OTG controller and an HS USB Host controller,
selected by a USB switch.
+7
View File
@@ -0,0 +1,7 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PHY_MIPHY28LP) += phy-miphy28lp.o
obj-$(CONFIG_PHY_ST_SPEAR1310_MIPHY) += phy-spear1310-miphy.o
obj-$(CONFIG_PHY_ST_SPEAR1340_MIPHY) += phy-spear1340-miphy.o
obj-$(CONFIG_PHY_STIH407_USB) += phy-stih407-usb.o
obj-$(CONFIG_PHY_STM32_COMBOPHY) += phy-stm32-combophy.o
obj-$(CONFIG_PHY_STM32_USBPHYC) += phy-stm32-usbphyc.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,258 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ST SPEAr1310-miphy driver
*
* Copyright (C) 2014 ST Microelectronics
* Pratyush Anand <pratyush.anand@gmail.com>
* Mohit Kumar <mohit.kumar.dhaka@gmail.com>
*/
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.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/regmap.h>
/* SPEAr1310 Registers */
#define SPEAR1310_PCIE_SATA_CFG 0x3A4
#define SPEAR1310_PCIE_SATA2_SEL_PCIE (0 << 31)
#define SPEAR1310_PCIE_SATA1_SEL_PCIE (0 << 30)
#define SPEAR1310_PCIE_SATA0_SEL_PCIE (0 << 29)
#define SPEAR1310_PCIE_SATA2_SEL_SATA BIT(31)
#define SPEAR1310_PCIE_SATA1_SEL_SATA BIT(30)
#define SPEAR1310_PCIE_SATA0_SEL_SATA BIT(29)
#define SPEAR1310_SATA2_CFG_TX_CLK_EN BIT(27)
#define SPEAR1310_SATA2_CFG_RX_CLK_EN BIT(26)
#define SPEAR1310_SATA2_CFG_POWERUP_RESET BIT(25)
#define SPEAR1310_SATA2_CFG_PM_CLK_EN BIT(24)
#define SPEAR1310_SATA1_CFG_TX_CLK_EN BIT(23)
#define SPEAR1310_SATA1_CFG_RX_CLK_EN BIT(22)
#define SPEAR1310_SATA1_CFG_POWERUP_RESET BIT(21)
#define SPEAR1310_SATA1_CFG_PM_CLK_EN BIT(20)
#define SPEAR1310_SATA0_CFG_TX_CLK_EN BIT(19)
#define SPEAR1310_SATA0_CFG_RX_CLK_EN BIT(18)
#define SPEAR1310_SATA0_CFG_POWERUP_RESET BIT(17)
#define SPEAR1310_SATA0_CFG_PM_CLK_EN BIT(16)
#define SPEAR1310_PCIE2_CFG_DEVICE_PRESENT BIT(11)
#define SPEAR1310_PCIE2_CFG_POWERUP_RESET BIT(10)
#define SPEAR1310_PCIE2_CFG_CORE_CLK_EN BIT(9)
#define SPEAR1310_PCIE2_CFG_AUX_CLK_EN BIT(8)
#define SPEAR1310_PCIE1_CFG_DEVICE_PRESENT BIT(7)
#define SPEAR1310_PCIE1_CFG_POWERUP_RESET BIT(6)
#define SPEAR1310_PCIE1_CFG_CORE_CLK_EN BIT(5)
#define SPEAR1310_PCIE1_CFG_AUX_CLK_EN BIT(4)
#define SPEAR1310_PCIE0_CFG_DEVICE_PRESENT BIT(3)
#define SPEAR1310_PCIE0_CFG_POWERUP_RESET BIT(2)
#define SPEAR1310_PCIE0_CFG_CORE_CLK_EN BIT(1)
#define SPEAR1310_PCIE0_CFG_AUX_CLK_EN BIT(0)
#define SPEAR1310_PCIE_CFG_MASK(x) ((0xF << (x * 4)) | BIT((x + 29)))
#define SPEAR1310_SATA_CFG_MASK(x) ((0xF << (x * 4 + 16)) | \
BIT((x + 29)))
#define SPEAR1310_PCIE_CFG_VAL(x) \
(SPEAR1310_PCIE_SATA##x##_SEL_PCIE | \
SPEAR1310_PCIE##x##_CFG_AUX_CLK_EN | \
SPEAR1310_PCIE##x##_CFG_CORE_CLK_EN | \
SPEAR1310_PCIE##x##_CFG_POWERUP_RESET | \
SPEAR1310_PCIE##x##_CFG_DEVICE_PRESENT)
#define SPEAR1310_SATA_CFG_VAL(x) \
(SPEAR1310_PCIE_SATA##x##_SEL_SATA | \
SPEAR1310_SATA##x##_CFG_PM_CLK_EN | \
SPEAR1310_SATA##x##_CFG_POWERUP_RESET | \
SPEAR1310_SATA##x##_CFG_RX_CLK_EN | \
SPEAR1310_SATA##x##_CFG_TX_CLK_EN)
#define SPEAR1310_PCIE_MIPHY_CFG_1 0x3A8
#define SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT BIT(31)
#define SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 BIT(28)
#define SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(x) (x << 16)
#define SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT BIT(15)
#define SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 BIT(12)
#define SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(x) (x << 0)
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_MASK (0xFFFF)
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK (0xFFFF << 16)
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA \
(SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
SPEAR1310_MIPHY_DUAL_CLK_REF_DIV2 | \
SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(60) | \
SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
SPEAR1310_MIPHY_SINGLE_CLK_REF_DIV2 | \
SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(60))
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
(SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(120))
#define SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE \
(SPEAR1310_MIPHY_DUAL_OSC_BYPASS_EXT | \
SPEAR1310_MIPHY_DUAL_PLL_RATIO_TOP(25) | \
SPEAR1310_MIPHY_SINGLE_OSC_BYPASS_EXT | \
SPEAR1310_MIPHY_SINGLE_PLL_RATIO_TOP(25))
#define SPEAR1310_PCIE_MIPHY_CFG_2 0x3AC
enum spear1310_miphy_mode {
SATA,
PCIE,
};
struct spear1310_miphy_priv {
/* instance id of this phy */
u32 id;
/* phy mode: 0 for SATA 1 for PCIe */
enum spear1310_miphy_mode mode;
/* regmap for any soc specific misc registers */
struct regmap *misc;
/* phy struct pointer */
struct phy *phy;
};
static int spear1310_miphy_pcie_init(struct spear1310_miphy_priv *priv)
{
u32 val;
regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK,
SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE);
switch (priv->id) {
case 0:
val = SPEAR1310_PCIE_CFG_VAL(0);
break;
case 1:
val = SPEAR1310_PCIE_CFG_VAL(1);
break;
case 2:
val = SPEAR1310_PCIE_CFG_VAL(2);
break;
default:
return -EINVAL;
}
regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
SPEAR1310_PCIE_CFG_MASK(priv->id), val);
return 0;
}
static int spear1310_miphy_pcie_exit(struct spear1310_miphy_priv *priv)
{
regmap_update_bits(priv->misc, SPEAR1310_PCIE_SATA_CFG,
SPEAR1310_PCIE_CFG_MASK(priv->id), 0);
regmap_update_bits(priv->misc, SPEAR1310_PCIE_MIPHY_CFG_1,
SPEAR1310_PCIE_SATA_MIPHY_CFG_PCIE_MASK, 0);
return 0;
}
static int spear1310_miphy_init(struct phy *phy)
{
struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
int ret = 0;
if (priv->mode == PCIE)
ret = spear1310_miphy_pcie_init(priv);
return ret;
}
static int spear1310_miphy_exit(struct phy *phy)
{
struct spear1310_miphy_priv *priv = phy_get_drvdata(phy);
int ret = 0;
if (priv->mode == PCIE)
ret = spear1310_miphy_pcie_exit(priv);
return ret;
}
static const struct of_device_id spear1310_miphy_of_match[] = {
{ .compatible = "st,spear1310-miphy" },
{ },
};
MODULE_DEVICE_TABLE(of, spear1310_miphy_of_match);
static const struct phy_ops spear1310_miphy_ops = {
.init = spear1310_miphy_init,
.exit = spear1310_miphy_exit,
.owner = THIS_MODULE,
};
static struct phy *spear1310_miphy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct spear1310_miphy_priv *priv = dev_get_drvdata(dev);
if (args->args_count < 1) {
dev_err(dev, "DT did not pass correct no of args\n");
return ERR_PTR(-ENODEV);
}
priv->mode = args->args[0];
if (priv->mode != SATA && priv->mode != PCIE) {
dev_err(dev, "DT did not pass correct phy mode\n");
return ERR_PTR(-ENODEV);
}
return priv->phy;
}
static int spear1310_miphy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct spear1310_miphy_priv *priv;
struct phy_provider *phy_provider;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->misc =
syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
if (IS_ERR(priv->misc)) {
dev_err(dev, "failed to find misc regmap\n");
return PTR_ERR(priv->misc);
}
if (of_property_read_u32(dev->of_node, "phy-id", &priv->id)) {
dev_err(dev, "failed to find phy id\n");
return -EINVAL;
}
priv->phy = devm_phy_create(dev, NULL, &spear1310_miphy_ops);
if (IS_ERR(priv->phy)) {
dev_err(dev, "failed to create SATA PCIe PHY\n");
return PTR_ERR(priv->phy);
}
dev_set_drvdata(dev, priv);
phy_set_drvdata(priv->phy, priv);
phy_provider =
devm_of_phy_provider_register(dev, spear1310_miphy_xlate);
if (IS_ERR(phy_provider)) {
dev_err(dev, "failed to register phy provider\n");
return PTR_ERR(phy_provider);
}
return 0;
}
static struct platform_driver spear1310_miphy_driver = {
.probe = spear1310_miphy_probe,
.driver = {
.name = "spear1310-miphy",
.of_match_table = spear1310_miphy_of_match,
},
};
module_platform_driver(spear1310_miphy_driver);
MODULE_DESCRIPTION("ST SPEAR1310-MIPHY driver");
MODULE_AUTHOR("Pratyush Anand <pratyush.anand@gmail.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ST spear1340-miphy driver
*
* Copyright (C) 2014 ST Microelectronics
* Pratyush Anand <pratyush.anand@gmail.com>
* Mohit Kumar <mohit.kumar.dhaka@gmail.com>
*/
#include <linux/bitops.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/kernel.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/regmap.h>
/* SPEAr1340 Registers */
/* Power Management Registers */
#define SPEAR1340_PCM_CFG 0x100
#define SPEAR1340_PCM_CFG_SATA_POWER_EN BIT(11)
#define SPEAR1340_PCM_WKUP_CFG 0x104
#define SPEAR1340_SWITCH_CTR 0x108
#define SPEAR1340_PERIP1_SW_RST 0x318
#define SPEAR1340_PERIP1_SW_RSATA BIT(12)
#define SPEAR1340_PERIP2_SW_RST 0x31C
#define SPEAR1340_PERIP3_SW_RST 0x320
/* PCIE - SATA configuration registers */
#define SPEAR1340_PCIE_SATA_CFG 0x424
/* PCIE CFG MASks */
#define SPEAR1340_PCIE_CFG_DEVICE_PRESENT BIT(11)
#define SPEAR1340_PCIE_CFG_POWERUP_RESET BIT(10)
#define SPEAR1340_PCIE_CFG_CORE_CLK_EN BIT(9)
#define SPEAR1340_PCIE_CFG_AUX_CLK_EN BIT(8)
#define SPEAR1340_SATA_CFG_TX_CLK_EN BIT(4)
#define SPEAR1340_SATA_CFG_RX_CLK_EN BIT(3)
#define SPEAR1340_SATA_CFG_POWERUP_RESET BIT(2)
#define SPEAR1340_SATA_CFG_PM_CLK_EN BIT(1)
#define SPEAR1340_PCIE_SATA_SEL_PCIE (0)
#define SPEAR1340_PCIE_SATA_SEL_SATA (1)
#define SPEAR1340_PCIE_SATA_CFG_MASK 0xF1F
#define SPEAR1340_PCIE_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_PCIE | \
SPEAR1340_PCIE_CFG_AUX_CLK_EN | \
SPEAR1340_PCIE_CFG_CORE_CLK_EN | \
SPEAR1340_PCIE_CFG_POWERUP_RESET | \
SPEAR1340_PCIE_CFG_DEVICE_PRESENT)
#define SPEAR1340_SATA_CFG_VAL (SPEAR1340_PCIE_SATA_SEL_SATA | \
SPEAR1340_SATA_CFG_PM_CLK_EN | \
SPEAR1340_SATA_CFG_POWERUP_RESET | \
SPEAR1340_SATA_CFG_RX_CLK_EN | \
SPEAR1340_SATA_CFG_TX_CLK_EN)
#define SPEAR1340_PCIE_MIPHY_CFG 0x428
#define SPEAR1340_MIPHY_OSC_BYPASS_EXT BIT(31)
#define SPEAR1340_MIPHY_CLK_REF_DIV2 BIT(27)
#define SPEAR1340_MIPHY_CLK_REF_DIV4 (2 << 27)
#define SPEAR1340_MIPHY_CLK_REF_DIV8 (3 << 27)
#define SPEAR1340_MIPHY_PLL_RATIO_TOP(x) (x << 0)
#define SPEAR1340_PCIE_MIPHY_CFG_MASK 0xF80000FF
#define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA \
(SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
SPEAR1340_MIPHY_CLK_REF_DIV2 | \
SPEAR1340_MIPHY_PLL_RATIO_TOP(60))
#define SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK \
(SPEAR1340_MIPHY_PLL_RATIO_TOP(120))
#define SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE \
(SPEAR1340_MIPHY_OSC_BYPASS_EXT | \
SPEAR1340_MIPHY_PLL_RATIO_TOP(25))
enum spear1340_miphy_mode {
SATA,
PCIE,
};
struct spear1340_miphy_priv {
/* phy mode: 0 for SATA 1 for PCIe */
enum spear1340_miphy_mode mode;
/* regmap for any soc specific misc registers */
struct regmap *misc;
/* phy struct pointer */
struct phy *phy;
};
static int spear1340_miphy_sata_init(struct spear1340_miphy_priv *priv)
{
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
SPEAR1340_PCIE_SATA_CFG_MASK,
SPEAR1340_SATA_CFG_VAL);
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
SPEAR1340_PCIE_MIPHY_CFG_MASK,
SPEAR1340_PCIE_SATA_MIPHY_CFG_SATA_25M_CRYSTAL_CLK);
/* Switch on sata power domain */
regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
SPEAR1340_PCM_CFG_SATA_POWER_EN,
SPEAR1340_PCM_CFG_SATA_POWER_EN);
/* Wait for SATA power domain on */
msleep(20);
/* Disable PCIE SATA Controller reset */
regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
SPEAR1340_PERIP1_SW_RSATA, 0);
/* Wait for SATA reset de-assert completion */
msleep(20);
return 0;
}
static int spear1340_miphy_sata_exit(struct spear1340_miphy_priv *priv)
{
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
SPEAR1340_PCIE_SATA_CFG_MASK, 0);
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
/* Enable PCIE SATA Controller reset */
regmap_update_bits(priv->misc, SPEAR1340_PERIP1_SW_RST,
SPEAR1340_PERIP1_SW_RSATA,
SPEAR1340_PERIP1_SW_RSATA);
/* Wait for SATA power domain off */
msleep(20);
/* Switch off sata power domain */
regmap_update_bits(priv->misc, SPEAR1340_PCM_CFG,
SPEAR1340_PCM_CFG_SATA_POWER_EN, 0);
/* Wait for SATA reset assert completion */
msleep(20);
return 0;
}
static int spear1340_miphy_pcie_init(struct spear1340_miphy_priv *priv)
{
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
SPEAR1340_PCIE_MIPHY_CFG_MASK,
SPEAR1340_PCIE_SATA_MIPHY_CFG_PCIE);
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
SPEAR1340_PCIE_SATA_CFG_MASK,
SPEAR1340_PCIE_CFG_VAL);
return 0;
}
static int spear1340_miphy_pcie_exit(struct spear1340_miphy_priv *priv)
{
regmap_update_bits(priv->misc, SPEAR1340_PCIE_MIPHY_CFG,
SPEAR1340_PCIE_MIPHY_CFG_MASK, 0);
regmap_update_bits(priv->misc, SPEAR1340_PCIE_SATA_CFG,
SPEAR1340_PCIE_SATA_CFG_MASK, 0);
return 0;
}
static int spear1340_miphy_init(struct phy *phy)
{
struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
int ret = 0;
if (priv->mode == SATA)
ret = spear1340_miphy_sata_init(priv);
else if (priv->mode == PCIE)
ret = spear1340_miphy_pcie_init(priv);
return ret;
}
static int spear1340_miphy_exit(struct phy *phy)
{
struct spear1340_miphy_priv *priv = phy_get_drvdata(phy);
int ret = 0;
if (priv->mode == SATA)
ret = spear1340_miphy_sata_exit(priv);
else if (priv->mode == PCIE)
ret = spear1340_miphy_pcie_exit(priv);
return ret;
}
static const struct of_device_id spear1340_miphy_of_match[] = {
{ .compatible = "st,spear1340-miphy" },
{ },
};
MODULE_DEVICE_TABLE(of, spear1340_miphy_of_match);
static const struct phy_ops spear1340_miphy_ops = {
.init = spear1340_miphy_init,
.exit = spear1340_miphy_exit,
.owner = THIS_MODULE,
};
#ifdef CONFIG_PM_SLEEP
static int spear1340_miphy_suspend(struct device *dev)
{
struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
int ret = 0;
if (priv->mode == SATA)
ret = spear1340_miphy_sata_exit(priv);
return ret;
}
static int spear1340_miphy_resume(struct device *dev)
{
struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
int ret = 0;
if (priv->mode == SATA)
ret = spear1340_miphy_sata_init(priv);
return ret;
}
#endif
static SIMPLE_DEV_PM_OPS(spear1340_miphy_pm_ops, spear1340_miphy_suspend,
spear1340_miphy_resume);
static struct phy *spear1340_miphy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct spear1340_miphy_priv *priv = dev_get_drvdata(dev);
if (args->args_count < 1) {
dev_err(dev, "DT did not pass correct no of args\n");
return ERR_PTR(-ENODEV);
}
priv->mode = args->args[0];
if (priv->mode != SATA && priv->mode != PCIE) {
dev_err(dev, "DT did not pass correct phy mode\n");
return ERR_PTR(-ENODEV);
}
return priv->phy;
}
static int spear1340_miphy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct spear1340_miphy_priv *priv;
struct phy_provider *phy_provider;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->misc =
syscon_regmap_lookup_by_phandle(dev->of_node, "misc");
if (IS_ERR(priv->misc)) {
dev_err(dev, "failed to find misc regmap\n");
return PTR_ERR(priv->misc);
}
priv->phy = devm_phy_create(dev, NULL, &spear1340_miphy_ops);
if (IS_ERR(priv->phy)) {
dev_err(dev, "failed to create SATA PCIe PHY\n");
return PTR_ERR(priv->phy);
}
dev_set_drvdata(dev, priv);
phy_set_drvdata(priv->phy, priv);
phy_provider =
devm_of_phy_provider_register(dev, spear1340_miphy_xlate);
if (IS_ERR(phy_provider)) {
dev_err(dev, "failed to register phy provider\n");
return PTR_ERR(phy_provider);
}
return 0;
}
static struct platform_driver spear1340_miphy_driver = {
.probe = spear1340_miphy_probe,
.driver = {
.name = "spear1340-miphy",
.pm = &spear1340_miphy_pm_ops,
.of_match_table = spear1340_miphy_of_match,
},
};
module_platform_driver(spear1340_miphy_driver);
MODULE_DESCRIPTION("ST SPEAR1340-MIPHY driver");
MODULE_AUTHOR("Pratyush Anand <pratyush.anand@gmail.com>");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,164 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2014 STMicroelectronics
*
* STMicroelectronics Generic PHY driver for STiH407 USB2.
*
* Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
*/
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/clk.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <linux/mfd/syscon.h>
#include <linux/phy/phy.h>
#define PHYPARAM_REG 0
#define PHYCTRL_REG 1
/* Default PHY_SEL and REFCLKSEL configuration */
#define STIH407_USB_PICOPHY_CTRL_PORT_CONF 0x6
#define STIH407_USB_PICOPHY_CTRL_PORT_MASK 0x1f
/* ports parameters overriding */
#define STIH407_USB_PICOPHY_PARAM_DEF 0x39a4dc
#define STIH407_USB_PICOPHY_PARAM_MASK 0xffffffff
struct stih407_usb2_picophy {
struct phy *phy;
struct regmap *regmap;
struct device *dev;
struct reset_control *rstc;
struct reset_control *rstport;
int ctrl;
int param;
};
static int stih407_usb2_pico_ctrl(struct stih407_usb2_picophy *phy_dev)
{
reset_control_deassert(phy_dev->rstc);
return regmap_update_bits(phy_dev->regmap, phy_dev->ctrl,
STIH407_USB_PICOPHY_CTRL_PORT_MASK,
STIH407_USB_PICOPHY_CTRL_PORT_CONF);
}
static int stih407_usb2_init_port(struct phy *phy)
{
int ret;
struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
stih407_usb2_pico_ctrl(phy_dev);
ret = regmap_update_bits(phy_dev->regmap,
phy_dev->param,
STIH407_USB_PICOPHY_PARAM_MASK,
STIH407_USB_PICOPHY_PARAM_DEF);
if (ret)
return ret;
return reset_control_deassert(phy_dev->rstport);
}
static int stih407_usb2_exit_port(struct phy *phy)
{
struct stih407_usb2_picophy *phy_dev = phy_get_drvdata(phy);
/*
* Only port reset is asserted, phy global reset is kept untouched
* as other ports may still be active. When all ports are in reset
* state, assumption is made that power will be cut off on the phy, in
* case of suspend for instance. Theoretically, asserting individual
* reset (like here) or global reset should be equivalent.
*/
return reset_control_assert(phy_dev->rstport);
}
static const struct phy_ops stih407_usb2_picophy_data = {
.init = stih407_usb2_init_port,
.exit = stih407_usb2_exit_port,
.owner = THIS_MODULE,
};
static int stih407_usb2_picophy_probe(struct platform_device *pdev)
{
struct stih407_usb2_picophy *phy_dev;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct phy_provider *phy_provider;
unsigned int syscon_args[2];
struct phy *phy;
phy_dev = devm_kzalloc(dev, sizeof(*phy_dev), GFP_KERNEL);
if (!phy_dev)
return -ENOMEM;
phy_dev->dev = dev;
dev_set_drvdata(dev, phy_dev);
phy_dev->rstc = devm_reset_control_get_shared(dev, "global");
if (IS_ERR(phy_dev->rstc)) {
dev_err(dev, "failed to ctrl picoPHY reset\n");
return PTR_ERR(phy_dev->rstc);
}
phy_dev->rstport = devm_reset_control_get_exclusive(dev, "port");
if (IS_ERR(phy_dev->rstport)) {
dev_err(dev, "failed to ctrl picoPHY reset\n");
return PTR_ERR(phy_dev->rstport);
}
/* Reset port by default: only deassert it in phy init */
reset_control_assert(phy_dev->rstport);
phy_dev->regmap = syscon_regmap_lookup_by_phandle_args(np, "st,syscfg",
2, syscon_args);
if (IS_ERR(phy_dev->regmap)) {
dev_err(dev, "No syscfg phandle specified\n");
return PTR_ERR(phy_dev->regmap);
}
phy_dev->param = syscon_args[PHYPARAM_REG];
phy_dev->ctrl = syscon_args[PHYCTRL_REG];
phy = devm_phy_create(dev, NULL, &stih407_usb2_picophy_data);
if (IS_ERR(phy)) {
dev_err(dev, "failed to create Display Port PHY\n");
return PTR_ERR(phy);
}
phy_dev->phy = phy;
phy_set_drvdata(phy, phy_dev);
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
if (IS_ERR(phy_provider))
return PTR_ERR(phy_provider);
return 0;
}
static const struct of_device_id stih407_usb2_picophy_of_match[] = {
{ .compatible = "st,stih407-usb2-phy" },
{ /*sentinel */ },
};
MODULE_DEVICE_TABLE(of, stih407_usb2_picophy_of_match);
static struct platform_driver stih407_usb2_picophy_driver = {
.probe = stih407_usb2_picophy_probe,
.driver = {
.name = "stih407-usb-genphy",
.of_match_table = stih407_usb2_picophy_of_match,
}
};
module_platform_driver(stih407_usb2_picophy_driver);
MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>");
MODULE_DESCRIPTION("STMicroelectronics Generic picoPHY driver for STiH407");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* STMicroelectronics COMBOPHY STM32MP25 Controller driver.
*
* Copyright (C) 2024 STMicroelectronics
* Author: Christian Bruel <christian.bruel@foss.st.com>
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <dt-bindings/phy/phy.h>
#define SYSCFG_COMBOPHY_CR1 0x4c00
#define SYSCFG_COMBOPHY_CR2 0x4c04
#define SYSCFG_COMBOPHY_CR4 0x4c0c
#define SYSCFG_COMBOPHY_CR5 0x4c10
#define SYSCFG_COMBOPHY_SR 0x4c14
#define SYSCFG_PCIEPRGCR 0x6080
/* SYSCFG PCIEPRGCR */
#define STM32MP25_PCIEPRGCR_EN BIT(0)
#define STM32MP25_PCIEPRG_IMPCTRL_OHM GENMASK(3, 1)
#define STM32MP25_PCIEPRG_IMPCTRL_VSWING GENMASK(5, 4)
/* SYSCFG SYSCFG_COMBOPHY_SR */
#define STM32MP25_PIPE0_PHYSTATUS BIT(1)
/* SYSCFG CR1 */
#define SYSCFG_COMBOPHY_CR1_REFUSEPAD BIT(0)
#define SYSCFG_COMBOPHY_CR1_MPLLMULT GENMASK(7, 1)
#define SYSCFG_COMBOPHY_CR1_REFCLKSEL GENMASK(16, 8)
#define SYSCFG_COMBOPHY_CR1_REFCLKDIV2 BIT(17)
#define SYSCFG_COMBOPHY_CR1_REFSSPEN BIT(18)
#define SYSCFG_COMBOPHY_CR1_SSCEN BIT(19)
/* SYSCFG CR4 */
#define SYSCFG_COMBOPHY_CR4_RX0_EQ GENMASK(2, 0)
#define MPLLMULT_19_2 (0x02u << 1)
#define MPLLMULT_20 (0x7du << 1)
#define MPLLMULT_24 (0x68u << 1)
#define MPLLMULT_25 (0x64u << 1)
#define MPLLMULT_26 (0x60u << 1)
#define MPLLMULT_38_4 (0x41u << 1)
#define MPLLMULT_48 (0x6cu << 1)
#define MPLLMULT_50 (0x32u << 1)
#define MPLLMULT_52 (0x30u << 1)
#define MPLLMULT_100 (0x19u << 1)
#define REFCLKSEL_0 0
#define REFCLKSEL_1 (0x108u << 8)
#define REFCLDIV_0 0
/* SYSCFG CR2 */
#define SYSCFG_COMBOPHY_CR2_MODESEL GENMASK(1, 0)
#define SYSCFG_COMBOPHY_CR2_ISO_DIS BIT(15)
#define COMBOPHY_MODESEL_PCIE 0
#define COMBOPHY_MODESEL_USB 3
/* SYSCFG CR5 */
#define SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS BIT(12)
#define COMBOPHY_SUP_ANA_MPLL_LOOP_CTL 0xc0
#define COMBOPHY_PROP_CNTRL GENMASK(7, 4)
/* Required apb/ker clocks first, optional pad last. */
static const char * const combophy_clks[] = {"apb", "ker", "pad"};
#define APB_CLK 0
#define KER_CLK 1
#define PAD_CLK 2
struct stm32_combophy {
struct phy *phy;
struct regmap *regmap;
struct device *dev;
void __iomem *base;
struct reset_control *phy_reset;
struct clk_bulk_data clks[ARRAY_SIZE(combophy_clks)];
int num_clks;
bool have_pad_clk;
unsigned int type;
bool is_init;
int irq_wakeup;
};
struct clk_impedance {
u32 microohm;
u32 vswing[4];
};
/*
* lookup table to hold the settings needed for a ref clock frequency
* impedance, the offset is used to set the IMP_CTL and DE_EMP bit of the
* PRG_IMP_CTRL register. Use ordered discrete values in the table
*/
static const struct clk_impedance imp_lookup[] = {
{ 6090000, { 442000, 564000, 684000, 802000 } },
{ 5662000, { 528000, 621000, 712000, 803000 } },
{ 5292000, { 491000, 596000, 700000, 802000 } },
{ 4968000, { 558000, 640000, 722000, 803000 } },
{ 4684000, { 468000, 581000, 692000, 802000 } },
{ 4429000, { 554000, 613000, 717000, 803000 } },
{ 4204000, { 511000, 609000, 706000, 802000 } },
{ 3999000, { 571000, 648000, 726000, 803000 } }
};
#define DEFAULT_IMP_INDEX 3 /* Default impedance is 50 Ohm */
static int stm32_impedance_tune(struct stm32_combophy *combophy)
{
u8 imp_size = ARRAY_SIZE(imp_lookup);
u8 vswing_size = ARRAY_SIZE(imp_lookup[0].vswing);
u8 imp_of, vswing_of;
u32 max_imp = imp_lookup[0].microohm;
u32 min_imp = imp_lookup[imp_size - 1].microohm;
u32 max_vswing;
u32 min_vswing = imp_lookup[0].vswing[0];
u32 val;
if (!of_property_read_u32(combophy->dev->of_node, "st,output-micro-ohms", &val)) {
if (val < min_imp || val > max_imp) {
dev_err(combophy->dev, "Invalid value %u for output ohm\n", val);
return -EINVAL;
}
for (imp_of = 0; imp_of < ARRAY_SIZE(imp_lookup); imp_of++)
if (imp_lookup[imp_of].microohm <= val)
break;
if (WARN_ON(imp_of == ARRAY_SIZE(imp_lookup)))
return -EINVAL;
dev_dbg(combophy->dev, "Set %u micro-ohms output impedance\n",
imp_lookup[imp_of].microohm);
regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR,
STM32MP25_PCIEPRG_IMPCTRL_OHM,
FIELD_PREP(STM32MP25_PCIEPRG_IMPCTRL_OHM, imp_of));
} else
imp_of = DEFAULT_IMP_INDEX;
if (!of_property_read_u32(combophy->dev->of_node, "st,output-vswing-microvolt", &val)) {
max_vswing = imp_lookup[imp_of].vswing[vswing_size - 1];
if (val < min_vswing || val > max_vswing) {
dev_err(combophy->dev, "Invalid value %u for output vswing\n", val);
return -EINVAL;
}
for (vswing_of = 0; vswing_of < ARRAY_SIZE(imp_lookup[imp_of].vswing); vswing_of++)
if (imp_lookup[imp_of].vswing[vswing_of] >= val)
break;
if (WARN_ON(vswing_of == ARRAY_SIZE(imp_lookup[imp_of].vswing)))
return -EINVAL;
dev_dbg(combophy->dev, "Set %u microvolt swing\n",
imp_lookup[imp_of].vswing[vswing_of]);
regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR,
STM32MP25_PCIEPRG_IMPCTRL_VSWING,
FIELD_PREP(STM32MP25_PCIEPRG_IMPCTRL_VSWING, vswing_of));
}
return 0;
}
static int stm32_combophy_pll_init(struct stm32_combophy *combophy)
{
int ret;
u32 refclksel, pllmult, propcntrl, val;
u32 clk_rate;
struct clk *clk;
u32 cr1_val = 0, cr1_mask = 0;
if (combophy->have_pad_clk)
clk = combophy->clks[PAD_CLK].clk;
else
clk = combophy->clks[KER_CLK].clk;
clk_rate = clk_get_rate(clk);
dev_dbg(combophy->dev, "%s pll init rate %d\n",
combophy->have_pad_clk ? "External" : "Ker", clk_rate);
if (combophy->type != PHY_TYPE_PCIE) {
cr1_mask |= SYSCFG_COMBOPHY_CR1_REFSSPEN;
cr1_val |= SYSCFG_COMBOPHY_CR1_REFSSPEN;
}
if (of_property_present(combophy->dev->of_node, "st,ssc-on")) {
dev_dbg(combophy->dev, "Enabling clock with SSC\n");
cr1_mask |= SYSCFG_COMBOPHY_CR1_SSCEN;
cr1_val |= SYSCFG_COMBOPHY_CR1_SSCEN;
}
switch (clk_rate) {
case 100000000:
pllmult = MPLLMULT_100;
refclksel = REFCLKSEL_0;
propcntrl = 0x8u << 4;
break;
case 19200000:
pllmult = MPLLMULT_19_2;
refclksel = REFCLKSEL_1;
propcntrl = 0x8u << 4;
break;
case 25000000:
pllmult = MPLLMULT_25;
refclksel = REFCLKSEL_0;
propcntrl = 0xeu << 4;
break;
case 24000000:
pllmult = MPLLMULT_24;
refclksel = REFCLKSEL_1;
propcntrl = 0xeu << 4;
break;
case 20000000:
pllmult = MPLLMULT_20;
refclksel = REFCLKSEL_0;
propcntrl = 0xeu << 4;
break;
default:
dev_err(combophy->dev, "Invalid rate 0x%x\n", clk_rate);
return -EINVAL;
}
cr1_mask |= SYSCFG_COMBOPHY_CR1_REFCLKDIV2;
cr1_val |= REFCLDIV_0;
cr1_mask |= SYSCFG_COMBOPHY_CR1_REFCLKSEL;
cr1_val |= refclksel;
cr1_mask |= SYSCFG_COMBOPHY_CR1_MPLLMULT;
cr1_val |= pllmult;
/*
* vddcombophy is interconnected with vddcore. Isolation bit should be unset
* before using the ComboPHY.
*/
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2,
SYSCFG_COMBOPHY_CR2_ISO_DIS, SYSCFG_COMBOPHY_CR2_ISO_DIS);
reset_control_assert(combophy->phy_reset);
if (combophy->type == PHY_TYPE_PCIE) {
ret = stm32_impedance_tune(combophy);
if (ret)
goto out_iso;
cr1_mask |= SYSCFG_COMBOPHY_CR1_REFUSEPAD;
cr1_val |= combophy->have_pad_clk ? SYSCFG_COMBOPHY_CR1_REFUSEPAD : 0;
}
if (!of_property_read_u32(combophy->dev->of_node, "st,rx-equalizer", &val)) {
dev_dbg(combophy->dev, "Set RX equalizer %u\n", val);
if (val > SYSCFG_COMBOPHY_CR4_RX0_EQ) {
dev_err(combophy->dev, "Invalid value %u for rx0 equalizer\n", val);
ret = -EINVAL;
goto out_iso;
}
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR4,
SYSCFG_COMBOPHY_CR4_RX0_EQ, val);
}
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1, cr1_mask, cr1_val);
/*
* Force elasticity buffer to be tuned for the reference clock as
* the separated clock model is not supported
*/
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR5,
SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS, SYSCFG_COMBOPHY_CR5_COMMON_CLOCKS);
reset_control_deassert(combophy->phy_reset);
ret = regmap_read_poll_timeout(combophy->regmap, SYSCFG_COMBOPHY_SR, val,
!(val & STM32MP25_PIPE0_PHYSTATUS),
10, 1000);
if (ret) {
dev_err(combophy->dev, "timeout, cannot lock PLL\n");
if (combophy->type == PHY_TYPE_PCIE && !combophy->have_pad_clk)
regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR,
STM32MP25_PCIEPRGCR_EN, 0);
if (combophy->type != PHY_TYPE_PCIE)
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1,
SYSCFG_COMBOPHY_CR1_REFSSPEN, 0);
goto out;
}
if (combophy->type == PHY_TYPE_PCIE) {
if (!combophy->have_pad_clk)
regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR,
STM32MP25_PCIEPRGCR_EN, STM32MP25_PCIEPRGCR_EN);
val = readl_relaxed(combophy->base + COMBOPHY_SUP_ANA_MPLL_LOOP_CTL);
val &= ~COMBOPHY_PROP_CNTRL;
val |= propcntrl;
writel_relaxed(val, combophy->base + COMBOPHY_SUP_ANA_MPLL_LOOP_CTL);
}
return 0;
out_iso:
reset_control_deassert(combophy->phy_reset);
out:
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2,
SYSCFG_COMBOPHY_CR2_ISO_DIS, 0);
return ret;
}
static struct phy *stm32_combophy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct stm32_combophy *combophy = dev_get_drvdata(dev);
unsigned int type;
if (args->args_count != 1) {
dev_err(dev, "invalid number of cells in 'phy' property\n");
return ERR_PTR(-EINVAL);
}
type = args->args[0];
if (type != PHY_TYPE_USB3 && type != PHY_TYPE_PCIE) {
dev_err(dev, "unsupported device type: %d\n", type);
return ERR_PTR(-EINVAL);
}
if (combophy->have_pad_clk && type != PHY_TYPE_PCIE) {
dev_err(dev, "Invalid use of clk_pad for USB3 mode\n");
return ERR_PTR(-EINVAL);
}
combophy->type = type;
return combophy->phy;
}
static int stm32_combophy_set_mode(struct stm32_combophy *combophy)
{
int type = combophy->type;
u32 val;
switch (type) {
case PHY_TYPE_PCIE:
dev_dbg(combophy->dev, "setting PCIe ComboPHY\n");
val = COMBOPHY_MODESEL_PCIE;
break;
case PHY_TYPE_USB3:
dev_dbg(combophy->dev, "setting USB3 ComboPHY\n");
val = COMBOPHY_MODESEL_USB;
break;
default:
dev_err(combophy->dev, "Invalid PHY mode %d\n", type);
return -EINVAL;
}
return regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2,
SYSCFG_COMBOPHY_CR2_MODESEL, val);
}
static int stm32_combophy_suspend_noirq(struct device *dev)
{
struct stm32_combophy *combophy = dev_get_drvdata(dev);
/*
* Clocks should be turned off since it is not needed for
* wakeup capability. In case usb-remote wakeup is not enabled,
* combo-phy is already turned off by HCD driver using exit callback
*/
if (combophy->is_init) {
clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks);
/* since wakeup is enabled for ctrl */
enable_irq_wake(combophy->irq_wakeup);
}
return 0;
}
static int stm32_combophy_resume_noirq(struct device *dev)
{
struct stm32_combophy *combophy = dev_get_drvdata(dev);
int ret;
/*
* If clocks was turned off by suspend call for wakeup then needs
* to be turned back ON in resume. In case usb-remote wakeup is not
* enabled, clocks already turned ON by HCD driver using init callback
*/
if (combophy->is_init) {
/* since wakeup was enabled for ctrl */
disable_irq_wake(combophy->irq_wakeup);
ret = clk_bulk_prepare_enable(combophy->num_clks, combophy->clks);
if (ret) {
dev_err(dev, "can't enable clocks (%d)\n", ret);
return ret;
}
}
return 0;
}
static int stm32_combophy_exit(struct phy *phy)
{
struct stm32_combophy *combophy = phy_get_drvdata(phy);
struct device *dev = combophy->dev;
combophy->is_init = false;
if (combophy->type == PHY_TYPE_PCIE && !combophy->have_pad_clk)
regmap_update_bits(combophy->regmap, SYSCFG_PCIEPRGCR,
STM32MP25_PCIEPRGCR_EN, 0);
if (combophy->type != PHY_TYPE_PCIE)
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR1,
SYSCFG_COMBOPHY_CR1_REFSSPEN, 0);
regmap_update_bits(combophy->regmap, SYSCFG_COMBOPHY_CR2,
SYSCFG_COMBOPHY_CR2_ISO_DIS, 0);
clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks);
pm_runtime_put_noidle(dev);
return 0;
}
static int stm32_combophy_init(struct phy *phy)
{
struct stm32_combophy *combophy = phy_get_drvdata(phy);
struct device *dev = combophy->dev;
int ret;
pm_runtime_get_noresume(dev);
ret = clk_bulk_prepare_enable(combophy->num_clks, combophy->clks);
if (ret) {
dev_err(dev, "can't enable clocks (%d)\n", ret);
pm_runtime_put_noidle(dev);
return ret;
}
ret = stm32_combophy_set_mode(combophy);
if (ret) {
dev_err(dev, "combophy mode not set\n");
clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks);
pm_runtime_put_noidle(dev);
return ret;
}
ret = stm32_combophy_pll_init(combophy);
if (ret) {
clk_bulk_disable_unprepare(combophy->num_clks, combophy->clks);
pm_runtime_put_noidle(dev);
return ret;
}
pm_runtime_disable(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
combophy->is_init = true;
return ret;
}
static const struct phy_ops stm32_combophy_phy_data = {
.init = stm32_combophy_init,
.exit = stm32_combophy_exit,
.owner = THIS_MODULE
};
static irqreturn_t stm32_combophy_irq_wakeup_handler(int irq, void *dev_id)
{
return IRQ_HANDLED;
}
static int stm32_combophy_get_clocks(struct stm32_combophy *combophy)
{
int i, ret;
for (i = 0; i < ARRAY_SIZE(combophy_clks); i++)
combophy->clks[i].id = combophy_clks[i];
combophy->num_clks = ARRAY_SIZE(combophy_clks) - 1;
ret = devm_clk_bulk_get(combophy->dev, combophy->num_clks, combophy->clks);
if (ret)
return ret;
ret = devm_clk_bulk_get_optional(combophy->dev, 1, combophy->clks + combophy->num_clks);
if (ret)
return ret;
if (combophy->clks[combophy->num_clks].clk != NULL) {
combophy->have_pad_clk = true;
combophy->num_clks++;
}
return 0;
}
static int stm32_combophy_probe(struct platform_device *pdev)
{
struct stm32_combophy *combophy;
struct device *dev = &pdev->dev;
struct phy_provider *phy_provider;
int ret, irq;
combophy = devm_kzalloc(dev, sizeof(*combophy), GFP_KERNEL);
if (!combophy)
return -ENOMEM;
combophy->dev = dev;
dev_set_drvdata(dev, combophy);
combophy->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(combophy->base))
return PTR_ERR(combophy->base);
ret = stm32_combophy_get_clocks(combophy);
if (ret)
return ret;
combophy->phy_reset = devm_reset_control_get_exclusive(dev, "phy");
if (IS_ERR(combophy->phy_reset))
return dev_err_probe(dev, PTR_ERR(combophy->phy_reset),
"Failed to get PHY reset\n");
combophy->regmap = syscon_regmap_lookup_by_compatible("st,stm32mp25-syscfg");
if (IS_ERR(combophy->regmap))
return dev_err_probe(dev, PTR_ERR(combophy->regmap),
"No syscfg specified\n");
combophy->phy = devm_phy_create(dev, NULL, &stm32_combophy_phy_data);
if (IS_ERR(combophy->phy))
return dev_err_probe(dev, PTR_ERR(combophy->phy),
"failed to create PCIe/USB3 ComboPHY\n");
if (device_property_read_bool(dev, "wakeup-source")) {
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return dev_err_probe(dev, irq, "failed to get IRQ\n");
combophy->irq_wakeup = irq;
ret = devm_request_threaded_irq(dev, combophy->irq_wakeup, NULL,
stm32_combophy_irq_wakeup_handler, IRQF_ONESHOT,
NULL, NULL);
if (ret)
return dev_err_probe(dev, ret, "unable to request wake IRQ %d\n",
combophy->irq_wakeup);
}
ret = devm_pm_runtime_enable(dev);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable pm runtime\n");
phy_set_drvdata(combophy->phy, combophy);
phy_provider = devm_of_phy_provider_register(dev, stm32_combophy_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static const struct dev_pm_ops stm32_combophy_pm_ops = {
NOIRQ_SYSTEM_SLEEP_PM_OPS(stm32_combophy_suspend_noirq,
stm32_combophy_resume_noirq)
};
static const struct of_device_id stm32_combophy_of_match[] = {
{ .compatible = "st,stm32mp25-combophy", },
{ },
};
MODULE_DEVICE_TABLE(of, stm32_combophy_of_match);
static struct platform_driver stm32_combophy_driver = {
.probe = stm32_combophy_probe,
.driver = {
.name = "stm32-combophy",
.of_match_table = stm32_combophy_of_match,
.pm = pm_sleep_ptr(&stm32_combophy_pm_ops)
}
};
module_platform_driver(stm32_combophy_driver);
MODULE_AUTHOR("Christian Bruel <christian.bruel@foss.st.com>");
MODULE_DESCRIPTION("STM32MP25 Combophy USB3/PCIe controller driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,826 @@
// SPDX-License-Identifier: GPL-2.0
/*
* STMicroelectronics STM32 USB PHY Controller driver
*
* Copyright (C) 2018 STMicroelectronics
* Author(s): Amelie Delaunay <amelie.delaunay@st.com>.
*/
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/phy/phy.h>
#include <linux/platform_device.h>
#include <linux/reset.h>
#include <linux/units.h>
#define STM32_USBPHYC_PLL 0x0
#define STM32_USBPHYC_MISC 0x8
#define STM32_USBPHYC_MONITOR(X) (0x108 + ((X) * 0x100))
#define STM32_USBPHYC_TUNE(X) (0x10C + ((X) * 0x100))
#define STM32_USBPHYC_VERSION 0x3F4
/* STM32_USBPHYC_PLL bit fields */
#define PLLNDIV GENMASK(6, 0)
#define PLLFRACIN GENMASK(25, 10)
#define PLLEN BIT(26)
#define PLLSTRB BIT(27)
#define PLLSTRBYP BIT(28)
#define PLLFRACCTL BIT(29)
#define PLLDITHEN0 BIT(30)
#define PLLDITHEN1 BIT(31)
/* STM32_USBPHYC_MISC bit fields */
#define SWITHOST BIT(0)
/* STM32_USBPHYC_MONITOR bit fields */
#define STM32_USBPHYC_MON_OUT GENMASK(3, 0)
#define STM32_USBPHYC_MON_SEL GENMASK(8, 4)
#define STM32_USBPHYC_MON_SEL_LOCKP 0x1F
#define STM32_USBPHYC_MON_OUT_LOCKP BIT(3)
/* STM32_USBPHYC_TUNE bit fields */
#define INCURREN BIT(0)
#define INCURRINT BIT(1)
#define LFSCAPEN BIT(2)
#define HSDRVSLEW BIT(3)
#define HSDRVDCCUR BIT(4)
#define HSDRVDCLEV BIT(5)
#define HSDRVCURINCR BIT(6)
#define FSDRVRFADJ BIT(7)
#define HSDRVRFRED BIT(8)
#define HSDRVCHKITRM GENMASK(12, 9)
#define HSDRVCHKZTRM GENMASK(14, 13)
#define OTPCOMP GENMASK(19, 15)
#define SQLCHCTL GENMASK(21, 20)
#define HDRXGNEQEN BIT(22)
#define HSRXOFF GENMASK(24, 23)
#define HSFALLPREEM BIT(25)
#define SHTCCTCTLPROT BIT(26)
#define STAGSEL BIT(27)
enum boosting_vals {
BOOST_1000_UA = 1000,
BOOST_2000_UA = 2000,
};
enum dc_level_vals {
DC_NOMINAL,
DC_PLUS_5_TO_7_MV,
DC_PLUS_10_TO_14_MV,
DC_MINUS_5_TO_7_MV,
DC_MAX,
};
enum current_trim {
CUR_NOMINAL,
CUR_PLUS_1_56_PCT,
CUR_PLUS_3_12_PCT,
CUR_PLUS_4_68_PCT,
CUR_PLUS_6_24_PCT,
CUR_PLUS_7_8_PCT,
CUR_PLUS_9_36_PCT,
CUR_PLUS_10_92_PCT,
CUR_PLUS_12_48_PCT,
CUR_PLUS_14_04_PCT,
CUR_PLUS_15_6_PCT,
CUR_PLUS_17_16_PCT,
CUR_PLUS_19_01_PCT,
CUR_PLUS_20_58_PCT,
CUR_PLUS_22_16_PCT,
CUR_PLUS_23_73_PCT,
CUR_MAX,
};
enum impedance_trim {
IMP_NOMINAL,
IMP_MINUS_2_OHMS,
IMP_MINUS_4_OMHS,
IMP_MINUS_6_OHMS,
IMP_MAX,
};
enum squelch_level {
SQLCH_NOMINAL,
SQLCH_PLUS_7_MV,
SQLCH_MINUS_5_MV,
SQLCH_PLUS_14_MV,
SQLCH_MAX,
};
enum rx_offset {
NO_RX_OFFSET,
RX_OFFSET_PLUS_5_MV,
RX_OFFSET_PLUS_10_MV,
RX_OFFSET_MINUS_5_MV,
RX_OFFSET_MAX,
};
/* STM32_USBPHYC_VERSION bit fields */
#define MINREV GENMASK(3, 0)
#define MAJREV GENMASK(7, 4)
#define PLL_FVCO_MHZ 2880
#define PLL_INFF_MIN_RATE_HZ 19200000
#define PLL_INFF_MAX_RATE_HZ 38400000
struct pll_params {
u8 ndiv;
u16 frac;
};
struct stm32_usbphyc_phy {
struct phy *phy;
struct stm32_usbphyc *usbphyc;
struct regulator *vbus;
u32 index;
bool active;
u32 tune;
};
struct stm32_usbphyc {
struct device *dev;
void __iomem *base;
struct clk *clk;
struct reset_control *rst;
struct stm32_usbphyc_phy **phys;
int nphys;
struct regulator *vdda1v1;
struct regulator *vdda1v8;
atomic_t n_pll_cons;
struct clk_hw clk48_hw;
int switch_setup;
};
static inline void stm32_usbphyc_set_bits(void __iomem *reg, u32 bits)
{
writel_relaxed(readl_relaxed(reg) | bits, reg);
}
static inline void stm32_usbphyc_clr_bits(void __iomem *reg, u32 bits)
{
writel_relaxed(readl_relaxed(reg) & ~bits, reg);
}
static int stm32_usbphyc_regulators_enable(struct stm32_usbphyc *usbphyc)
{
int ret;
ret = regulator_enable(usbphyc->vdda1v1);
if (ret)
return ret;
ret = regulator_enable(usbphyc->vdda1v8);
if (ret)
goto vdda1v1_disable;
return 0;
vdda1v1_disable:
regulator_disable(usbphyc->vdda1v1);
return ret;
}
static int stm32_usbphyc_regulators_disable(struct stm32_usbphyc *usbphyc)
{
int ret;
ret = regulator_disable(usbphyc->vdda1v8);
if (ret)
return ret;
ret = regulator_disable(usbphyc->vdda1v1);
if (ret)
return ret;
return 0;
}
static void stm32_usbphyc_get_pll_params(u32 clk_rate,
struct pll_params *pll_params)
{
unsigned long long fvco, ndiv, frac;
/* _
* | FVCO = INFF*2*(NDIV + FRACT/2^16) when DITHER_DISABLE[1] = 1
* | FVCO = 2880MHz
* <
* | NDIV = integer part of input bits to set the LDF
* |_FRACT = fractional part of input bits to set the LDF
* => PLLNDIV = integer part of (FVCO / (INFF*2))
* => PLLFRACIN = fractional part of(FVCO / INFF*2) * 2^16
* <=> PLLFRACIN = ((FVCO / (INFF*2)) - PLLNDIV) * 2^16
*/
fvco = (unsigned long long)PLL_FVCO_MHZ * HZ_PER_MHZ;
ndiv = fvco;
do_div(ndiv, (clk_rate * 2));
pll_params->ndiv = (u8)ndiv;
frac = fvco * (1 << 16);
do_div(frac, (clk_rate * 2));
frac = frac - (ndiv * (1 << 16));
pll_params->frac = (u16)frac;
}
static int stm32_usbphyc_pll_init(struct stm32_usbphyc *usbphyc)
{
struct pll_params pll_params;
u32 clk_rate = clk_get_rate(usbphyc->clk);
u32 ndiv, frac;
u32 usbphyc_pll;
if ((clk_rate < PLL_INFF_MIN_RATE_HZ) ||
(clk_rate > PLL_INFF_MAX_RATE_HZ)) {
dev_err(usbphyc->dev, "input clk freq (%dHz) out of range\n",
clk_rate);
return -EINVAL;
}
stm32_usbphyc_get_pll_params(clk_rate, &pll_params);
ndiv = FIELD_PREP(PLLNDIV, pll_params.ndiv);
frac = FIELD_PREP(PLLFRACIN, pll_params.frac);
usbphyc_pll = PLLDITHEN1 | PLLDITHEN0 | PLLSTRBYP | ndiv;
if (pll_params.frac)
usbphyc_pll |= PLLFRACCTL | frac;
writel_relaxed(usbphyc_pll, usbphyc->base + STM32_USBPHYC_PLL);
dev_dbg(usbphyc->dev, "input clk freq=%dHz, ndiv=%lu, frac=%lu\n",
clk_rate, FIELD_GET(PLLNDIV, usbphyc_pll),
FIELD_GET(PLLFRACIN, usbphyc_pll));
return 0;
}
static int __stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc)
{
void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL;
u32 pllen;
stm32_usbphyc_clr_bits(pll_reg, PLLEN);
/* Wait for minimum width of powerdown pulse (ENABLE = Low) */
if (readl_relaxed_poll_timeout(pll_reg, pllen, !(pllen & PLLEN), 5, 50))
dev_err(usbphyc->dev, "PLL not reset\n");
return stm32_usbphyc_regulators_disable(usbphyc);
}
static int stm32_usbphyc_pll_disable(struct stm32_usbphyc *usbphyc)
{
/* Check if a phy port is still active or clk48 in use */
if (atomic_dec_return(&usbphyc->n_pll_cons) > 0)
return 0;
return __stm32_usbphyc_pll_disable(usbphyc);
}
static int stm32_usbphyc_pll_enable(struct stm32_usbphyc *usbphyc)
{
void __iomem *pll_reg = usbphyc->base + STM32_USBPHYC_PLL;
bool pllen = readl_relaxed(pll_reg) & PLLEN;
int ret;
/*
* Check if a phy port or clk48 prepare has configured the pll
* and ensure the PLL is enabled
*/
if (atomic_inc_return(&usbphyc->n_pll_cons) > 1 && pllen)
return 0;
if (pllen) {
/*
* PLL shouldn't be enabled without known consumer,
* disable it and reinit n_pll_cons
*/
dev_warn(usbphyc->dev, "PLL enabled without known consumers\n");
ret = __stm32_usbphyc_pll_disable(usbphyc);
if (ret)
goto dec_n_pll_cons;
}
ret = stm32_usbphyc_regulators_enable(usbphyc);
if (ret)
goto dec_n_pll_cons;
ret = stm32_usbphyc_pll_init(usbphyc);
if (ret)
goto reg_disable;
stm32_usbphyc_set_bits(pll_reg, PLLEN);
/* Wait for maximum lock time */
usleep_range(200, 300);
return 0;
reg_disable:
stm32_usbphyc_regulators_disable(usbphyc);
dec_n_pll_cons:
atomic_dec(&usbphyc->n_pll_cons);
return ret;
}
static int stm32_usbphyc_phy_init(struct phy *phy)
{
struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc;
u32 reg_mon = STM32_USBPHYC_MONITOR(usbphyc_phy->index);
u32 monsel = FIELD_PREP(STM32_USBPHYC_MON_SEL,
STM32_USBPHYC_MON_SEL_LOCKP);
u32 monout;
int ret;
ret = stm32_usbphyc_pll_enable(usbphyc);
if (ret)
return ret;
/* Check that PLL Lock input to PHY is High */
writel_relaxed(monsel, usbphyc->base + reg_mon);
ret = readl_relaxed_poll_timeout(usbphyc->base + reg_mon, monout,
(monout & STM32_USBPHYC_MON_OUT_LOCKP),
100, 1000);
if (ret) {
dev_err(usbphyc->dev, "PLL Lock input to PHY is Low (val=%x)\n",
(u32)(monout & STM32_USBPHYC_MON_OUT));
goto pll_disable;
}
usbphyc_phy->active = true;
return 0;
pll_disable:
stm32_usbphyc_pll_disable(usbphyc);
return ret;
}
static int stm32_usbphyc_phy_exit(struct phy *phy)
{
struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
struct stm32_usbphyc *usbphyc = usbphyc_phy->usbphyc;
usbphyc_phy->active = false;
return stm32_usbphyc_pll_disable(usbphyc);
}
static int stm32_usbphyc_phy_power_on(struct phy *phy)
{
struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
if (usbphyc_phy->vbus)
return regulator_enable(usbphyc_phy->vbus);
return 0;
}
static int stm32_usbphyc_phy_power_off(struct phy *phy)
{
struct stm32_usbphyc_phy *usbphyc_phy = phy_get_drvdata(phy);
if (usbphyc_phy->vbus)
return regulator_disable(usbphyc_phy->vbus);
return 0;
}
static const struct phy_ops stm32_usbphyc_phy_ops = {
.init = stm32_usbphyc_phy_init,
.exit = stm32_usbphyc_phy_exit,
.power_on = stm32_usbphyc_phy_power_on,
.power_off = stm32_usbphyc_phy_power_off,
.owner = THIS_MODULE,
};
static int stm32_usbphyc_clk48_prepare(struct clk_hw *hw)
{
struct stm32_usbphyc *usbphyc = container_of(hw, struct stm32_usbphyc, clk48_hw);
return stm32_usbphyc_pll_enable(usbphyc);
}
static void stm32_usbphyc_clk48_unprepare(struct clk_hw *hw)
{
struct stm32_usbphyc *usbphyc = container_of(hw, struct stm32_usbphyc, clk48_hw);
stm32_usbphyc_pll_disable(usbphyc);
}
static unsigned long stm32_usbphyc_clk48_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
{
return 48000000;
}
static const struct clk_ops usbphyc_clk48_ops = {
.prepare = stm32_usbphyc_clk48_prepare,
.unprepare = stm32_usbphyc_clk48_unprepare,
.recalc_rate = stm32_usbphyc_clk48_recalc_rate,
};
static void stm32_usbphyc_clk48_unregister(void *data)
{
struct stm32_usbphyc *usbphyc = data;
of_clk_del_provider(usbphyc->dev->of_node);
clk_hw_unregister(&usbphyc->clk48_hw);
}
static int stm32_usbphyc_clk48_register(struct stm32_usbphyc *usbphyc)
{
struct device_node *node = usbphyc->dev->of_node;
struct clk_init_data init = { };
int ret = 0;
init.name = "ck_usbo_48m";
init.ops = &usbphyc_clk48_ops;
usbphyc->clk48_hw.init = &init;
ret = clk_hw_register(usbphyc->dev, &usbphyc->clk48_hw);
if (ret)
return ret;
ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, &usbphyc->clk48_hw);
if (ret)
clk_hw_unregister(&usbphyc->clk48_hw);
return ret;
}
static void stm32_usbphyc_phy_tuning(struct stm32_usbphyc *usbphyc,
struct device_node *np, u32 index)
{
struct stm32_usbphyc_phy *usbphyc_phy = usbphyc->phys[index];
u32 reg = STM32_USBPHYC_TUNE(index);
u32 otpcomp, val;
int ret;
/* Backup OTP compensation code */
otpcomp = FIELD_GET(OTPCOMP, readl_relaxed(usbphyc->base + reg));
ret = of_property_read_u32(np, "st,current-boost-microamp", &val);
if (ret != -EINVAL) {
if (!ret && (val == BOOST_1000_UA || val == BOOST_2000_UA)) {
val = (val == BOOST_2000_UA) ? 1 : 0;
usbphyc_phy->tune |= INCURREN | FIELD_PREP(INCURRINT, val);
} else {
dev_warn(usbphyc->dev, "phy%d: invalid st,current-boost-microamp\n", index);
}
}
if (!of_property_read_bool(np, "st,no-lsfs-fb-cap"))
usbphyc_phy->tune |= LFSCAPEN;
if (of_property_read_bool(np, "st,decrease-hs-slew-rate"))
usbphyc_phy->tune |= HSDRVSLEW;
ret = of_property_read_u32(np, "st,tune-hs-dc-level", &val);
if (ret != -EINVAL) {
if (!ret && val < DC_MAX) {
if (val == DC_MINUS_5_TO_7_MV) {/* Decreases HS driver DC level */
usbphyc_phy->tune |= HSDRVDCCUR;
} else if (val > 0) { /* Increases HS driver DC level */
val = (val == DC_PLUS_10_TO_14_MV) ? 1 : 0;
usbphyc_phy->tune |= HSDRVCURINCR | FIELD_PREP(HSDRVDCLEV, val);
}
} else {
dev_warn(usbphyc->dev, "phy%d: invalid st,tune-hs-dc-level\n", index);
}
}
if (of_property_read_bool(np, "st,enable-fs-rftime-tuning"))
usbphyc_phy->tune |= FSDRVRFADJ;
if (of_property_read_bool(np, "st,enable-hs-rftime-reduction"))
usbphyc_phy->tune |= HSDRVRFRED;
ret = of_property_read_u32(np, "st,trim-hs-current", &val);
if (ret != -EINVAL) {
if (!ret && val < CUR_MAX)
usbphyc_phy->tune |= FIELD_PREP(HSDRVCHKITRM, val);
else
dev_warn(usbphyc->dev, "phy%d: invalid st,trim-hs-current\n", index);
}
ret = of_property_read_u32(np, "st,trim-hs-impedance", &val);
if (ret != -EINVAL) {
if (!ret && val < IMP_MAX)
usbphyc_phy->tune |= FIELD_PREP(HSDRVCHKZTRM, val);
else
dev_warn(usbphyc->dev, "phy%d: invalid st,trim-hs-impedance\n", index);
}
ret = of_property_read_u32(np, "st,tune-squelch-level", &val);
if (ret != -EINVAL) {
if (!ret && val < SQLCH_MAX)
usbphyc_phy->tune |= FIELD_PREP(SQLCHCTL, val);
else
dev_warn(usbphyc->dev, "phy%d: invalid st,tune-squelch\n", index);
}
if (of_property_read_bool(np, "st,enable-hs-rx-gain-eq"))
usbphyc_phy->tune |= HDRXGNEQEN;
ret = of_property_read_u32(np, "st,tune-hs-rx-offset", &val);
if (ret != -EINVAL) {
if (!ret && val < RX_OFFSET_MAX)
usbphyc_phy->tune |= FIELD_PREP(HSRXOFF, val);
else
dev_warn(usbphyc->dev, "phy%d: invalid st,tune-hs-rx-offset\n", index);
}
if (of_property_read_bool(np, "st,no-hs-ftime-ctrl"))
usbphyc_phy->tune |= HSFALLPREEM;
if (!of_property_read_bool(np, "st,no-lsfs-sc"))
usbphyc_phy->tune |= SHTCCTCTLPROT;
if (of_property_read_bool(np, "st,enable-hs-tx-staggering"))
usbphyc_phy->tune |= STAGSEL;
/* Restore OTP compensation code */
usbphyc_phy->tune |= FIELD_PREP(OTPCOMP, otpcomp);
/*
* By default, if no st,xxx tuning property is used, usbphyc_phy->tune is equal to
* STM32_USBPHYC_TUNE reset value (LFSCAPEN | SHTCCTCTLPROT | OTPCOMP).
*/
writel_relaxed(usbphyc_phy->tune, usbphyc->base + reg);
}
static void stm32_usbphyc_switch_setup(struct stm32_usbphyc *usbphyc,
u32 utmi_switch)
{
if (!utmi_switch)
stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_MISC,
SWITHOST);
else
stm32_usbphyc_set_bits(usbphyc->base + STM32_USBPHYC_MISC,
SWITHOST);
usbphyc->switch_setup = utmi_switch;
}
static struct phy *stm32_usbphyc_of_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev);
struct stm32_usbphyc_phy *usbphyc_phy = NULL;
struct device_node *phynode = args->np;
int port = 0;
for (port = 0; port < usbphyc->nphys; port++) {
if (phynode == usbphyc->phys[port]->phy->dev.of_node) {
usbphyc_phy = usbphyc->phys[port];
break;
}
}
if (!usbphyc_phy) {
dev_err(dev, "failed to find phy\n");
return ERR_PTR(-EINVAL);
}
if (((usbphyc_phy->index == 0) && (args->args_count != 0)) ||
((usbphyc_phy->index == 1) && (args->args_count != 1))) {
dev_err(dev, "invalid number of cells for phy port%d\n",
usbphyc_phy->index);
return ERR_PTR(-EINVAL);
}
/* Configure the UTMI switch for PHY port#2 */
if (usbphyc_phy->index == 1) {
if (usbphyc->switch_setup < 0) {
stm32_usbphyc_switch_setup(usbphyc, args->args[0]);
} else {
if (args->args[0] != usbphyc->switch_setup) {
dev_err(dev, "phy port1 already used\n");
return ERR_PTR(-EBUSY);
}
}
}
return usbphyc_phy->phy;
}
static int stm32_usbphyc_probe(struct platform_device *pdev)
{
struct stm32_usbphyc *usbphyc;
struct device *dev = &pdev->dev;
struct device_node *child, *np = dev->of_node;
struct phy_provider *phy_provider;
u32 pllen, version;
int ret, port = 0;
usbphyc = devm_kzalloc(dev, sizeof(*usbphyc), GFP_KERNEL);
if (!usbphyc)
return -ENOMEM;
usbphyc->dev = dev;
dev_set_drvdata(dev, usbphyc);
usbphyc->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(usbphyc->base))
return PTR_ERR(usbphyc->base);
usbphyc->clk = devm_clk_get(dev, NULL);
if (IS_ERR(usbphyc->clk))
return dev_err_probe(dev, PTR_ERR(usbphyc->clk), "clk get_failed\n");
ret = clk_prepare_enable(usbphyc->clk);
if (ret) {
dev_err(dev, "clk enable failed: %d\n", ret);
return ret;
}
usbphyc->rst = devm_reset_control_get(dev, NULL);
if (!IS_ERR(usbphyc->rst)) {
reset_control_assert(usbphyc->rst);
udelay(2);
reset_control_deassert(usbphyc->rst);
} else {
ret = PTR_ERR(usbphyc->rst);
if (ret == -EPROBE_DEFER)
goto clk_disable;
stm32_usbphyc_clr_bits(usbphyc->base + STM32_USBPHYC_PLL, PLLEN);
}
/*
* Wait for minimum width of powerdown pulse (ENABLE = Low):
* we have to ensure the PLL is disabled before phys initialization.
*/
if (readl_relaxed_poll_timeout(usbphyc->base + STM32_USBPHYC_PLL,
pllen, !(pllen & PLLEN), 5, 50)) {
dev_warn(usbphyc->dev, "PLL not reset\n");
ret = -EPROBE_DEFER;
goto clk_disable;
}
usbphyc->switch_setup = -EINVAL;
usbphyc->nphys = of_get_child_count(np);
usbphyc->phys = devm_kcalloc(dev, usbphyc->nphys,
sizeof(*usbphyc->phys), GFP_KERNEL);
if (!usbphyc->phys) {
ret = -ENOMEM;
goto clk_disable;
}
usbphyc->vdda1v1 = devm_regulator_get(dev, "vdda1v1");
if (IS_ERR(usbphyc->vdda1v1)) {
ret = dev_err_probe(dev, PTR_ERR(usbphyc->vdda1v1),
"failed to get vdda1v1 supply\n");
goto clk_disable;
}
usbphyc->vdda1v8 = devm_regulator_get(dev, "vdda1v8");
if (IS_ERR(usbphyc->vdda1v8)) {
ret = dev_err_probe(dev, PTR_ERR(usbphyc->vdda1v8),
"failed to get vdda1v8 supply\n");
goto clk_disable;
}
for_each_child_of_node(np, child) {
struct stm32_usbphyc_phy *usbphyc_phy;
struct phy *phy;
u32 index;
phy = devm_phy_create(dev, child, &stm32_usbphyc_phy_ops);
if (IS_ERR(phy)) {
ret = PTR_ERR(phy);
if (ret != -EPROBE_DEFER)
dev_err(dev, "failed to create phy%d: %d\n",
port, ret);
goto put_child;
}
usbphyc_phy = devm_kzalloc(dev, sizeof(*usbphyc_phy),
GFP_KERNEL);
if (!usbphyc_phy) {
ret = -ENOMEM;
goto put_child;
}
ret = of_property_read_u32(child, "reg", &index);
if (ret || index >= usbphyc->nphys) {
dev_err(&phy->dev, "invalid reg property: %d\n", ret);
if (!ret)
ret = -EINVAL;
goto put_child;
}
usbphyc->phys[port] = usbphyc_phy;
phy_set_bus_width(phy, 8);
phy_set_drvdata(phy, usbphyc_phy);
usbphyc->phys[port]->phy = phy;
usbphyc->phys[port]->usbphyc = usbphyc;
usbphyc->phys[port]->index = index;
usbphyc->phys[port]->active = false;
usbphyc->phys[port]->vbus = devm_regulator_get_optional(&phy->dev, "vbus");
if (IS_ERR(usbphyc->phys[port]->vbus)) {
ret = PTR_ERR(usbphyc->phys[port]->vbus);
if (ret == -EPROBE_DEFER)
goto put_child;
usbphyc->phys[port]->vbus = NULL;
}
/* Configure phy tuning */
stm32_usbphyc_phy_tuning(usbphyc, child, index);
port++;
}
phy_provider = devm_of_phy_provider_register(dev,
stm32_usbphyc_of_xlate);
if (IS_ERR(phy_provider)) {
ret = PTR_ERR(phy_provider);
dev_err(dev, "failed to register phy provider: %d\n", ret);
goto clk_disable;
}
ret = stm32_usbphyc_clk48_register(usbphyc);
if (ret) {
dev_err(dev, "failed to register ck_usbo_48m clock: %d\n", ret);
goto clk_disable;
}
version = readl_relaxed(usbphyc->base + STM32_USBPHYC_VERSION);
dev_dbg(dev, "registered rev: %lu.%lu\n",
FIELD_GET(MAJREV, version), FIELD_GET(MINREV, version));
return 0;
put_child:
of_node_put(child);
clk_disable:
clk_disable_unprepare(usbphyc->clk);
return ret;
}
static void stm32_usbphyc_remove(struct platform_device *pdev)
{
struct stm32_usbphyc *usbphyc = dev_get_drvdata(&pdev->dev);
int port;
/* Ensure PHYs are not active, to allow PLL disabling */
for (port = 0; port < usbphyc->nphys; port++)
if (usbphyc->phys[port]->active)
stm32_usbphyc_phy_exit(usbphyc->phys[port]->phy);
stm32_usbphyc_clk48_unregister(usbphyc);
clk_disable_unprepare(usbphyc->clk);
}
static int __maybe_unused stm32_usbphyc_resume(struct device *dev)
{
struct stm32_usbphyc *usbphyc = dev_get_drvdata(dev);
struct stm32_usbphyc_phy *usbphyc_phy;
int port;
if (usbphyc->switch_setup >= 0)
stm32_usbphyc_switch_setup(usbphyc, usbphyc->switch_setup);
for (port = 0; port < usbphyc->nphys; port++) {
usbphyc_phy = usbphyc->phys[port];
writel_relaxed(usbphyc_phy->tune, usbphyc->base + STM32_USBPHYC_TUNE(port));
}
return 0;
}
static SIMPLE_DEV_PM_OPS(stm32_usbphyc_pm_ops, NULL, stm32_usbphyc_resume);
static const struct of_device_id stm32_usbphyc_of_match[] = {
{ .compatible = "st,stm32mp1-usbphyc", },
{ },
};
MODULE_DEVICE_TABLE(of, stm32_usbphyc_of_match);
static struct platform_driver stm32_usbphyc_driver = {
.probe = stm32_usbphyc_probe,
.remove = stm32_usbphyc_remove,
.driver = {
.of_match_table = stm32_usbphyc_of_match,
.name = "stm32-usbphyc",
.pm = &stm32_usbphyc_pm_ops,
}
};
module_platform_driver(stm32_usbphyc_driver);
MODULE_DESCRIPTION("STMicroelectronics STM32 USBPHYC driver");
MODULE_AUTHOR("Amelie Delaunay <amelie.delaunay@st.com>");
MODULE_LICENSE("GPL v2");