684 lines
21 KiB
C
684 lines
21 KiB
C
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
|
|
/*
|
|
* Copyright (c) 2026 Amlogic, Inc. All rights reserved.
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/ethtool.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/reset.h>
|
|
|
|
#include "phy-aml-hsgmii.h"
|
|
|
|
#define HSG_UPCTX_SEL_PDR_POST BIT(19)
|
|
|
|
#define HSG_UPCTX_BOOST_EN BIT(22)
|
|
#define HSG_UPCTX_BOOST_CTRL_MASK GENMASK(4, 0)
|
|
#define HSG_UPCTX_BOOST_CTRL_SHIFT 0
|
|
#define HSG_UPCTX_PUIO_MAIN_MASK GENMASK(7, 5)
|
|
#define HSG_UPCTX_PUIO_MAIN_SHIFT 5
|
|
#define HSG_UPCTX_PDIO_MAIN_MASK GENMASK(10, 8)
|
|
#define HSG_UPCTX_PDIO_MAIN_SHIFT 8
|
|
#define HSG_DCHA_AFE_UPCRX_MASK GENMASK(7, 4)
|
|
#define HSG_DCHA_AFE_UPCRX_SHIFT 4
|
|
|
|
#define HSG_RESET_TIME_US 100
|
|
|
|
/* Only for PHY Sync Link mode */
|
|
#define HSG_WAIT_SPEED_DETECT_TIMER 0xA2C3
|
|
|
|
#define HSG_PHY_DEBUG
|
|
#ifdef HSG_PHY_DEBUG
|
|
#define hsg_debug(fmt, args...) pr_info(fmt, ##args)
|
|
#else
|
|
#define hsg_debug(fmt, args...) pr_debug(fmt, ##args)
|
|
#endif
|
|
|
|
struct amlogic_hsgmii_phy_data {
|
|
struct regmap *regmap;
|
|
struct clk *hsg_general_clk; /* Warning: Must not disable the clk */
|
|
struct reset_control *hsg_reset; /* The Logic reset of HSGMII PHY */
|
|
struct reset_control *hsg_apb_reset; /* The Reg reset of HSGMII PHY */
|
|
void __iomem *hsg_trim_reg; /* HSGMII trim reg base */
|
|
void __iomem *u3hsg_combo_reg; /* U3 HSGMII combo reg base */
|
|
u32 combo_hsg_sel_val; /* Value to select HSGMII mode */
|
|
|
|
bool power_on;
|
|
/* Current speed of HSGMII PHY */
|
|
int speed;
|
|
/* True: PHY Sync Link, False: Fast Link */
|
|
bool psl_mode;
|
|
/* Only for Fast Link mode. If set, phy speed change will lead to Rx fsm restart */
|
|
bool rx_fsm_restart;
|
|
/* Only for Fast Link mode. If set, phy speed change will lead to pll fsm restart */
|
|
bool pll_fsm_restart;
|
|
#ifdef HSG_PHY_DEBUG
|
|
struct hsg_phy_stats hsg_stats;
|
|
#endif
|
|
};
|
|
|
|
static int aml_regmap_update_bits(struct regmap *map, unsigned int reg,
|
|
unsigned int mask, unsigned int val)
|
|
{
|
|
return regmap_update_bits_base(map, reg, mask, val, NULL, false, true);
|
|
}
|
|
|
|
static void amlogic_hsgmii_phy_init_1g(struct regmap *regmap)
|
|
{
|
|
/* TODO */
|
|
}
|
|
|
|
static void amlogic_hsgmii_phy_init_2p5g(struct regmap *regmap)
|
|
{
|
|
/* TODO */
|
|
}
|
|
|
|
static inline int
|
|
amlogic_hsgmii_phy_poll_status(struct regmap *regmap, unsigned int reg,
|
|
unsigned int bit)
|
|
{
|
|
unsigned int val;
|
|
|
|
return regmap_read_poll_timeout(regmap, reg, val,
|
|
val & bit, 1500, 750000);
|
|
}
|
|
|
|
static inline int amlogic_hsgmii_phy_reset(struct phy *phy)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
struct device *dev = phy->dev.parent;
|
|
int ret;
|
|
|
|
/* Reset hsgmii initial fsm and flow */
|
|
ret = reset_control_reset(data->hsg_reset);
|
|
if (ret) {
|
|
dev_err(dev, "failed to reset HSGMII PHY: %d\n", ret);
|
|
return ret;
|
|
}
|
|
// aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
// HSG_INIT_CTRL_RESET,
|
|
// HSG_INIT_CTRL_RESET);
|
|
|
|
udelay(HSG_RESET_TIME_US);
|
|
|
|
// aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
// HSG_INIT_CTRL_RESET,
|
|
// 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
struct device *dev = phy->dev.parent;
|
|
u32 val;
|
|
|
|
/* As of now only PHY_MODE_ETHERNET is supported */
|
|
if (mode != PHY_MODE_ETHERNET)
|
|
return -EOPNOTSUPP;
|
|
|
|
if (submode != PHY_INTERFACE_MODE_2500BASEX &&
|
|
submode != PHY_INTERFACE_MODE_SGMII)
|
|
return -EOPNOTSUPP;
|
|
|
|
/* Switch u3hsg combo to select HSGMII mode */
|
|
if (data->u3hsg_combo_reg) {
|
|
val = readl(data->u3hsg_combo_reg);
|
|
if (val != data->combo_hsg_sel_val) {
|
|
dev_dbg(dev, "Switch combo PHY to select HSGMII, 0x%x -> 0x%x\n",
|
|
val, data->combo_hsg_sel_val);
|
|
writel(data->combo_hsg_sel_val, data->u3hsg_combo_reg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_calibrate(struct phy *phy)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
struct device *dev = phy->dev.parent;
|
|
|
|
switch (data->speed) {
|
|
case SPEED_10:
|
|
case SPEED_100:
|
|
case SPEED_1000:
|
|
amlogic_hsgmii_phy_init_1g(data->regmap);
|
|
break;
|
|
case SPEED_2500:
|
|
amlogic_hsgmii_phy_init_2p5g(data->regmap);
|
|
break;
|
|
}
|
|
|
|
/* HSGMII reset */
|
|
amlogic_hsgmii_phy_reset(phy);
|
|
|
|
if (amlogic_hsgmii_phy_poll_status(data->regmap,
|
|
AML_PHY_HSG_PLL_INIT_DEBUG,
|
|
HSG_PLL_INIT_DEBUG_PLL1_LOCK)) {
|
|
dev_err(dev, "HSGMII_PLL1_LOCK timed-out");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
if (amlogic_hsgmii_phy_poll_status(data->regmap,
|
|
AML_PHY_HSG_RX_CTRL_DEBUG,
|
|
HSG_RX_CTRL_DEBUG_RX_FSM_READY)) {
|
|
dev_err(dev, "HSGMII_RX_FSM_READY timed-out");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
if (amlogic_hsgmii_phy_poll_status(data->regmap,
|
|
AML_PHY_HSG_RX_CTRL_DEBUG,
|
|
HSG_RX_CTRL_DEBUG_UPCRX_CDRLOCK_SYNC)) {
|
|
dev_err(dev, "HSGMII_UPCRX_CDRLOCK_SYNC timed-out");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
if (amlogic_hsgmii_phy_poll_status(data->regmap,
|
|
AML_PHY_HSG_RX_CTRL_DEBUG,
|
|
HSG_RX_CTRL_DEBUG_PCS_SYNC_CDC)) {
|
|
dev_err(dev, "HSGMII_PCS_SYNC_CDC timed-out");
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_trim(struct phy *phy)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
struct device *dev = phy->dev.parent;
|
|
u32 raw, val, otp_val;
|
|
|
|
if (!data->hsg_trim_reg)
|
|
return -EINVAL;
|
|
|
|
raw = readl(data->hsg_trim_reg);
|
|
|
|
if (!(raw & BIT(6))) {
|
|
dev_err(dev, "No HSGMII PHY TX impedance trim? Skip.\n");
|
|
} else {
|
|
regmap_read(data->regmap, AML_PHY_HSG_TX_CFG2, &val);
|
|
val &= (~HSG_UPCTX_PDIO_MAIN_MASK) & (~HSG_UPCTX_PUIO_MAIN_MASK);
|
|
|
|
/* set upctx_pdio_main in tx_cfg2. */
|
|
otp_val = (raw & (u32)GENMASK(2, 0)) >> 0;
|
|
val |= (otp_val << HSG_UPCTX_PDIO_MAIN_SHIFT);
|
|
/* set upctx_puio_main in tx_cfg2. */
|
|
otp_val = (raw & (u32)GENMASK(5, 3)) >> 3;
|
|
val |= (otp_val << HSG_UPCTX_PUIO_MAIN_SHIFT);
|
|
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG2, val);
|
|
}
|
|
|
|
if (!(raw & BIT(12))) {
|
|
dev_err(dev, "No HSGMII PHY TX amplitude trim? Skip.\n");
|
|
} else {
|
|
/* set upctx_boost_ctrl in tx_cfg2. */
|
|
otp_val = (raw & (u32)GENMASK(11, 7)) >> 7;
|
|
regmap_read(data->regmap, AML_PHY_HSG_TX_CFG2, &val);
|
|
val &= ~HSG_UPCTX_BOOST_CTRL_MASK;
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG2,
|
|
val | (otp_val << HSG_UPCTX_BOOST_CTRL_SHIFT));
|
|
|
|
/* set upctx_boost_en in tx_cfg0. */
|
|
regmap_read(data->regmap, AML_PHY_HSG_TX_CFG0, &val);
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG0, val | HSG_UPCTX_BOOST_EN);
|
|
/* set upctx_boost_en in tx_cfg1. */
|
|
regmap_read(data->regmap, AML_PHY_HSG_TX_CFG1, &val);
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG1, val | HSG_UPCTX_BOOST_EN);
|
|
}
|
|
|
|
if (!(raw & BIT(17))) {
|
|
dev_err(dev, "No HSGMII PHY RX impedance trim? Skip.\n");
|
|
} else {
|
|
/* set usb3_upc_reg_dxxx in rx_cfg1. */
|
|
otp_val = (raw & (u32)GENMASK(16, 13)) >> 13;
|
|
regmap_read(data->regmap, AML_PHY_HSG_RX_CFG1, &val);
|
|
val &= ~HSG_DCHA_AFE_UPCRX_MASK;
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG1,
|
|
val | (otp_val << HSG_DCHA_AFE_UPCRX_SHIFT));
|
|
}
|
|
|
|
/* Hardware fix config: disable upctx_boost and de_emph */
|
|
regmap_read(data->regmap, AML_PHY_HSG_TX_CFG0, &val);
|
|
val &= ~(HSG_UPCTX_BOOST_EN | HSG_UPCTX_SEL_PDR_POST);
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG0, val);
|
|
|
|
regmap_read(data->regmap, AML_PHY_HSG_TX_CFG1, &val);
|
|
val &= ~(HSG_UPCTX_BOOST_EN | HSG_UPCTX_SEL_PDR_POST);
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG1, val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_power_on(struct phy *phy)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
struct device *dev = phy->dev.parent;
|
|
int ret;
|
|
|
|
/* HSGMII PMA default parameters optimization */
|
|
/* RX */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG1, 0xE9943075); /* reg_dcha_afe */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG8, 0x5C144268); /* reg_dcha_dfe_h */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG9, 0x2C0014D4); /* reg_dchd_cdr_h */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG2, 0x54164268); /* reg_dcha_dfe */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG3, 0x2C001CD4); /* reg_dchd_cdr */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG4, 0x2011104F); /* reg_dchd_eq */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG0, 0x00007600); /* reg_misc */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG7, 0x0078E80A); /* upcrx_eq_byp_val */
|
|
/* TX */
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG0, 0x001D3C5A); /* 2.5G */
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG1, 0x001DFC5A); /* 1G */
|
|
/* FSM ctrl */
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_BYPASS_PLL_CTRL,
|
|
HSG_BYPASS_PLL_CTRL_DEBUG_MODE,
|
|
0);
|
|
|
|
if (data->psl_mode) { /* PHY Sync Link mode */
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
HSG_INIT_CTRL_INITIAL_MODE,
|
|
HSG_INIT_CTRL_INITIAL_MODE);
|
|
/* Config timer to wait phy speed stable */
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
HSG_INIT_CTRL_SPEED_DETECT_TIMER_MASK,
|
|
HSG_WAIT_SPEED_DETECT_TIMER <<
|
|
HSG_INIT_CTRL_SPEED_DETECT_TIMER_SHIFT);
|
|
/* Force enable wait speed stable */
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
HSG_INIT_CTRL_WAIT_SPEED_EN,
|
|
HSG_INIT_CTRL_WAIT_SPEED_EN);
|
|
/* FIXME:
|
|
* Software assert phy_link_up, otherwise, mac dma reset
|
|
* will fail and hsgmii_mode_rst_n will not release.
|
|
*/
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
HSG_INIT_CTRL_PHY_LINK_STATUS,
|
|
HSG_INIT_CTRL_PHY_LINK_STATUS);
|
|
} else { /* Fast Link mode */
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_INIT_CTRL,
|
|
HSG_INIT_CTRL_INITIAL_MODE,
|
|
0);
|
|
|
|
if (data->rx_fsm_restart)
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_RX_FSM_CTRL,
|
|
HSG_RX_FSM_CTRL_RESTART_MODE,
|
|
HSG_RX_FSM_CTRL_RESTART_MODE);
|
|
if (data->pll_fsm_restart)
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_PLL_FSM_CTRL,
|
|
HSG_PLL_FSM_CTRL_RESTART_MODE,
|
|
HSG_PLL_FSM_CTRL_RESTART_MODE);
|
|
}
|
|
|
|
ret = amlogic_hsgmii_phy_trim(phy);
|
|
if (ret)
|
|
dev_err(dev, "failed to trim HSGMII PHY: %d\n", ret);
|
|
|
|
data->power_on = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_power_off(struct phy *phy)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
|
|
/* bypass FSM ctrl */
|
|
aml_regmap_update_bits(data->regmap, AML_PHY_HSG_BYPASS_PLL_CTRL,
|
|
HSG_BYPASS_PLL_CTRL_DEBUG_MODE,
|
|
HSG_BYPASS_PLL_CTRL_DEBUG_MODE);
|
|
/* PLL */
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL0_HCSL_H_REG0, 0x80899788); /* 2.5G */
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL0_H_REG0, 0x02EE55E9);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL0_H_REG1, 0x002A4085);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL1_H_REG0, 0x40000071);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL1_H_REG1, 0x1842A320);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL1_H_REG2, 0x00000050);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL0_HCSL_L_REG0, 0x80899788); /* 1G */
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL0_L_REG0, 0x01F455E9);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL0_L_REG1, 0x0009E7E5);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL1_L_REG0, 0x40000071);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL1_L_REG1, 0x1842A320);
|
|
regmap_write(data->regmap, AML_PHY_HSG_PLL1_L_REG2, 0x00000050);
|
|
/* RX */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG4, 0x00000000);
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG9, 0x00000000); /* 2.5G */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG8, 0x00000200);
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG3, 0x00000000); /* 1G */
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG2, 0x00000200);
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG1, 0x00000075);
|
|
regmap_write(data->regmap, AML_PHY_HSG_RX_CFG0, 0x0000F600);
|
|
/* TX */
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG0, 0x001800B0); /* 2.5G */
|
|
regmap_write(data->regmap, AML_PHY_HSG_TX_CFG1, 0x001800B0); /* 1G */
|
|
|
|
data->power_on = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_set_speed(struct phy *phy, int speed)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
|
|
if (speed != data->speed)
|
|
data->speed = speed;
|
|
|
|
return amlogic_hsgmii_phy_calibrate(phy);
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_init(struct phy *phy)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = phy_get_drvdata(phy);
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(data->hsg_general_clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return amlogic_hsgmii_phy_reset(phy);
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_exit(struct phy *phy)
|
|
{
|
|
return amlogic_hsgmii_phy_reset(phy);
|
|
}
|
|
|
|
static const struct phy_ops amlogic_hsgmii_phy_ops = {
|
|
.init = amlogic_hsgmii_phy_init,
|
|
.exit = amlogic_hsgmii_phy_exit,
|
|
.reset = amlogic_hsgmii_phy_reset,
|
|
.power_on = amlogic_hsgmii_phy_power_on,
|
|
.power_off = amlogic_hsgmii_phy_power_off,
|
|
.set_speed = amlogic_hsgmii_phy_set_speed,
|
|
.calibrate = amlogic_hsgmii_phy_calibrate,
|
|
.set_mode = amlogic_hsgmii_phy_set_mode,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static const struct regmap_config amlogic_hsgmii_phy_regmap_cfg = {
|
|
.reg_bits = 32,
|
|
.val_bits = 32,
|
|
.reg_stride = 4,
|
|
.use_relaxed_mmio = true,
|
|
.disable_locking = true,
|
|
};
|
|
|
|
static void amlogic_serdes_config_dt(struct platform_device *pdev,
|
|
struct amlogic_hsgmii_phy_data *data)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
void __iomem *addr = NULL;
|
|
unsigned int reg;
|
|
|
|
data->psl_mode = of_property_read_bool(np, "amlogic,psl-mode");
|
|
data->rx_fsm_restart = of_property_read_bool(np, "amlogic,rx-fsm-restart");
|
|
data->pll_fsm_restart = of_property_read_bool(np, "amlogic,pll-fsm-restart");
|
|
if (data->psl_mode && (data->rx_fsm_restart || data->pll_fsm_restart))
|
|
dev_warn(dev, "rx_fsm_restart and pll_fsm_restart have no effect in PSL mode\n");
|
|
|
|
if (of_property_read_u32(np, "u3hsg-combo-reg", ®) == 0) {
|
|
hsg_debug("u3hsg combo reg=0x%x\n", reg);
|
|
addr = devm_ioremap(dev, reg, 4);
|
|
if (IS_ERR_OR_NULL(addr)) {
|
|
dev_err(dev, "failed to request u3hsg combo reg\n");
|
|
data->u3hsg_combo_reg = NULL;
|
|
} else {
|
|
data->u3hsg_combo_reg = addr;
|
|
if (of_property_read_u32(np,
|
|
"combo-hsg-sel-val", &data->combo_hsg_sel_val) != 0)
|
|
data->combo_hsg_sel_val = 0;
|
|
hsg_debug("combo_hsg_sel_val=0x%x\n", data->combo_hsg_sel_val);
|
|
}
|
|
} else {
|
|
data->u3hsg_combo_reg = NULL;
|
|
}
|
|
|
|
if (of_property_read_u32(np, "hsg-trim-reg", ®) == 0) {
|
|
hsg_debug("hsg trim reg=0x%x\n", reg);
|
|
addr = devm_ioremap(dev, reg, 4);
|
|
if (IS_ERR_OR_NULL(addr)) {
|
|
dev_err(dev, "failed to request hsg trim reg\n");
|
|
data->hsg_trim_reg = NULL;
|
|
} else {
|
|
data->hsg_trim_reg = addr;
|
|
}
|
|
} else {
|
|
data->hsg_trim_reg = NULL;
|
|
}
|
|
}
|
|
|
|
static int amlogic_hsgmii_phy_probe(struct platform_device *pdev)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data;
|
|
struct device *dev = &pdev->dev;
|
|
struct phy *phy;
|
|
struct phy_provider *provider;
|
|
void __iomem *base;
|
|
|
|
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
|
|
if (!data)
|
|
return -ENOMEM;
|
|
|
|
base = devm_platform_ioremap_resource(pdev, 0);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
data->hsg_general_clk = devm_clk_get(dev, "hsg_general");
|
|
if (IS_ERR(data->hsg_general_clk))
|
|
return PTR_ERR(data->hsg_general_clk);
|
|
|
|
data->hsg_reset = devm_reset_control_get_exclusive(dev, "hsg_rst");
|
|
if (IS_ERR(data->hsg_reset))
|
|
return PTR_ERR(data->hsg_reset);
|
|
|
|
data->hsg_apb_reset = devm_reset_control_get_exclusive(dev, "hsg_apb_rst");
|
|
if (IS_ERR(data->hsg_apb_reset))
|
|
return PTR_ERR(data->hsg_apb_reset);
|
|
|
|
data->regmap = devm_regmap_init_mmio(dev, base,
|
|
&amlogic_hsgmii_phy_regmap_cfg);
|
|
if (IS_ERR(data->regmap))
|
|
return PTR_ERR(data->regmap);
|
|
|
|
data->power_on = false;
|
|
data->speed = SPEED_UNKNOWN;
|
|
|
|
amlogic_serdes_config_dt(pdev, data);
|
|
|
|
phy = devm_phy_create(dev, NULL, &amlogic_hsgmii_phy_ops);
|
|
if (IS_ERR(phy))
|
|
return PTR_ERR(phy);
|
|
|
|
provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
if (IS_ERR(provider))
|
|
return PTR_ERR(provider);
|
|
|
|
phy_set_drvdata(phy, data);
|
|
dev_set_drvdata(dev, data);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HSG_PHY_DEBUG
|
|
#define HSG_PRINT_REG(var, ex) do { \
|
|
int _ret = snprintf(buf + len, PAGE_SIZE - len, \
|
|
"\n=== " #ex " (0x%03x) = 0x%08x ===\n", \
|
|
AML_PHY_HSG_##ex, (stats->var).val); \
|
|
len += _ret; \
|
|
} while (0)
|
|
|
|
#define __F(var, fld) { #fld, ((stats->var).reg).fld }
|
|
#define __F_2(val, f1, f2) __F(val, f1), __F(val, f2)
|
|
#define __F_3(val, f1, f2, f3) __F_2(val, f1, f2), __F(val, f3)
|
|
#define __F_4(val, f1, f2, f3, f4) __F_3(val, f1, f2, f3), __F(val, f4)
|
|
#define __F_5(val, f1, f2, f3, f4, f5) __F_4(val, f1, f2, f3, f4), __F(val, f5)
|
|
#define __F_6(val, f1, f2, f3, f4, f5, f6) __F_5(val, f1, f2, f3, f4, f5), __F(val, f6)
|
|
#define __F_7(val, f1, f2, f3, f4, f5, f6, f7) __F_6(val, f1, f2, f3, f4, f5, f6), __F(val, f7)
|
|
|
|
#define HSG_PRINT_FIELDS(...) do { \
|
|
const struct { const char *name; u32 val; } _f[] = { __VA_ARGS__ }; \
|
|
int _ret; \
|
|
int _i; \
|
|
for (_i = 0; _i < ARRAY_SIZE(_f); _i++) { \
|
|
_ret = snprintf(buf + len, PAGE_SIZE - len, \
|
|
" %-24s = %x\n", _f[_i].name, _f[_i].val); \
|
|
len += _ret; \
|
|
} \
|
|
} while (0)
|
|
|
|
static void hsg_update_debug_stats(struct amlogic_hsgmii_phy_data *data)
|
|
{
|
|
struct hsg_phy_stats *stats = &data->hsg_stats;
|
|
const struct {
|
|
unsigned int reg;
|
|
u32 *val;
|
|
} hsg_regs[] = {
|
|
{ AML_PHY_HSG_RX_CFG1, &stats->rx_cfg1 },
|
|
{ AML_PHY_HSG_TX_CFG0, &stats->tx_cfg0 },
|
|
{ AML_PHY_HSG_TX_CFG1, &stats->tx_cfg1 },
|
|
{ AML_PHY_HSG_BYPASS_PLL_CTRL, &stats->bypass_pll_ctrl },
|
|
|
|
{ AML_PHY_HSG_TX_CFG2, &stats->tx_cfg2.val },
|
|
{ AML_PHY_HSG_INIT_CTRL, &stats->init_ctrl.val },
|
|
{ AML_PHY_HSG_MAC_STATUS, &stats->mac_status.val },
|
|
{ AML_PHY_HSG_INIT_DEBUG, &stats->init_dbg.val },
|
|
{ AML_PHY_HSG_PLL_INIT_DEBUG, &stats->pll_dbg.val },
|
|
{ AML_PHY_HSG_RX_CTRL_DEBUG, &stats->rx_ctrl_dbg.val },
|
|
{ AML_PHY_HSG_RX_DEBUG, &stats->rx_dbg.val },
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(hsg_regs); i++)
|
|
regmap_read(data->regmap, hsg_regs[i].reg, hsg_regs[i].val);
|
|
}
|
|
|
|
static ssize_t status_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
struct amlogic_hsgmii_phy_data *data = dev_get_drvdata(dev);
|
|
struct hsg_phy_stats *stats = &data->hsg_stats;
|
|
|
|
ssize_t len = 0;
|
|
u32 val;
|
|
|
|
hsg_update_debug_stats(data);
|
|
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "=== Global Config ===\n");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "Power Status: %s\n",
|
|
data->power_on ? "ON" : "OFF");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "Speed: %d Mbps\n", data->speed);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "PSL Mode: %s\n",
|
|
data->psl_mode ? "PHY Sync" : "Fast Link");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "Rx Fsm Restart Mode: %s\n",
|
|
data->rx_fsm_restart ? "Y" : "N");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "PLL Fsm Restart Mode: %s\n",
|
|
data->pll_fsm_restart ? "Y" : "N");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "PLL Fsm Control: %s\n",
|
|
(stats->bypass_pll_ctrl & HSG_BYPASS_PLL_CTRL_DEBUG_MODE) ?
|
|
"Disable" : "Enable");
|
|
if (data->u3hsg_combo_reg) {
|
|
val = readl(data->u3hsg_combo_reg);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "Combo Select HSG: %s\n",
|
|
(val == data->combo_hsg_sel_val) ? "Y" : "N <- error");
|
|
}
|
|
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "=== Trim Config ===\n");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "RX impedance: %x\n",
|
|
(u32)(stats->rx_cfg1 & HSG_DCHA_AFE_UPCRX_MASK) >>
|
|
HSG_DCHA_AFE_UPCRX_SHIFT);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "upctx_boost_en(1G mode): %s\n",
|
|
(stats->tx_cfg1 & HSG_UPCTX_BOOST_EN) ? "Y" : "N");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "upctx_boost_en(2.5G mode): %s\n",
|
|
(stats->tx_cfg0 & HSG_UPCTX_BOOST_EN) ? "Y" : "N");
|
|
HSG_PRINT_FIELDS(__F_3(tx_cfg2,
|
|
upctx_pdio_main, upctx_puio_main, upctx_boost_ctrl),
|
|
);
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "upctx_sel_pdr_post(1G mode): %s\n",
|
|
(stats->tx_cfg1 & HSG_UPCTX_SEL_PDR_POST) ? "Y" : "N");
|
|
len += snprintf(buf + len, PAGE_SIZE - len, "upctx_sel_pdr_post(2.5G mode): %s\n",
|
|
(stats->tx_cfg0 & HSG_UPCTX_SEL_PDR_POST) ? "Y" : "N");
|
|
|
|
/* INIT CTRL */
|
|
HSG_PRINT_REG(init_ctrl, INIT_CTRL);
|
|
HSG_PRINT_FIELDS(__F_7(init_ctrl,
|
|
init_reset, phy_link_status, hsgmii_speed_en, hsgmii_speed,
|
|
initial_mode, wait_speed_en, speed_detect_timer),
|
|
);
|
|
|
|
/* MAC Status */
|
|
HSG_PRINT_REG(mac_status, MAC_STATUS);
|
|
HSG_PRINT_FIELDS(__F_6(mac_status,
|
|
rx_fsm_ready, pcs_acquired_sync, sgmii_link_spd,
|
|
pcs_ewrap, pcs_en_cdet, pcs_lck_ref),
|
|
);
|
|
|
|
/* INIT Debug */
|
|
HSG_PRINT_REG(init_dbg, INIT_DEBUG);
|
|
HSG_PRINT_FIELDS(__F_6(init_dbg,
|
|
hsgmii_mode_rst_n, hsgmii_clk_en, phy_link_status_final,
|
|
hsgmii_speed, phy_speed_final, hsi_cstate),
|
|
);
|
|
|
|
/* PLL Debug */
|
|
HSG_PRINT_REG(pll_dbg, PLL_INIT_DEBUG);
|
|
HSG_PRINT_FIELDS(__F_5(pll_dbg,
|
|
pll0_lock, pll1_lock, pll0_fail_cnt,
|
|
pll1_fail_cnt, pll_cstate),
|
|
);
|
|
|
|
/* RX Ctrl Debug */
|
|
HSG_PRINT_REG(rx_ctrl_dbg, RX_CTRL_DEBUG);
|
|
HSG_PRINT_FIELDS(__F_5(rx_ctrl_dbg,
|
|
pcs_acquired_sync_cdc, upcrx_cdlock_sync,
|
|
rx_fsm_ready, lock_fail_cnt, rx_fsm_cstate),
|
|
);
|
|
|
|
/* RX Debug */
|
|
HSG_PRINT_REG(rx_dbg, RX_DEBUG);
|
|
HSG_PRINT_FIELDS(__F_2(rx_dbg,
|
|
upcrx_state, upcrx_dbg_eq),
|
|
);
|
|
|
|
return len;
|
|
}
|
|
|
|
static DEVICE_ATTR_RO(status);
|
|
|
|
static struct attribute *hsgmii_phy_dev_attrs[] = {
|
|
&dev_attr_status.attr,
|
|
NULL,
|
|
};
|
|
ATTRIBUTE_GROUPS(hsgmii_phy_dev);
|
|
#endif
|
|
|
|
static const struct of_device_id amlogic_hsgmii_phy_of_match[] = {
|
|
{ .compatible = "amlogic,hsgmii-phy" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, amlogic_hsgmii_phy_of_match);
|
|
|
|
static struct platform_driver amlogic_hsgmii_phy_driver = {
|
|
.probe = amlogic_hsgmii_phy_probe,
|
|
.driver = {
|
|
.name = "amlogic-hsgmii-phy",
|
|
.of_match_table = amlogic_hsgmii_phy_of_match,
|
|
#ifdef HSG_PHY_DEBUG
|
|
.dev_groups = hsgmii_phy_dev_groups,
|
|
#endif
|
|
},
|
|
};
|
|
|
|
module_platform_driver(amlogic_hsgmii_phy_driver);
|
|
|
|
MODULE_DESCRIPTION("Amlogic HSGMII PHY driver");
|
|
MODULE_LICENSE("GPL");
|