clk: en7523: Add support for selecting the Serdes port in SCU

In the SCU register for clock and reset, there are also some register to
select the Serdes port mode. The Airoha AN7581 SoC have 4 different Serdes
that can switch between PCIe, USB or Ethernet mode.

Add a simple PHY provider that expose the .set_mode OP to toggle the
requested mode for the Serdes port.

Reviewed-by: Brian Masney <bmasney@redhat.com>
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Brian Masney <bmasney@redhat.com>
This commit is contained in:
Christian Marangi 2026-09-15 14:52:24 +02:00 committed by Brian Masney
parent 11952173d8
commit 4e39820b60
2 changed files with 216 additions and 3 deletions

View File

@ -221,6 +221,7 @@ config COMMON_CLK_EN7523
bool "Clock driver for Airoha/EcoNet SoC system clocks"
depends on OF
depends on ARCH_AIROHA || ECONET || COMPILE_TEST
select GENERIC_PHY
default ARCH_AIROHA
help
This driver provides the fixed clocks and gates present on Airoha

View File

@ -6,14 +6,18 @@
#include <linux/io.h>
#include <linux/mfd/syscon.h>
#include <linux/platform_device.h>
#include <linux/phy.h>
#include <linux/phy/phy.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/reset-controller.h>
#include <linux/spinlock.h>
#include <dt-bindings/clock/en7523-clk.h>
#include <dt-bindings/reset/airoha,en7523-reset.h>
#include <dt-bindings/reset/airoha,en7581-reset.h>
#include <dt-bindings/clock/econet,en751221-scu.h>
#include <dt-bindings/reset/econet,en751221-scu.h>
#include <dt-bindings/soc/airoha,scu-ssr.h>
#define RST_NR_PER_BANK 32
@ -40,9 +44,22 @@
#define REG_HIR_MASK GENMASK(31, 16)
/* EN7581 */
#define REG_NP_SCU_PCIC 0x88
#define REG_NP_SCU_SSR3 0x94
#define REG_SSUSB_HSGMII_SEL_MASK BIT(29)
#define REG_SSUSB_HSGMII_SEL_HSGMII FIELD_PREP_CONST(REG_SSUSB_HSGMII_SEL_MASK, 0x0)
#define REG_SSUSB_HSGMII_SEL_USB FIELD_PREP_CONST(REG_SSUSB_HSGMII_SEL_MASK, 0x1)
#define REG_NP_SCU_SSTR 0x9c
#define REG_PCIE_XSI0_SEL_MASK GENMASK(14, 13)
#define REG_PCIE_XSI0_SEL_PCIE FIELD_PREP_CONST(REG_PCIE_XSI0_SEL_MASK, 0x0)
#define REG_PCIE_XSI0_SEL_XFI FIELD_PREP_CONST(REG_PCIE_XSI0_SEL_MASK, 0x1)
#define REG_PCIE_XSI0_SEL_HSGMII FIELD_PREP_CONST(REG_PCIE_XSI0_SEL_MASK, 0x2)
#define REG_PCIE_XSI1_SEL_MASK GENMASK(12, 11)
#define REG_PCIE_XSI1_SEL_PCIE FIELD_PREP_CONST(REG_PCIE_XSI1_SEL_MASK, 0x0)
#define REG_PCIE_XSI1_SEL_XFI FIELD_PREP_CONST(REG_PCIE_XSI1_SEL_MASK, 0x1)
#define REG_PCIE_XSI1_SEL_HSGMII FIELD_PREP_CONST(REG_PCIE_XSI1_SEL_MASK, 0x2)
#define REG_USB_PCIE_SEL_MASK BIT(3)
#define REG_USB_PCIE_SEL_PCIE FIELD_PREP_CONST(REG_USB_PCIE_SEL_MASK, 0x0)
#define REG_USB_PCIE_SEL_USB FIELD_PREP_CONST(REG_USB_PCIE_SEL_MASK, 0x1)
#define REG_CRYPTO_CLKSRC2 0x20c
/* EN751221 */
#define EN751221_REG_SPI_DIV 0x0cc
@ -82,6 +99,8 @@ enum en_hir {
HIR_MAX = 14,
};
#define EN_SERDES_PHY_NUM 4
struct en_clk_desc {
int id;
const char *name;
@ -114,6 +133,18 @@ struct en_rst_data {
struct reset_controller_dev rcdev;
};
struct en_serdes_phy_instance {
struct phy *phy;
unsigned int serdes_port;
};
struct en_clk_priv {
void __iomem *base;
/* protect SCU register */
spinlock_t lock;
struct en_serdes_phy_instance *serdes_phys[EN_SERDES_PHY_NUM];
};
struct en_clk_soc_data {
u32 num_clocks;
const struct clk_ops pcie_ops;
@ -847,12 +878,179 @@ static int en7581_reset_register(struct device *dev, void __iomem *base,
return devm_reset_controller_register(dev, &rst_data->rcdev);
}
static int en7581_serdes_phy_set_mode(struct phy *phy, enum phy_mode mode,
int submode)
{
struct en_serdes_phy_instance *instance = phy_get_drvdata(phy);
struct en_clk_priv *priv = dev_get_drvdata(phy->dev.parent);
u32 reg, mask, sel, val;
unsigned long flags;
switch (instance->serdes_port) {
case AIROHA_SCU_SERDES_PCIE1:
reg = REG_NP_SCU_SSTR;
mask = REG_PCIE_XSI0_SEL_MASK;
if (mode != PHY_MODE_ETHERNET && mode != PHY_MODE_PCIE)
return -EINVAL;
if (mode == PHY_MODE_ETHERNET) {
switch (submode) {
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10GBASER:
sel = REG_PCIE_XSI0_SEL_XFI;
break;
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
sel = REG_PCIE_XSI0_SEL_HSGMII;
break;
default:
return -EINVAL;
}
} else {
sel = REG_PCIE_XSI0_SEL_PCIE;
}
break;
case AIROHA_SCU_SERDES_PCIE2:
reg = REG_NP_SCU_SSTR;
mask = REG_PCIE_XSI1_SEL_MASK;
if (mode != PHY_MODE_ETHERNET && mode != PHY_MODE_PCIE)
return -EINVAL;
if (mode == PHY_MODE_ETHERNET) {
switch (submode) {
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10GBASER:
sel = REG_PCIE_XSI1_SEL_XFI;
break;
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
sel = REG_PCIE_XSI1_SEL_HSGMII;
break;
default:
return -EINVAL;
}
} else {
sel = REG_PCIE_XSI1_SEL_PCIE;
}
break;
case AIROHA_SCU_SERDES_USB1:
reg = REG_NP_SCU_SSR3;
mask = REG_SSUSB_HSGMII_SEL_MASK;
if (mode != PHY_MODE_ETHERNET && mode != PHY_MODE_USB_DEVICE &&
mode != PHY_MODE_USB_DEVICE_SS)
return -EINVAL;
if (mode == PHY_MODE_ETHERNET)
sel = REG_SSUSB_HSGMII_SEL_HSGMII;
else
sel = REG_SSUSB_HSGMII_SEL_USB;
break;
case AIROHA_SCU_SERDES_USB2:
reg = REG_NP_SCU_SSTR;
mask = REG_USB_PCIE_SEL_MASK;
if (mode != PHY_MODE_PCIE && mode != PHY_MODE_USB_DEVICE &&
mode != PHY_MODE_USB_DEVICE_SS)
return -EINVAL;
if (mode == PHY_MODE_PCIE)
sel = REG_USB_PCIE_SEL_PCIE;
else
sel = REG_USB_PCIE_SEL_USB;
break;
default:
return -EINVAL;
}
spin_lock_irqsave(&priv->lock, flags);
val = readl(priv->base + reg);
val &= ~mask;
val |= sel;
writel(val, priv->base + reg);
spin_unlock_irqrestore(&priv->lock, flags);
return 0;
}
static const struct phy_ops en7581_serdes_phy_ops = {
.set_mode = en7581_serdes_phy_set_mode,
.owner = THIS_MODULE,
};
static struct phy *en7581_serdes_phy_xlate(struct device *dev,
const struct of_phandle_args *args)
{
struct en_clk_priv *priv = dev_get_drvdata(dev);
struct en_serdes_phy_instance *instance;
unsigned int serdes_port;
if (args->args_count != 1) {
dev_err(dev, "invalid number of cells in 'phy' property\n");
return ERR_PTR(-EINVAL);
}
serdes_port = args->args[0];
if (serdes_port >= EN_SERDES_PHY_NUM) {
dev_err(dev, "invalid serdes port: %d\n", serdes_port);
return ERR_PTR(-EINVAL);
}
instance = priv->serdes_phys[serdes_port];
if (!instance) {
dev_err(dev, "failed to find appropriate serdes phy\n");
return ERR_PTR(-EINVAL);
}
return instance->phy;
}
static int en7581_serdes_phy_register(struct device *dev)
{
struct en_clk_priv *priv = dev_get_drvdata(dev);
struct phy_provider *phy_provider;
int i;
for (i = 0; i < EN_SERDES_PHY_NUM; i++) {
struct en_serdes_phy_instance *instance;
instance = devm_kzalloc(dev, sizeof(*instance),
GFP_KERNEL);
if (!instance)
return -ENOMEM;
instance->phy = devm_phy_create(dev, NULL,
&en7581_serdes_phy_ops);
if (IS_ERR(instance->phy))
return dev_err_probe(dev, PTR_ERR(instance->phy), "failed to create phy\n");
instance->serdes_port = i;
priv->serdes_phys[i] = instance;
phy_set_drvdata(instance->phy, instance);
}
phy_provider = devm_of_phy_provider_register(dev, en7581_serdes_phy_xlate);
return PTR_ERR_OR_ZERO(phy_provider);
}
static int en7581_clk_hw_init(struct platform_device *pdev,
struct clk_hw_onecell_data *clk_data)
{
struct en_clk_priv *priv = platform_get_drvdata(pdev);
struct regmap *map;
void __iomem *base;
u32 val;
int ret;
map = syscon_regmap_lookup_by_compatible("airoha,en7581-chip-scu");
if (IS_ERR(map))
@ -862,6 +1060,8 @@ static int en7581_clk_hw_init(struct platform_device *pdev,
if (IS_ERR(base))
return PTR_ERR(base);
priv->base = base;
en7581_register_clocks(&pdev->dev, clk_data, map, base);
val = readl(base + REG_NP_SCU_SSTR);
@ -876,9 +1076,12 @@ static int en7581_clk_hw_init(struct platform_device *pdev,
val &= ~REG_PCIE_HB_RST;
writel(val, base + REG_RST_CTRL1);
return en7581_reset_register(&pdev->dev, base, en7581_rst_map,
ARRAY_SIZE(en7581_rst_map),
en7581_rst_ofs);
ret = en7581_reset_register(&pdev->dev, base, en7581_rst_map,
ARRAY_SIZE(en7581_rst_map), en7581_rst_ofs);
if (ret)
return ret;
return en7581_serdes_phy_register(&pdev->dev);
}
static enum en_hir get_hw_id(void __iomem *np_base)
@ -985,16 +1188,25 @@ static int en7523_clk_probe(struct platform_device *pdev)
struct device_node *node = pdev->dev.of_node;
const struct en_clk_soc_data *soc_data;
struct clk_hw_onecell_data *clk_data;
struct en_clk_priv *priv;
int r;
soc_data = device_get_match_data(&pdev->dev);
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
spin_lock_init(&priv->lock);
clk_data = devm_kzalloc(&pdev->dev,
struct_size(clk_data, hws, soc_data->num_clocks),
GFP_KERNEL);
if (!clk_data)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
clk_data->num = soc_data->num_clocks;
r = soc_data->hw_init(pdev, clk_data);
if (r)