phy: spacemit: Add USB3/PCIe comb PHY driver for Spacemit K3

The comb PHY on K3 requires to configure a syscon device for the
right mux configuration. And it requires calibration before any
usage.

Add USB3/PCIe comb PHY driver for Spacemit K3.

Signed-off-by: Inochi Amaoto <inochiama@gmail.com>
Tested-by: Aurelien Jarno <aurelien@aurel32.net>
Link: https://patch.msgid.link/20260703021024.495433-3-inochiama@gmail.com
Signed-off-by: Vinod Koul <vkoul@kernel.org>
This commit is contained in:
Inochi Amaoto
2026-07-03 10:10:23 +08:00
committed by Vinod Koul
parent 013004374b
commit 7a099760ea
5 changed files with 688 additions and 0 deletions

View File

@@ -23,3 +23,19 @@ config PHY_SPACEMIT_K1_USB2
help
Enable this to support K1 USB 2.0 PHY driver. This driver takes care of
enabling and clock setup and will be used by K1 udc/ehci/otg/xhci driver.
config PHY_SPACEMIT_K3_COMMON_OPS
tristate
select MFD_SYSCON
select GENERIC_PHY
config PHY_SPACEMIT_K3_COMBO_PHY
tristate "SpacemiT K3 USB3/PCIe PHY support"
depends on (ARCH_SPACEMIT || COMPILE_TEST) && OF
depends on COMMON_CLK
select PHY_SPACEMIT_K3_COMMON_OPS
help
Enable this to support K3 USB3/PCIe combo PHY driver. This
driver takes care of enabling and clock setup and will be used
by K3 dwc3 driver.
If unsure, say N.

View File

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_PHY_SPACEMIT_K1_PCIE) += phy-k1-pcie.o
obj-$(CONFIG_PHY_SPACEMIT_K1_USB2) += phy-k1-usb2.o
obj-$(CONFIG_PHY_SPACEMIT_K3_COMBO_PHY) += phy-k3-combo.o
obj-$(CONFIG_PHY_SPACEMIT_K3_COMMON_OPS) += phy-k3-common.o

View File

@@ -0,0 +1,252 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* phy-k3-combo.c - SpacemiT K3 PCIe/USB3 Combo PHY Driver
*
* Copyright (c) 2025 SpacemiT Technology Co. Ltd
*/
#include <linux/bitfield.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/mfd/syscon.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/phy/phy.h>
#include <dt-bindings/phy/phy.h>
#include "phy-k3-common.h"
/*
* The PCIE/USB Subsystem on SpacemiT K3 have 3 single lane PIPE3 PHYs
* (PHY2/3/4) shared by PCIE PortC/D and USB3 PortB/C/D.
*
* PMUA_PCIE_SUBSYS_MGMT[4:0]
*
* bit4 = 0 : PCIe A X8 mode, all 8 lanes dedicated to PCIe Port A
* 1 : PHY lanes shared between PCIe or USB according to [3:0]
*
* All PHY matrix combinations according to [4:0]:
*
* 0x0X : PCIe-A X8
* 0x10 : PCIe-C x2 (PHY2+PHY3) + PCIe-D x1 (PHY4)
* 0x11 : PCIe-C x2 (PHY2+PHY3) + USB-D (PHY4)
* 0x12 : PCIe-C x1 (PHY2) + USB-C (PHY3)
* 0x13 : PCIe-C x1 (PHY2) + USB-C (PHY3) + USB-D (PHY4)
* 0x14 : PCIe-C x1 (PHY3) + USB-B (PHY2)
* 0x15 : PCIe-C x1 (PHY3) + USB-B (PHY2) + USB-D (PHY4)
* 0x16 : USB-B (PHY2) + USB-C (PHY3) + PCIe D x1 (PHY4)
* 0x17 : USB-B (PHY2) + USB-C (PHY3) + USB-D (PHY4)
*
* So any USB Port B/C/D operation requires PCIe A X8 mode to be disabled.
*/
#define PMUA_PCIE_SUBSYS_MGMT 0x1d8
#define PU_MATRIX_CONF_MASK GENMASK(4, 0)
#define COMBPHY_MAX_SUBPHYS 6
struct k3_combo_phy {
struct device *dev;
struct k3_lane_group groups[COMBPHY_MAX_SUBPHYS];
void __iomem *base;
struct regmap *apb_spare;
};
static const struct k3_phy_lane_group_data k3_combphy_lane_group0 = {
.lanes = 2,
.config = 0xff,
.mask = 0x00,
.offsets = {
0x0, 0x400
},
};
static const struct k3_phy_lane_group_data k3_combphy_lane_group1 = {
.lanes = 2,
.config = 0xff,
.mask = 0x00,
.offsets = {
0x100000, 0x100400
},
};
static const struct k3_phy_lane_group_data k3_combphy_lane_group2 = {
.lanes = 1,
.config = 0x14,
.mask = 0x14,
.offsets = {
0x200000
},
};
static const struct k3_phy_lane_group_data k3_combphy_lane_group3 = {
.lanes = 1,
.config = 0x12,
.mask = 0x12,
.offsets = {
0x300000
},
};
static const struct k3_phy_lane_group_data k3_combphy_lane_group4 = {
.lanes = 1,
.config = 0x11,
.mask = 0x11,
.offsets = {
0x400000
},
};
static const struct k3_phy_lane_group_data k3_combphy_lane_group5 = {
.lanes = 1,
.config = 0xff,
.mask = 0x00,
.offsets = {
0x500000
},
};
static const struct k3_phy_lane_group_data *k3_combphy_lane_datas[] = {
&k3_combphy_lane_group0,
&k3_combphy_lane_group1,
&k3_combphy_lane_group2,
&k3_combphy_lane_group3,
&k3_combphy_lane_group4,
&k3_combphy_lane_group5,
};
static int k3_combo_phy_init_lanes(struct k3_combo_phy *phy, unsigned int config)
{
int i;
for (i = 0; i < ARRAY_SIZE(k3_combphy_lane_datas); i++) {
const struct k3_phy_lane_group_data *data = k3_combphy_lane_datas[i];
struct k3_lane_group *lg = &phy->groups[i];
const struct phy_ops *ops;
bool is_usb;
is_usb = (data->mask & config) == data->config;
if (is_usb)
ops = &k3_usb3_phy_ops;
else
ops = &k3_pcie_phy_ops;
dev_dbg(phy->dev, "phy %d is %s\n", i, is_usb ? "usb" : "pcie");
lg->phy = devm_phy_create(phy->dev, NULL, ops);
if (IS_ERR(lg->phy))
return PTR_ERR(lg->phy);
lg->is_pcie = !is_usb;
lg->data = data;
lg->base = phy->base;
phy_set_drvdata(lg->phy, lg);
}
return 0;
}
static int k3_combo_phy_update_config(struct regmap *apmu, unsigned int config)
{
if (config & ~PU_MATRIX_CONF_MASK)
return -EINVAL;
return regmap_update_bits(apmu, PMUA_PCIE_SUBSYS_MGMT, PU_MATRIX_CONF_MASK, config);
}
static struct phy *k3_combo_phy_xlate(struct device *dev, const struct of_phandle_args *args)
{
struct k3_combo_phy *phy = dev_get_drvdata(dev);
struct k3_lane_group *lg;
if (args->args_count != 2) {
dev_err(dev, "Invalid number of arguments\n");
return ERR_PTR(-EINVAL);
}
if (args->args[0] >= ARRAY_SIZE(k3_combphy_lane_datas)) {
dev_err(dev, "Invalid PHY id\n");
return ERR_PTR(-EINVAL);
}
lg = &phy->groups[args->args[0]];
if ((lg->is_pcie && args->args[1] != PHY_TYPE_PCIE) ||
(!lg->is_pcie && args->args[1] != PHY_TYPE_USB3)) {
dev_err(dev, "Invalid PHY mode\n");
return ERR_PTR(-EINVAL);
}
return lg->phy;
}
static int k3_combo_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *node = dev->of_node;
struct phy_provider *provider;
struct k3_combo_phy *phy;
struct regmap *apmu;
u32 config = 0;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENOMEM;
phy->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(phy->base))
return PTR_ERR(phy->base);
phy->apb_spare = syscon_regmap_lookup_by_phandle(node, "spacemit,apb-spare");
if (IS_ERR(phy->apb_spare))
return dev_err_probe(dev, PTR_ERR(phy->apb_spare),
"Failed to find APB SPARE syscon");
apmu = syscon_regmap_lookup_by_phandle_args(node, "spacemit,apmu", 1, &config);
if (IS_ERR(apmu))
return dev_err_probe(dev, PTR_ERR(apmu),
"Failed to find APMU syscon");
ret = k3_combo_phy_update_config(apmu, config);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to set lane configuration");
phy->dev = dev;
platform_set_drvdata(pdev, phy);
ret = k3_phy_calibrate(phy->apb_spare);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to calibrate phy");
ret = k3_combo_phy_init_lanes(phy, config);
if (ret < 0)
return dev_err_probe(dev, ret, "Failed to init lanes");
provider = devm_of_phy_provider_register(dev, k3_combo_phy_xlate);
if (IS_ERR(provider))
return dev_err_probe(dev, PTR_ERR(provider),
"Failed to register provider\n");
return 0;
}
static const struct of_device_id k3_combo_phy_of_match[] = {
{ .compatible = "spacemit,k3-combo-phy" },
{ },
};
MODULE_DEVICE_TABLE(of, k3_combo_phy_of_match);
static struct platform_driver k3_combo_phy_driver = {
.probe = k3_combo_phy_probe,
.driver = {
.name = "spacemit,k3-combo-phy",
.of_match_table = k3_combo_phy_of_match,
},
};
module_platform_driver(k3_combo_phy_driver);
MODULE_DESCRIPTION("SpacemiT K3 USB3/PCIe combo PHY driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,391 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <linux/bitfield.h>
#include <linux/cleanup.h>
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/usb.h>
#include <dt-bindings/phy/phy.h>
#include "phy-k3-common.h"
/* PHY Registers */
#define PHY_VERSION 0x0
#define PHY_RESET_CFG 0x04
#define PHY_RESET_RXBUF_RST BIT(0)
#define PHY_RESET_SOFT_RST_PCS BIT(1)
#define PHY_RESET_SOFT_RST_AHB BIT(2)
#define PHY_RESET_EN_SD_AFTER_LOCK BIT(6)
#define PHY_CLK_CFG 0x08
#define PHY_CLK_PLL_READY BIT(0)
#define PHY_CLK_TXCLK_INV BIT(2)
#define PHY_CLK_RXCLK_EN BIT(3)
#define PHY_CLK_TXCLK_EN BIT(4)
#define PHY_CLK_PCLK_EN BIT(5)
#define PHY_CLK_PIPE_PCLK_EN BIT(6)
#define PHY_CLK_REFCLK_FREQ GENMASK(10, 7)
#define PHY_CLK_REFCLK_24M 2
#define PHY_CLK_SW_INIT_DONE BIT(11)
#define PHY_CLK_PU_SSC_OUT BIT(23)
#define PHY_MODE_CFG 0x0C
#define PHY_MODE_PCIE_INT_EN BIT(0)
#define PHY_MODE_LFPS_TPERIOD GENMASK(9, 8)
#define PHY_MODE_LFPS_TPERIOD_USB 3
#define PHY_PU_SEL 0x40
#define PHY_PU_CFG_STATUS BIT(9)
#define PHY_PU_OVRD_STATUS BIT(10)
#define PHY_PU_CK_REG 0x54
#define PHY_PU_REFCLK_100 BIT(25)
#define PHY_PLL_REG1 0x58
#define PHY_PLL_FREF_SEL GENMASK(15, 13)
#define PHY_PLL_FREF_24M 0x1
#define PHY_PLL_SSC_DEP_SEL GENMASK(27, 24)
#define PHY_PLL_SSC_5000PPM 0xa
#define PHY_PLL_SSC_MODE GENMASK(29, 28)
#define PHY_PLL_SSC_MODE_CENTER_SPREAD 0
#define PHY_PLL_SSC_MODE_UP_SPREAD 1
#define PHY_PLL_SSC_MODE_DOWN_SPREAD 2
#define PHY_PLL_SSC_MODE_DOWN_SPREAD1 3
#define PHY_PLL_REG2 0x5c
#define PHY_PLL_SEL_REF100 BIT(21)
/* PHY RX Register Definitions */
#define PHY_RX_REG_A 0x60
#define PHY_RX_REG0_MASK GENMASK(7, 0)
#define PHY_RX_REG1_MASK GENMASK(15, 8)
#define PHY_RX_REG2_MASK GENMASK(23, 16)
#define PHY_RX_REG3_MASK GENMASK(31, 24)
#define PHY_RX_REG0_RLOAD BIT(4)
#define PHY_RX_REG1_RTERM GENMASK(11, 8)
#define PHY_RX_REG1_RC_CALI GENMASK(15, 12)
#define PHY_RX_REG2_CSEL GENMASK(19, 16)
#define PHY_RX_REG2_FORCE_CSEL BIT(20)
#define PHY_RX_REG2_PSEL GENMASK(23, 21)
#define PHY_RX_REG3_I_LOAD GENMASK(26, 24)
#define PHY_RX_REG3_SEL_CBOOST_CODE BIT(27)
#define PHY_RX_REG3_ADJ_BIAS GENMASK(29, 28)
#define PHY_RX_REG3_RDEG1 GENMASK(31, 30)
#define PHY_RX_REG_B 0x64
#define PHY_RX_REG4_MASK GENMASK(7, 0)
#define PHY_RX_REG5_MASK GENMASK(15, 8)
#define PHY_RX_REG6_MASK GENMASK(23, 16)
#define PHY_RX_REGB_MASK GENMASK(23, 0)
#define PHY_RX_REG4_RDEG2 GENMASK(2, 1)
#define PHY_RX_REG4_ENVOS BIT(4)
#define PHY_RX_REG4_RTERM_SEL BIT(5)
#define PHY_RX_REG4_MANUAL_CFG BIT(7)
#define PHY_RX_REG5_RCELL_VCM GENMASK(11, 8)
#define PHY_RX_REG5_RCELL_BIAS GENMASK(15, 12)
#define PHY_RX_REG6_H1_REG GENMASK(19, 16)
#define PHY_RX_REG6_ADAPT_GAIN GENMASK(21, 20)
#define PHY_RX_REG6_BYPASS_ADPT BIT(22)
#define PHY_ADPT_CFG0 0x140
#define PHY_ADPT_AFE_RST_OVRD_EN BIT(1)
#define PHY_ADPT_AFE_RST_OVRD_VAL BIT(4)
#define PHY_RXEQ_TIME 0xb4
#define PHY_RXEQ_TIME_OVRD_POST_C_SOC BIT(21)
#define PHY_RXEQ_TIME_CFG_AMP_SOC GENMASK(23, 22)
#define PHY_RXEQ_TIME_AMP_SOC_650M 0
#define PHY_RXEQ_TIME_AMP_SOC_800M 1
#define PHY_RXEQ_TIME_AMP_SOC_870M 2
#define PHY_RXEQ_TIME_AMP_SOC_900M 3
#define PHY_RXEQ_TIME_OVRD_AMP_SOC BIT(24)
#define PCIE_PU_ADDR_CLK_CFG 0x0008
#define PHY_CLK_PLL_READY BIT(0)
#define PCIE_INITAL_TIMER GENMASK(6, 3)
#define CFG_INTERNAL_TIMER_ADJ GENMASK(10, 7)
#define CFG_SW_PHY_INIT_DONE BIT(11)
/* Lane RX/TX configuration (perlane, at lane_base) */
#define PCIE_RX_REG1 0x050
#define PCIE_RX_REFCLK_MODE GENMASK(1, 0)
#define PCIE_RX_REFCLK_MODE_DRIVER 1
#define PCIE_RX_SEL_TRI_CODE BIT(2)
#define PCIE_RX_LEGACY GENMASK(15, 8)
#define PCIE_RX_LEGACY_DEFAULT 0x65
#define PCIE_TX_REG1 0x064
#define PCIE_PLL_TIMEOUT 500000
#define PCIE_POLL_DELAY 500
static int k3_usb3phy_init_single(struct k3_lane_group *lg, void __iomem *base)
{
struct phy *phy = lg->phy;
u32 val, tmp;
int ret;
/* Do not wait CDR lock before sampling data */
val = readl(base + PHY_RESET_CFG);
val = u32_replace_bits(val, 0, PHY_RESET_EN_SD_AFTER_LOCK);
writel(val, base + PHY_RESET_CFG);
/* Power down 100MHz refclk buffer */
val = readl(base + PHY_PU_CK_REG);
val = u32_replace_bits(val, 0, PHY_PU_REFCLK_100);
writel(val, base + PHY_PU_CK_REG);
/* Program PLL REG1 configure the SSC */
val = FIELD_PREP(PHY_PLL_SSC_MODE, PHY_PLL_SSC_MODE_DOWN_SPREAD1) |
FIELD_PREP(PHY_PLL_SSC_DEP_SEL, PHY_PLL_SSC_5000PPM) |
FIELD_PREP(PHY_PLL_FREF_SEL, PHY_PLL_FREF_24M);
writel(val, base + PHY_PLL_REG1);
/* Un-select 100MHz PLL reference */
val = readl(base + PHY_PLL_REG2);
val = u32_replace_bits(val, 0, PHY_PLL_SEL_REF100);
writel(val, base + PHY_PLL_REG2);
/* USB LFPS period configuration */
val = readl(base + PHY_MODE_CFG);
val = u32_replace_bits(val, PHY_MODE_LFPS_TPERIOD_USB, PHY_MODE_LFPS_TPERIOD);
writel(val, base + PHY_MODE_CFG);
/* Force AFE adaptation reset */
val = readl(base + PHY_ADPT_CFG0);
val |= PHY_ADPT_AFE_RST_OVRD_EN | PHY_ADPT_AFE_RST_OVRD_VAL;
writel(val, base + PHY_ADPT_CFG0);
/* Override driver amplitude value to 900m */
val = readl(base + PHY_RXEQ_TIME);
val |= PHY_RXEQ_TIME_OVRD_AMP_SOC;
val = u32_replace_bits(val, PHY_RXEQ_TIME_AMP_SOC_900M, PHY_RXEQ_TIME_CFG_AMP_SOC);
writel(val, base + PHY_RXEQ_TIME);
/* Configure RX parameters */
val = PHY_RX_REG0_RLOAD |
FIELD_PREP(PHY_RX_REG1_RTERM, 0x8) |
FIELD_PREP(PHY_RX_REG1_RC_CALI, 0x7) |
FIELD_PREP(PHY_RX_REG2_CSEL, 0x8) |
PHY_RX_REG2_FORCE_CSEL |
FIELD_PREP(PHY_RX_REG2_PSEL, 0x4) |
FIELD_PREP(PHY_RX_REG3_I_LOAD, 0x7) |
PHY_RX_REG3_SEL_CBOOST_CODE |
FIELD_PREP(PHY_RX_REG3_ADJ_BIAS, 0x1) |
FIELD_PREP(PHY_RX_REG3_RDEG1, 0x3);
writel(val, base + PHY_RX_REG_A);
val = readl(base + PHY_RX_REG_B);
tmp = FIELD_PREP(PHY_RX_REG4_RDEG2, 0x2) |
PHY_RX_REG4_ENVOS | PHY_RX_REG4_RTERM_SEL | PHY_RX_REG4_MANUAL_CFG |
FIELD_PREP(PHY_RX_REG5_RCELL_VCM, 0x8) |
FIELD_PREP(PHY_RX_REG5_RCELL_BIAS, 0x8) |
FIELD_PREP(PHY_RX_REG6_H1_REG, 0x8) |
FIELD_PREP(PHY_RX_REG6_ADAPT_GAIN, 0x2);
val = u32_replace_bits(val, tmp, PHY_RX_REGB_MASK);
writel(val, base + PHY_RX_REG_B);
/*
* Inform PHY that all PLL-related configuration is done.
* PLL will not start locking until PHY_CLK_SW_INIT_DONE is set.
*/
val = PHY_CLK_SW_INIT_DONE | PHY_CLK_PU_SSC_OUT |
FIELD_PREP(PHY_CLK_REFCLK_FREQ, PHY_CLK_REFCLK_24M) |
PHY_CLK_RXCLK_EN | PHY_CLK_TXCLK_EN |
PHY_CLK_PCLK_EN | PHY_CLK_PIPE_PCLK_EN;
writel(val, base + PHY_CLK_CFG);
ret = readl_poll_timeout(base + PHY_CLK_CFG, val,
(val & PHY_CLK_PLL_READY),
PCIE_POLL_DELAY, PCIE_PLL_TIMEOUT);
if (ret) {
dev_err(&phy->dev, "PHY PLL polling timeout\n");
return ret;
}
return 0;
}
static int k3_usb3phy_init(struct phy *phy)
{
struct k3_lane_group *lg = phy_get_drvdata(phy);
int ret, i;
for (i = 0; i < lg->data->lanes; i++) {
ret = k3_usb3phy_init_single(lg, lg->base + lg->data->offsets[i]);
if (ret < 0)
return ret;
}
return 0;
}
const struct phy_ops k3_usb3_phy_ops = {
.init = k3_usb3phy_init,
.owner = THIS_MODULE,
};
EXPORT_SYMBOL_GPL(k3_usb3_phy_ops);
static int k3_pcie_phy_init(struct phy *phy)
{
struct k3_lane_group *lg = phy_get_drvdata(phy);
void __iomem *phy_base = lg->base + lg->data->offsets[0];
u32 val;
int ret;
int i;
val = readl(phy_base + PHY_PLL_REG1);
val = u32_replace_bits(val, 0x2, GENMASK(15, 12));
writel(val, phy_base + PHY_PLL_REG1);
val = readl(phy_base + PHY_PLL_REG2);
val = u32_replace_bits(val, 0, BIT(21));
writel(val, phy_base + PHY_PLL_REG2);
for (i = 0; i < lg->data->lanes; i++) {
void __iomem *lane_base = lg->base + lg->data->offsets[i];
val = readl(lane_base + PCIE_RX_REG1);
val = u32_replace_bits(val, 0, 0x3);
writel(val, lane_base + PCIE_RX_REG1);
}
val = readl(phy_base + PHY_PLL_REG2);
val |= BIT(20);
writel(val, phy_base + PHY_PLL_REG2);
/* The write is needed as clock requires renegotiation */
val = FIELD_PREP(PCIE_RX_REFCLK_MODE, PCIE_RX_REFCLK_MODE_DRIVER) |
PCIE_RX_SEL_TRI_CODE |
FIELD_PREP(PCIE_RX_LEGACY, PCIE_RX_LEGACY_DEFAULT);
writel(val, phy_base + PCIE_RX_REG1);
/* pll_reg1 of lane0, disable SSC: pll[27:24] = 0 */
val = readl(phy_base + PHY_PLL_REG1);
val = u32_replace_bits(val, 0, GENMASK(27, 24));
writel(val, phy_base + PHY_PLL_REG1);
for (i = 0; i < lg->data->lanes; i++) {
void __iomem *lane_base = lg->base + lg->data->offsets[i];
/* set cfg_tx_send_dummy_data to be 1'b1 for disable dash data */
val = readl(lane_base + PHY_PU_SEL);
val = u32_replace_bits(val, 1, BIT(13));
writel(val, lane_base + PHY_PU_SEL);
/* disable en_sample_data_after_cdr_locked */
val = readl(lane_base + PHY_RESET_CFG);
val = u32_replace_bits(val, 0, BIT(6));
writel(val, lane_base + PHY_RESET_CFG);
/* Dynamic Lock */
val = readl(lane_base + PHY_MODE_CFG);
val = u32_replace_bits(val, 1, BIT(2));
writel(val, lane_base + PHY_MODE_CFG);
val = FIELD_PREP(PHY_RX_REG0_MASK, 0x10) |
FIELD_PREP(PHY_RX_REG1_MASK, 0x78) |
FIELD_PREP(PHY_RX_REG2_MASK, 0x98) |
FIELD_PREP(PHY_RX_REG3_MASK, 0xdf);
writel(val, lane_base + PHY_RX_REG_A);
val = readl(lane_base + PHY_RX_REG_B);
val &= ~PHY_RX_REGB_MASK;
val |= FIELD_PREP(PHY_RX_REG4_MASK, 0xb4) |
FIELD_PREP(PHY_RX_REG5_MASK, 0x88) |
FIELD_PREP(PHY_RX_REG6_MASK, 0x28);
writel(val, lane_base + PHY_RX_REG_B);
/* Set init done */
val = readl(lane_base + PCIE_PU_ADDR_CLK_CFG);
val = u32_replace_bits(val, 1, CFG_SW_PHY_INIT_DONE);
writel(val, lane_base + PCIE_PU_ADDR_CLK_CFG);
}
ret = readl_poll_timeout(phy_base + PCIE_PU_ADDR_CLK_CFG, val,
(val & PHY_CLK_PLL_READY), PCIE_POLL_DELAY,
PCIE_PLL_TIMEOUT);
if (ret) {
dev_err(&lg->phy->dev, "PHY PLL lock timeout\n");
return ret;
}
return 0;
}
const struct phy_ops k3_pcie_phy_ops = {
.init = k3_pcie_phy_init,
.owner = THIS_MODULE,
};
EXPORT_SYMBOL_GPL(k3_pcie_phy_ops);
/* PHY rcal init requires APB_SPARE regmap access */
#define APB_SPARE_PU_CAL 0x178
#define PU_CAL BIT(17)
#define APB_SPARE_RCAL_HSIO 0x17c
#define APB_SPARE_PU_CAL_DONE BIT(8)
#define RCAL_OVRD_PTRIM GENMASK(23, 20)
#define RCAL_OVRD_NTRIM GENMASK(27, 24)
#define RCAL_OVRD_PTRIM_EN BIT(28)
#define RCAL_OVRD_NTRIM_EN BIT(29)
#define RCAL_OVRD_STABLE_VAL BIT(30)
#define RCAL_OVRD_STABLE_EN BIT(31)
#define RCAL_OVRD_TRIM_EN (RCAL_OVRD_NTRIM_EN | RCAL_OVRD_PTRIM_EN)
#define RCAL_OVRD_TRIM_MASK (RCAL_OVRD_NTRIM | RCAL_OVRD_PTRIM)
#define PU_CAL_TIMEOUT 2000000
static DEFINE_MUTEX(calibrate_lock);
int k3_phy_calibrate(struct regmap *apb_spare)
{
unsigned int val = 0;
int ret;
guard(mutex)(&calibrate_lock);
regmap_read(apb_spare, APB_SPARE_RCAL_HSIO, &val);
if (val & APB_SPARE_PU_CAL_DONE)
return 0;
regmap_update_bits(apb_spare, APB_SPARE_PU_CAL, PU_CAL,
PU_CAL);
ret = regmap_read_poll_timeout(apb_spare, APB_SPARE_RCAL_HSIO,
val, (val & APB_SPARE_PU_CAL_DONE), PCIE_POLL_DELAY,
PU_CAL_TIMEOUT);
if (ret)
regmap_update_bits(apb_spare, APB_SPARE_RCAL_HSIO,
RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL |
RCAL_OVRD_TRIM_MASK | RCAL_OVRD_STABLE_EN,
RCAL_OVRD_TRIM_EN | RCAL_OVRD_STABLE_VAL |
FIELD_PREP(RCAL_OVRD_NTRIM, 0x6) |
FIELD_PREP(RCAL_OVRD_PTRIM, 0xa) |
RCAL_OVRD_STABLE_EN);
return 0;
}
EXPORT_SYMBOL_GPL(k3_phy_calibrate);
MODULE_DESCRIPTION("SpacemiT K3 PHY common ops");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0-only */
#ifndef _PHY_K3_COMMON_H
#define _PHY_K3_COMMON_H
#include <linux/phy/phy.h>
struct k3_phy_lane_group_data {
u32 lanes;
u8 config;
u8 mask;
u32 offsets[] __counted_by(lanes);
};
struct k3_lane_group {
const struct k3_phy_lane_group_data *data;
void __iomem *base;
struct phy *phy;
bool is_pcie;
};
extern const struct phy_ops k3_pcie_phy_ops;
extern const struct phy_ops k3_usb3_phy_ops;
int k3_phy_calibrate(struct regmap *apb_spare);
#endif