mirror of
https://github.com/openwrt/openwrt.git
synced 2026-10-14 04:28:41 +04:00
qualcommax: add UNIPHY PCS driver
PCS driver for UNIPHY SerDes blocks on Qualcomm IPQ platforms. Signed-off-by: John Crispin <john@phrozen.org> [ rework Makefile for external repository, dependency ] Signed-off-by: Christian Marangi <ansuelsmth@gmail.com> [ make it in-tree under qualcommax ] Link: https://github.com/openwrt/openwrt/pull/22381 Signed-off-by: Robert Marko <robimarko@gmail.com>
This commit is contained in:
1 parent
d1098d5d51
commit
05c180511d
5 files changed
+1397
No files matched your search
@@ -0,0 +1,1000 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Qualcomm IPQ6018 UNIPHY PCS driver
|
||||
*
|
||||
* Copyright (c) 2025 The OpenWrt Project
|
||||
*
|
||||
* Standalone PCS driver for the UNIPHY SerDes blocks in IPQ6018 SoCs.
|
||||
* Each UNIPHY instance provides up to 5 SGMII channels (PSGMII mode)
|
||||
* or a single channel for SGMII/USXGMII.
|
||||
*/
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk/clk-conf.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/pcs/pcs-provider.h>
|
||||
#include <linux/pcs/pcs-qca-uniphy.h>
|
||||
#include <linux/phylink.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <dt-bindings/net/qca-uniphy.h>
|
||||
|
||||
static const struct qca_uniphy_match_data ipq5018_data = {
|
||||
.uniphy_type = UNIPHY_IPQ5018,
|
||||
.ref_clk_enable = true,
|
||||
};
|
||||
|
||||
static const struct qca_uniphy_match_data ipq6018_data = {
|
||||
.uniphy_type = UNIPHY_IPQ6018,
|
||||
.ref_clk_enable = false,
|
||||
};
|
||||
|
||||
static const struct qca_uniphy_match_data ipq8074_data = {
|
||||
.uniphy_type = UNIPHY_IPQ8074,
|
||||
.ref_clk_enable = false,
|
||||
};
|
||||
|
||||
static unsigned long
|
||||
qca_uniphy_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
{
|
||||
struct qca_uniphy_clk *uclk =
|
||||
container_of(hw, struct qca_uniphy_clk, hw);
|
||||
struct qca_uniphy *uniphy = uclk->uniphy;
|
||||
u32 val;
|
||||
|
||||
/*
|
||||
* UNIPHY switch reference clock based on the configured
|
||||
* PHY mode.
|
||||
*/
|
||||
regmap_read(uniphy->regmap, UNIPHY_MODE_CTRL, &val);
|
||||
if (val & UNIPHY_SGMII_MODE ||
|
||||
val & UNIPHY_CH0_PSGMII_QSGMII ||
|
||||
val & UNIPHY_CH0_QSGMII_SGMII)
|
||||
return 125000000;
|
||||
else if (val & UNIPHY_SGPLUS_MODE ||
|
||||
val & UNIPHY_XPCS_MODE)
|
||||
return 312500000;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct clk_ops qca_uniphy_clk_ops = {
|
||||
.recalc_rate = qca_uniphy_clk_recalc_rate,
|
||||
};
|
||||
|
||||
static int qca_uniphy_clk_register(struct qca_uniphy *uniphy,
|
||||
struct qca_uniphy_clk *uclk,
|
||||
const char *name)
|
||||
{
|
||||
struct clk_init_data init = {
|
||||
.name = name,
|
||||
.ops = &qca_uniphy_clk_ops,
|
||||
/* always derive rate from the UNIPHY register */
|
||||
.flags = CLK_GET_RATE_NOCACHE,
|
||||
};
|
||||
|
||||
uclk->hw.init = &init;
|
||||
uclk->uniphy = uniphy;
|
||||
|
||||
return devm_clk_hw_register(uniphy->dev, &uclk->hw);
|
||||
}
|
||||
|
||||
static int
|
||||
qca_uniphy_refclk_enable(struct clk_hw *hw)
|
||||
{
|
||||
struct qca_uniphy_clk *uclk =
|
||||
container_of(hw, struct qca_uniphy_clk, hw);
|
||||
struct qca_uniphy *uniphy = uclk->uniphy;
|
||||
|
||||
switch (uniphy->data->uniphy_type) {
|
||||
case UNIPHY_IPQ5018:
|
||||
if (!clk_hw_is_enabled(&uclk->hw)) {
|
||||
regmap_update_bits(uniphy->regmap, IPQ5018_UNIPHY_REFCLK,
|
||||
IPQ5018_UNIPHY_REFCLK_EN |
|
||||
IPQ5018_UNIPHY_REFCLK_DS,
|
||||
IPQ5018_UNIPHY_REFCLK_EN |
|
||||
FIELD_PREP(IPQ5018_UNIPHY_REFCLK_DS, 1));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qca_uniphy_refclk_is_enabled(struct clk_hw *hw)
|
||||
{
|
||||
struct qca_uniphy_clk *uclk =
|
||||
container_of(hw, struct qca_uniphy_clk, hw);
|
||||
struct qca_uniphy *uniphy = uclk->uniphy;
|
||||
u32 val;
|
||||
|
||||
switch (uniphy->data->uniphy_type) {
|
||||
case UNIPHY_IPQ5018:
|
||||
regmap_read(uniphy->regmap, IPQ5018_UNIPHY_REFCLK, &val);
|
||||
return !!(val & IPQ5018_UNIPHY_REFCLK_EN);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qca_uniphy_refclk_determine_rate(struct clk_hw *hw,
|
||||
struct clk_rate_request *req)
|
||||
{
|
||||
struct qca_uniphy_clk *uclk =
|
||||
container_of(hw, struct qca_uniphy_clk, hw);
|
||||
struct qca_uniphy *uniphy = uclk->uniphy;
|
||||
|
||||
switch (uniphy->data->uniphy_type) {
|
||||
case UNIPHY_IPQ5018:
|
||||
switch (req->rate) {
|
||||
case UNIPHY_REFCLK_25MHZ:
|
||||
case UNIPHY_REFCLK_50MHZ:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned long
|
||||
qca_uniphy_refclk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate)
|
||||
{
|
||||
struct qca_uniphy_clk *uclk =
|
||||
container_of(hw, struct qca_uniphy_clk, hw);
|
||||
struct qca_uniphy *uniphy = uclk->uniphy;
|
||||
u32 val;
|
||||
|
||||
switch (uniphy->data->uniphy_type) {
|
||||
case UNIPHY_IPQ5018:
|
||||
regmap_read(uniphy->regmap, IPQ5018_UNIPHY_REFCLK, &val);
|
||||
if (val & IPQ5018_UNIPHY_REFCLK_DIV)
|
||||
return UNIPHY_REFCLK_25MHZ;
|
||||
else
|
||||
return UNIPHY_REFCLK_50MHZ;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
qca_uniphy_refclk_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct qca_uniphy_clk *refclk =
|
||||
container_of(hw, struct qca_uniphy_clk, hw);
|
||||
struct qca_uniphy *uniphy = refclk->uniphy;
|
||||
u32 val = 0;
|
||||
|
||||
switch (uniphy->data->uniphy_type) {
|
||||
case UNIPHY_IPQ5018:
|
||||
if (parent_rate != UNIPHY_REFCLK_50MHZ)
|
||||
return -EINVAL;
|
||||
|
||||
if (rate == UNIPHY_REFCLK_50MHZ)
|
||||
val = FIELD_PREP(IPQ5018_UNIPHY_REFCLK_DIV, 0x0);
|
||||
else if (rate == UNIPHY_REFCLK_25MHZ)
|
||||
val = FIELD_PREP(IPQ5018_UNIPHY_REFCLK_DIV, 0x1);
|
||||
|
||||
regmap_update_bits(uniphy->regmap, IPQ5018_UNIPHY_REFCLK,
|
||||
IPQ5018_UNIPHY_REFCLK_DIV, val);
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct clk_ops qca_uniphy_refclk_ops = {
|
||||
.enable = qca_uniphy_refclk_enable,
|
||||
.determine_rate = qca_uniphy_refclk_determine_rate,
|
||||
.recalc_rate = qca_uniphy_refclk_recalc_rate,
|
||||
.set_rate = qca_uniphy_refclk_set_rate,
|
||||
};
|
||||
|
||||
static int qca_uniphy_refclk_register(struct qca_uniphy *uniphy,
|
||||
const char *name) {
|
||||
struct clk_parent_data pdata = { .fw_name = "ref" };
|
||||
struct clk_init_data init = {};
|
||||
|
||||
init.name = name,
|
||||
init.parent_data = &pdata;
|
||||
init.num_parents = 1;
|
||||
init.ops = &qca_uniphy_refclk_ops,
|
||||
/* always derive rate from the UNIPHY register */
|
||||
init.flags = CLK_GET_RATE_NOCACHE,
|
||||
|
||||
uniphy->ref_clk.hw.init = &init;
|
||||
uniphy->ref_clk.uniphy = uniphy;
|
||||
|
||||
return devm_clk_hw_register(uniphy->dev, &uniphy->ref_clk.hw);
|
||||
}
|
||||
|
||||
static int qca_uniphy_register_clks(struct qca_uniphy *uniphy) {
|
||||
struct clk_hw_onecell_data *hw_data;
|
||||
struct device *dev = uniphy->dev;
|
||||
const char *name;
|
||||
int num_clks;
|
||||
int ret;
|
||||
|
||||
num_clks = (uniphy->data->ref_clk_enable) ? 3 : 2;
|
||||
hw_data = devm_kzalloc(dev, struct_size(hw_data, hws, num_clks),
|
||||
GFP_KERNEL);
|
||||
if (!hw_data)
|
||||
return -ENOMEM;
|
||||
|
||||
if (of_property_read_string_index(dev->of_node, "clock-output-names",
|
||||
UNIPHY_CLK_RX, &name))
|
||||
return -ENODEV;
|
||||
|
||||
ret = qca_uniphy_clk_register(uniphy, &uniphy->rx_clk, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
hw_data->hws[UNIPHY_CLK_RX] = &uniphy->rx_clk.hw;
|
||||
|
||||
if (of_property_read_string_index(dev->of_node, "clock-output-names",
|
||||
UNIPHY_CLK_TX, &name))
|
||||
return -ENODEV;
|
||||
|
||||
ret = qca_uniphy_clk_register(uniphy, &uniphy->tx_clk, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
hw_data->hws[UNIPHY_CLK_TX] = &uniphy->tx_clk.hw;
|
||||
|
||||
if (uniphy->data->ref_clk_enable) {
|
||||
if (of_property_read_string_index(dev->of_node,
|
||||
"clock-output-names",
|
||||
UNIPHY_CLK_REF, &name))
|
||||
return -ENODEV;
|
||||
|
||||
ret = qca_uniphy_refclk_register(uniphy, name);
|
||||
if (ret)
|
||||
return ret;
|
||||
hw_data->hws[UNIPHY_CLK_REF] = &uniphy->ref_clk.hw;
|
||||
}
|
||||
|
||||
hw_data->num = num_clks;
|
||||
|
||||
return devm_of_clk_add_hw_provider(dev, of_clk_hw_onecell_get, hw_data);
|
||||
}
|
||||
|
||||
static unsigned int
|
||||
qca_uniphy_pcs_inband_caps(struct phylink_pcs *pcs,
|
||||
phy_interface_t interface)
|
||||
{
|
||||
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_get_state_sgmii(struct qca_uniphy *uniphy,
|
||||
int channel,
|
||||
struct phylink_link_state *state)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
regmap_read(uniphy->regmap,
|
||||
UNIPHY_CH_STS(channel),
|
||||
&val);
|
||||
|
||||
state->link = !!(val & UNIPHY_CH_STS_LINK);
|
||||
if (!state->link)
|
||||
return;
|
||||
|
||||
state->duplex = (val & UNIPHY_CH_STS_DUPLEX) ? DUPLEX_FULL : DUPLEX_HALF;
|
||||
|
||||
switch (FIELD_GET(UNIPHY_CH_STS_SPEED_MODE, val)) {
|
||||
case 0:
|
||||
state->speed = SPEED_10;
|
||||
break;
|
||||
case 1:
|
||||
state->speed = SPEED_100;
|
||||
break;
|
||||
case 2:
|
||||
state->speed = SPEED_1000;
|
||||
break;
|
||||
default:
|
||||
state->link = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state->pause = 0;
|
||||
if (val & UNIPHY_CH_STS_RX_PAUSE)
|
||||
state->pause |= MLO_PAUSE_RX;
|
||||
if (val & UNIPHY_CH_STS_TX_PAUSE)
|
||||
state->pause |= MLO_PAUSE_TX;
|
||||
|
||||
state->an_complete = state->link;
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_get_state_hsgmii(struct qca_uniphy *uniphy,
|
||||
int channel,
|
||||
struct phylink_link_state *state)
|
||||
{
|
||||
u32 val;
|
||||
|
||||
regmap_read(uniphy->regmap,
|
||||
UNIPHY_CH_STS(channel),
|
||||
&val);
|
||||
|
||||
state->link = !!(val & UNIPHY_CH_STS_LINK);
|
||||
if (!state->link)
|
||||
return;
|
||||
|
||||
state->speed = SPEED_2500;
|
||||
state->duplex = DUPLEX_FULL;
|
||||
|
||||
state->pause = 0;
|
||||
if (val & UNIPHY_CH_STS_RX_PAUSE)
|
||||
state->pause |= MLO_PAUSE_RX;
|
||||
if (val & UNIPHY_CH_STS_TX_PAUSE)
|
||||
state->pause |= MLO_PAUSE_TX;
|
||||
|
||||
state->an_complete = state->link;
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_get_state_usxgmii(struct qca_uniphy *uniphy,
|
||||
struct phylink_link_state *state)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(uniphy->regmap, XPCS_MII_AN_INTR_STS, &val);
|
||||
if (ret) {
|
||||
state->link = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
state->link = !!(val & XPCS_USXG_AN_LINK_STS);
|
||||
|
||||
if (!state->link)
|
||||
return;
|
||||
|
||||
switch (FIELD_GET(XPCS_USXG_AN_SPEED_MASK, val)) {
|
||||
case XPCS_USXG_AN_SPEED_10000:
|
||||
state->speed = SPEED_10000;
|
||||
break;
|
||||
case XPCS_USXG_AN_SPEED_5000:
|
||||
state->speed = SPEED_5000;
|
||||
break;
|
||||
case XPCS_USXG_AN_SPEED_2500:
|
||||
state->speed = SPEED_2500;
|
||||
break;
|
||||
case XPCS_USXG_AN_SPEED_1000:
|
||||
state->speed = SPEED_1000;
|
||||
break;
|
||||
case XPCS_USXG_AN_SPEED_100:
|
||||
state->speed = SPEED_100;
|
||||
break;
|
||||
case XPCS_USXG_AN_SPEED_10:
|
||||
state->speed = SPEED_10;
|
||||
break;
|
||||
default:
|
||||
state->link = false;
|
||||
return;
|
||||
}
|
||||
|
||||
state->duplex = DUPLEX_FULL;
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_get_state_10base_r(struct qca_uniphy *uniphy,
|
||||
struct phylink_link_state *state)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(uniphy->regmap, XPCS_KR_STS1, &val);
|
||||
if (ret) {
|
||||
state->link = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
state->link = !!(val & XPCS_KR_STS1_PLU);
|
||||
|
||||
if (!state->link)
|
||||
return;
|
||||
|
||||
state->speed = SPEED_10000;
|
||||
state->duplex = DUPLEX_FULL;
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_get_state(struct phylink_pcs *pcs,
|
||||
struct phylink_link_state *state)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
|
||||
switch (state->interface) {
|
||||
case PHY_INTERFACE_MODE_SGMII:
|
||||
case PHY_INTERFACE_MODE_QSGMII:
|
||||
case PHY_INTERFACE_MODE_PSGMII:
|
||||
case PHY_INTERFACE_MODE_1000BASEX:
|
||||
qca_uniphy_pcs_get_state_sgmii(uniphy, upcs->channel,
|
||||
state);
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_2500BASEX:
|
||||
qca_uniphy_pcs_get_state_hsgmii(uniphy, upcs->channel,
|
||||
state);
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_USXGMII:
|
||||
qca_uniphy_pcs_get_state_usxgmii(uniphy, state);
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_10GBASER:
|
||||
qca_uniphy_pcs_get_state_10base_r(uniphy, state);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int qca_uniphy_pcs_config_mode(struct phylink_pcs *pcs,
|
||||
unsigned int neg_mode,
|
||||
phy_interface_t interface,
|
||||
const unsigned long *advertising,
|
||||
bool permit_pause_to_mac)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
u32 misc2_phy_mode, mode_ctrl, val;
|
||||
int ret;
|
||||
|
||||
dev_dbg(uniphy->dev, "Configuring PCS: chan=%d, interface=%s, neg_mode=0x%x\n",
|
||||
upcs->channel, phy_modes(interface), neg_mode);
|
||||
|
||||
switch (interface) {
|
||||
case PHY_INTERFACE_MODE_1000BASEX:
|
||||
misc2_phy_mode = UNIPHY_MISC2_SGMII;
|
||||
mode_ctrl = UNIPHY_SGMII_MODE;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_SGMII:
|
||||
misc2_phy_mode = UNIPHY_MISC2_SGMII;
|
||||
mode_ctrl = UNIPHY_SGMII_MODE;
|
||||
mode_ctrl |= FIELD_PREP(UNIPHY_CH0_MODE_CTRL_25M, UNIPHY_CH0_MODE_MAC);
|
||||
mode_ctrl |= UNIPHY_AUTONEG_MODE_ATH;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_QSGMII:
|
||||
mode_ctrl = UNIPHY_CH0_QSGMII_SGMII;
|
||||
misc2_phy_mode = UNIPHY_MISC2_SGMII;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_PSGMII:
|
||||
mode_ctrl = UNIPHY_CH0_PSGMII_QSGMII;
|
||||
misc2_phy_mode = 0;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_USXGMII:
|
||||
mode_ctrl = UNIPHY_XPCS_MODE;
|
||||
misc2_phy_mode = UNIPHY_MISC2_USXGMII;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_10GBASER:
|
||||
mode_ctrl = UNIPHY_XPCS_MODE;
|
||||
misc2_phy_mode = 0;
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_2500BASEX:
|
||||
misc2_phy_mode = UNIPHY_MISC2_SGMIIPLUS;
|
||||
mode_ctrl = UNIPHY_SGPLUS_MODE;
|
||||
mode_ctrl |= FIELD_PREP(UNIPHY_CH0_MODE_CTRL_25M, UNIPHY_CH0_MODE_MAC);
|
||||
mode_ctrl |= UNIPHY_AUTONEG_MODE_ATH;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* The clock could have been enabled by the bootloader.
|
||||
* Increase enable count (>0) CCF can properly account for clock state.
|
||||
*/
|
||||
if (uniphy->data->ref_clk_enable) {
|
||||
if (!clk_hw_is_enabled(&uniphy->ref_clk.hw) &&
|
||||
qca_uniphy_refclk_is_enabled(&uniphy->ref_clk.hw))
|
||||
clk_prepare_enable(uniphy->ref_clk.hw.clk);
|
||||
}
|
||||
|
||||
/* First update misc2 PHY mode... */
|
||||
regmap_update_bits(uniphy->regmap, UNIPHY_MISC2_PHY_MODE,
|
||||
UNIPHY_MISC2_PHY_MODE_MASK, misc2_phy_mode);
|
||||
|
||||
/* ...and reset Analog */
|
||||
regmap_clear_bits(uniphy->regmap, UNIPHY_PLL_POWER_ON_AND_RESET,
|
||||
UNIPHY_PLL_RESET_ANALOG);
|
||||
msleep(100);
|
||||
regmap_set_bits(uniphy->regmap, UNIPHY_PLL_POWER_ON_AND_RESET,
|
||||
UNIPHY_PLL_RESET_ANALOG);
|
||||
msleep(100);
|
||||
|
||||
/* Second assert XPCS... */
|
||||
if (uniphy->rst_xpcs)
|
||||
reset_control_assert(uniphy->rst_xpcs);
|
||||
|
||||
/* ...and disable PHY clock */
|
||||
clk_disable(uniphy->clks[port_rx_clk_idx(upcs)].clk);
|
||||
clk_disable(uniphy->clks[port_tx_clk_idx(upcs)].clk);
|
||||
|
||||
/* TODO: Fix for IPQ6018 and IPQ8074 */
|
||||
if (uniphy->data->uniphy_type == UNIPHY_IPQ5018) {
|
||||
//set force mode for fixed link
|
||||
if (neg_mode == PHYLINK_PCS_NEG_OUTBAND && !phylink_expects_phy(pcs->phylink)) {
|
||||
regmap_set_bits(uniphy->regmap,
|
||||
UNIPHY_CH_CTRL(upcs->channel),
|
||||
UNIPHY_CH_FORCE_MODE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Third update the mode ctrl... */
|
||||
regmap_update_bits(uniphy->regmap, UNIPHY_MODE_CTRL,
|
||||
UNIPHY_MODE_SEL_MASK | UNIPHY_CH0_MODE_CTRL_25M |
|
||||
UNIPHY_AUTONEG_MODE_ATH,
|
||||
mode_ctrl);
|
||||
|
||||
/*
|
||||
* ...and execute soft reset...
|
||||
*
|
||||
* (soft reset is XPCS + all the UNIPHY PHY port reset,
|
||||
* XPCS gets indirectly deassert by the soft reset deassert.
|
||||
* It seems that resetting all at once is mandatory as from
|
||||
* lots of testing it has been verified that operating
|
||||
* on the single reset is problematic)
|
||||
*/
|
||||
reset_control_assert(uniphy->rst_soft);
|
||||
msleep(100);
|
||||
reset_control_deassert(uniphy->rst_soft);
|
||||
|
||||
/* ...and wait for calibration */
|
||||
ret = regmap_read_poll_timeout(uniphy->regmap, UNIPHY_OFFSET_CALIB_4,
|
||||
val, val & UNIPHY_CALIBRATION_DONE,
|
||||
UNIPHY_CALIBRATION_POLL_US,
|
||||
UNIPHY_CALIBRATION_TIMEOUT_US);
|
||||
if (ret) {
|
||||
dev_err(uniphy->dev, "PCS calibration timeout\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Trigger UNIPHY ref clock to recal rate */
|
||||
clk_hw_recalc_rate(&uniphy->rx_clk.hw);
|
||||
clk_hw_recalc_rate(&uniphy->tx_clk.hw);
|
||||
|
||||
/* As last step enable PHY clock... */
|
||||
clk_enable(uniphy->clks[port_rx_clk_idx(upcs)].clk);
|
||||
clk_enable(uniphy->clks[port_tx_clk_idx(upcs)].clk);
|
||||
|
||||
if (interface == PHY_INTERFACE_MODE_USXGMII ||
|
||||
interface == PHY_INTERFACE_MODE_10GBASER)
|
||||
reset_control_deassert(uniphy->rst_xpcs);
|
||||
|
||||
if (!uniphy->data->ref_clk_enable)
|
||||
return 0;
|
||||
|
||||
/* Trigger UNIPHY ref output clock to recalc rate */
|
||||
clk_hw_recalc_rate(&uniphy->ref_clk.hw);
|
||||
/* Trigger assigned-clock-rates in DT to be applied */
|
||||
of_clk_set_defaults(uniphy->dev->of_node, true);
|
||||
if (!qca_uniphy_refclk_is_enabled(&uniphy->ref_clk.hw))
|
||||
qca_uniphy_refclk_enable(&uniphy->ref_clk.hw);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qca_uniphy_pcs_config_usxgmii(struct phylink_pcs *pcs,
|
||||
unsigned int neg_mode,
|
||||
phy_interface_t interface,
|
||||
const unsigned long *advertising,
|
||||
bool permit_pause_to_mac)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
int ret;
|
||||
|
||||
ret = qca_uniphy_pcs_config_mode(pcs, neg_mode, interface, advertising, permit_pause_to_mac);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(uniphy->regmap, XPCS_DIG_CTRL,
|
||||
XPCS_USXG_EN,
|
||||
interface == PHY_INTERFACE_MODE_USXGMII ? XPCS_USXG_EN : 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = regmap_update_bits(uniphy->regmap, XPCS_MII_AN_CTRL,
|
||||
XPCS_MII_AN_8BIT,
|
||||
interface == PHY_INTERFACE_MODE_USXGMII ? XPCS_MII_AN_8BIT : 0);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return regmap_update_bits(uniphy->regmap, XPCS_MII_CTRL,
|
||||
XPCS_MII_AN_EN,
|
||||
interface == PHY_INTERFACE_MODE_USXGMII ? XPCS_MII_AN_EN : 0);
|
||||
}
|
||||
|
||||
static int qca_uniphy_pcs_config(struct phylink_pcs *pcs,
|
||||
unsigned int neg_mode,
|
||||
phy_interface_t interface,
|
||||
const unsigned long *advertising,
|
||||
bool permit_pause_to_mac)
|
||||
{
|
||||
switch (interface) {
|
||||
case PHY_INTERFACE_MODE_SGMII:
|
||||
case PHY_INTERFACE_MODE_QSGMII:
|
||||
case PHY_INTERFACE_MODE_PSGMII:
|
||||
case PHY_INTERFACE_MODE_1000BASEX:
|
||||
case PHY_INTERFACE_MODE_2500BASEX:
|
||||
return qca_uniphy_pcs_config_mode(pcs, neg_mode, interface, advertising, permit_pause_to_mac);
|
||||
case PHY_INTERFACE_MODE_USXGMII:
|
||||
case PHY_INTERFACE_MODE_10GBASER:
|
||||
return qca_uniphy_pcs_config_usxgmii(pcs, neg_mode, interface, advertising, permit_pause_to_mac);
|
||||
default:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
}
|
||||
|
||||
static int uniphy_link_up_sgmii(struct phylink_pcs *pcs,
|
||||
phy_interface_t interface,
|
||||
int speed)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
unsigned long uniphy_rate;
|
||||
int ret;
|
||||
|
||||
switch (interface) {
|
||||
case PHY_INTERFACE_MODE_SGMII:
|
||||
case PHY_INTERFACE_MODE_QSGMII:
|
||||
case PHY_INTERFACE_MODE_PSGMII:
|
||||
switch (speed) {
|
||||
case SPEED_10:
|
||||
uniphy_rate = 2500000;
|
||||
break;
|
||||
case SPEED_100:
|
||||
uniphy_rate = 25000000;
|
||||
break;
|
||||
case SPEED_1000:
|
||||
uniphy_rate = 125000000;
|
||||
break;
|
||||
default:
|
||||
dev_err(uniphy->dev, "Invalid SGMII speed %d\n", speed);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_1000BASEX:
|
||||
switch (speed) {
|
||||
case SPEED_1000:
|
||||
uniphy_rate = 125000000;
|
||||
break;
|
||||
default:
|
||||
dev_err(uniphy->dev, "Invalid 1000BaseX speed %d\n", speed);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_2500BASEX:
|
||||
switch (speed) {
|
||||
case SPEED_2500:
|
||||
uniphy_rate = 312500000;
|
||||
break;
|
||||
default:
|
||||
dev_err(uniphy->dev, "Invalid 2500BaseX speed %d\n", speed);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk_set_rate(uniphy->clks[port_rx_clk_idx(upcs)].clk, uniphy_rate);
|
||||
clk_set_rate(uniphy->clks[port_tx_clk_idx(upcs)].clk, uniphy_rate);
|
||||
|
||||
ret = regmap_clear_bits(uniphy->regmap, UNIPHY_CH_CTRL(upcs->channel),
|
||||
UNIPHY_CH_ADP_SW_RSTN);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
return regmap_set_bits(uniphy->regmap, UNIPHY_CH_CTRL(upcs->channel),
|
||||
UNIPHY_CH_ADP_SW_RSTN);
|
||||
}
|
||||
|
||||
static int uniphy_link_up_usxgmii(struct phylink_pcs *pcs, int speed)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
unsigned int val, uniphy_rate;
|
||||
int ret;
|
||||
|
||||
switch (speed) {
|
||||
case SPEED_10000:
|
||||
val = XPCS_SPEED_10000;
|
||||
uniphy_rate = 312500000;
|
||||
break;
|
||||
case SPEED_5000:
|
||||
val = XPCS_SPEED_5000;
|
||||
uniphy_rate = 156250000;
|
||||
break;
|
||||
case SPEED_2500:
|
||||
val = XPCS_SPEED_2500;
|
||||
uniphy_rate = 78125000;
|
||||
break;
|
||||
case SPEED_1000:
|
||||
val = XPCS_SPEED_1000;
|
||||
uniphy_rate = 125000000;
|
||||
break;
|
||||
case SPEED_100:
|
||||
val = XPCS_SPEED_100;
|
||||
uniphy_rate = 12500000;
|
||||
break;
|
||||
case SPEED_10:
|
||||
val = XPCS_SPEED_10;
|
||||
uniphy_rate = 1250000;
|
||||
break;
|
||||
default:
|
||||
dev_err(uniphy->dev, "Invalid USXGMII speed %d\n", speed);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
clk_set_rate(uniphy->clks[port_rx_clk_idx(upcs)].clk, uniphy_rate);
|
||||
clk_set_rate(uniphy->clks[port_tx_clk_idx(upcs)].clk, uniphy_rate);
|
||||
|
||||
/* Configure XPCS speed */
|
||||
ret = regmap_update_bits(uniphy->regmap, XPCS_MII_CTRL,
|
||||
XPCS_SPEED_MASK, val | XPCS_DUPLEX_FULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* XPCS adapter reset */
|
||||
return regmap_set_bits(uniphy->regmap, XPCS_DIG_CTRL, XPCS_USXG_ADPT_RESET);
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_link_up(struct phylink_pcs *pcs,
|
||||
unsigned int neg_mode,
|
||||
phy_interface_t interface,
|
||||
int speed, int duplex)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
int ret = 0;
|
||||
|
||||
switch (interface) {
|
||||
case PHY_INTERFACE_MODE_SGMII:
|
||||
case PHY_INTERFACE_MODE_QSGMII:
|
||||
case PHY_INTERFACE_MODE_PSGMII:
|
||||
case PHY_INTERFACE_MODE_1000BASEX:
|
||||
case PHY_INTERFACE_MODE_2500BASEX:
|
||||
ret = uniphy_link_up_sgmii(pcs, interface, speed);
|
||||
break;
|
||||
case PHY_INTERFACE_MODE_USXGMII:
|
||||
case PHY_INTERFACE_MODE_10GBASER:
|
||||
ret = uniphy_link_up_usxgmii(pcs, speed);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
dev_err(uniphy->dev, "PCS link up fail for interface %s\n",
|
||||
phy_modes(interface));
|
||||
}
|
||||
|
||||
static int qca_uniphy_pcs_validate(struct phylink_pcs *pcs, unsigned long *supported,
|
||||
const struct phylink_link_state *state)
|
||||
{
|
||||
switch (state->interface) {
|
||||
case PHY_INTERFACE_MODE_SGMII:
|
||||
case PHY_INTERFACE_MODE_QSGMII:
|
||||
case PHY_INTERFACE_MODE_PSGMII:
|
||||
case PHY_INTERFACE_MODE_1000BASEX:
|
||||
case PHY_INTERFACE_MODE_2500BASEX:
|
||||
case PHY_INTERFACE_MODE_USXGMII:
|
||||
case PHY_INTERFACE_MODE_10GBASER:
|
||||
return 0;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_disable(struct phylink_pcs *pcs)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
|
||||
if (uniphy->data->uniphy_type == UNIPHY_IPQ5018) {
|
||||
clk_disable(uniphy->clks[port_rx_clk_idx(upcs)].clk);
|
||||
clk_disable(uniphy->clks[port_tx_clk_idx(upcs)].clk);
|
||||
}
|
||||
}
|
||||
|
||||
static int qca_uniphy_pcs_enable(struct phylink_pcs *pcs)
|
||||
{
|
||||
struct qca_uniphy_pcs *upcs = to_qca_uniphy_pcs(pcs);
|
||||
struct qca_uniphy *uniphy = upcs->uniphy;
|
||||
int ret;
|
||||
|
||||
if (uniphy->data->uniphy_type == UNIPHY_IPQ5018) {
|
||||
ret = clk_enable(uniphy->clks[port_rx_clk_idx(upcs)].clk);
|
||||
if (ret) {
|
||||
dev_err(uniphy->dev, "Failed to enable RX clock for channel %d\n",
|
||||
upcs->channel);
|
||||
return ret;
|
||||
}
|
||||
ret = clk_enable(uniphy->clks[port_tx_clk_idx(upcs)].clk);
|
||||
if (ret) {
|
||||
dev_err(uniphy->dev, "Failed to enable TX clock for channel %d\n",
|
||||
upcs->channel);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void qca_uniphy_pcs_an_restart(struct phylink_pcs *pcs) { }
|
||||
|
||||
static const struct phylink_pcs_ops qca_uniphy_pcs_ops = {
|
||||
.pcs_validate = qca_uniphy_pcs_validate,
|
||||
.pcs_inband_caps = qca_uniphy_pcs_inband_caps,
|
||||
.pcs_get_state = qca_uniphy_pcs_get_state,
|
||||
.pcs_config = qca_uniphy_pcs_config,
|
||||
.pcs_link_up = qca_uniphy_pcs_link_up,
|
||||
.pcs_disable = qca_uniphy_pcs_disable,
|
||||
.pcs_enable = qca_uniphy_pcs_enable,
|
||||
.pcs_an_restart = qca_uniphy_pcs_an_restart,
|
||||
};
|
||||
|
||||
static const struct of_device_id qca_uniphy_of_match[] = {
|
||||
{ .compatible = "qualcomm,ipq5018-uniphy", .data = &ipq5018_data },
|
||||
{ .compatible = "qualcomm,ipq6018-uniphy", .data = &ipq6018_data },
|
||||
{ .compatible = "qualcomm,ipq8074-uniphy", .data = &ipq8074_data },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, qca_uniphy_of_match);
|
||||
|
||||
static int uniphy_pcs_regmap_read(void *context, unsigned int reg,
|
||||
unsigned int *val)
|
||||
{
|
||||
struct qca_uniphy *uniphy = context;
|
||||
|
||||
/* PCS uses direct AHB access while XPCS uses indirect AHB access */
|
||||
if (reg >= XPCS_INDIRECT_ADDR) {
|
||||
writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
|
||||
uniphy->base + XPCS_INDIRECT_AHB_ADDR);
|
||||
*val = readl(uniphy->base + XPCS_INDIRECT_DATA_ADDR(reg));
|
||||
} else {
|
||||
*val = readl(uniphy->base + reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int uniphy_pcs_regmap_write(void *context, unsigned int reg,
|
||||
unsigned int val)
|
||||
{
|
||||
struct qca_uniphy *uniphy = context;
|
||||
|
||||
/* PCS uses direct AHB access while XPCS uses indirect AHB access */
|
||||
if (reg >= XPCS_INDIRECT_ADDR) {
|
||||
writel(FIELD_GET(XPCS_INDIRECT_ADDR_H, reg),
|
||||
uniphy->base + XPCS_INDIRECT_AHB_ADDR);
|
||||
writel(val, uniphy->base + XPCS_INDIRECT_DATA_ADDR(reg));
|
||||
} else {
|
||||
writel(val, uniphy->base + reg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct phylink_pcs *qca_uniphy_get(struct fwnode_reference_args *pcsspec,
|
||||
void *data)
|
||||
{
|
||||
struct qca_uniphy *uniphy = data;
|
||||
struct device *dev = uniphy->dev;
|
||||
int channel = 0;
|
||||
|
||||
if (pcsspec->nargs > 1) {
|
||||
dev_err(dev, "Invalid number of cells in 'pcs-handle' property\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
if (pcsspec->nargs)
|
||||
channel = pcsspec->args[0];
|
||||
|
||||
if (channel >= QCA_UNIPHY_CHANNELS) {
|
||||
dev_err(dev, "Invalid channel in 'pcs-handle' property\n");
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return &uniphy->port_pcs[channel].pcs;
|
||||
}
|
||||
|
||||
static const struct regmap_config uniphy_regmap_cfg = {
|
||||
.reg_bits = 32,
|
||||
.val_bits = 32,
|
||||
.reg_read = uniphy_pcs_regmap_read,
|
||||
.reg_write = uniphy_pcs_regmap_write,
|
||||
.fast_io = true,
|
||||
};
|
||||
|
||||
static int qca_uniphy_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct clk_bulk_data *clks;
|
||||
struct qca_uniphy *uniphy;
|
||||
int ret, i, num_clks;
|
||||
|
||||
uniphy = devm_kzalloc(dev, sizeof(*uniphy), GFP_KERNEL);
|
||||
if (!uniphy)
|
||||
return -ENOMEM;
|
||||
|
||||
uniphy->dev = dev;
|
||||
|
||||
uniphy->data = device_get_match_data(dev);
|
||||
if (!uniphy->data)
|
||||
return -EINVAL;
|
||||
|
||||
uniphy->base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(uniphy->base))
|
||||
return dev_err_probe(dev, PTR_ERR(uniphy->base),
|
||||
"Failed to ioremap resource");
|
||||
|
||||
uniphy->regmap = devm_regmap_init(dev, NULL, uniphy, &uniphy_regmap_cfg);
|
||||
if (IS_ERR(uniphy->regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(uniphy->regmap),
|
||||
"Failed to init regmap");
|
||||
|
||||
uniphy->rst_soft = devm_reset_control_get_exclusive(dev, "soft");
|
||||
if (IS_ERR(uniphy->rst_soft))
|
||||
return dev_err_probe(dev, PTR_ERR(uniphy->rst_soft),
|
||||
"Failed to get soft reset\n");
|
||||
|
||||
uniphy->rst_xpcs = devm_reset_control_get_optional_exclusive(dev, "xpcs");
|
||||
if (IS_ERR(uniphy->rst_xpcs))
|
||||
return dev_err_probe(dev, PTR_ERR(uniphy->rst_xpcs),
|
||||
"Failed to get xpcs reset\n");
|
||||
|
||||
num_clks = devm_clk_bulk_get_all_enabled(dev, &clks);
|
||||
if (num_clks < 0)
|
||||
return dev_err_probe(dev, num_clks, "Failed to get clocks\n");
|
||||
|
||||
uniphy->clks = clks;
|
||||
uniphy->num_clks = num_clks;
|
||||
|
||||
ret = qca_uniphy_register_clks(uniphy);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "Failed to register output clocks\n");
|
||||
|
||||
for (i = 0; i < QCA_UNIPHY_CHANNELS; i++) {
|
||||
uniphy->port_pcs[i].pcs.ops = &qca_uniphy_pcs_ops;
|
||||
uniphy->port_pcs[i].pcs.neg_mode = true;
|
||||
uniphy->port_pcs[i].pcs.poll = true;
|
||||
uniphy->port_pcs[i].uniphy = uniphy;
|
||||
uniphy->port_pcs[i].channel = i;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, uniphy);
|
||||
|
||||
return fwnode_pcs_add_provider(dev_fwnode(dev), qca_uniphy_get,
|
||||
uniphy);
|
||||
}
|
||||
|
||||
static struct platform_driver qca_uniphy_driver = {
|
||||
.driver = {
|
||||
.name = "qca-uniphy",
|
||||
.suppress_bind_attrs = true,
|
||||
.of_match_table = qca_uniphy_of_match,
|
||||
},
|
||||
.probe = qca_uniphy_probe,
|
||||
};
|
||||
module_platform_driver(qca_uniphy_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Qualcomm IPQ6018 UNIPHY PCS driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
@@ -0,0 +1,208 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/net/pcs/qualcomm,ipq-uniphy.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Qualcomm IPQ UNIPHY PCS
|
||||
|
||||
maintainers:
|
||||
- John Crispin <john@phrozen.org>
|
||||
- Christian Marangi <ansuelsmth@gmail.com>
|
||||
|
||||
description: |
|
||||
The UNIPHY blocks found in Qualcomm IPQ AX and BE SoCs provide SerDes-based
|
||||
Physical Coding Sublayer (PCS) controllers. Each SoC contains one or multiple
|
||||
UNIPHY instances. An instance can operate in PSGMII mode or in single-channel
|
||||
mode (SGMII, 2500BASE-X, USXGMII). The PPE DSA switch driver references
|
||||
UNIPHY instances through pcs-handle phandles.
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
enum:
|
||||
- qualcomm,ipq5018-uniphy
|
||||
- qualcomm,ipq6018-uniphy
|
||||
- qualcomm,ipq8074-uniphy
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
'#pcs-cells':
|
||||
const: 1
|
||||
description:
|
||||
The single cell specifies the channel index within the UNIPHY instance.
|
||||
|
||||
clocks:
|
||||
minItems: 4
|
||||
maxItems: 12
|
||||
description: |
|
||||
Core clocks (AHB, SYS) are always required. Per-channel RX/TX clock
|
||||
pairs are provided for each port the UNIPHY instance serves. Instances
|
||||
serving a single port have 4 clocks, PSGMII instances serving 5 ports
|
||||
have 12. The CMN PLL CC provides a 50MHz reference clock to the UNIPHY
|
||||
PCS which gates and divides it to pass through either a 25 MHz or 50 MHz
|
||||
clock to an attached Ethernet PHY or a switch.
|
||||
items:
|
||||
- description: ahb clock
|
||||
- description: sys clock
|
||||
- description: port1_rx or port5_rx or port6_rx clock
|
||||
- description: port1_tx or port5_tx or port6_tx clock
|
||||
- description: port2_rx clock
|
||||
- description: port2_tx clock
|
||||
- description: port3_rx clock
|
||||
- description: port3_tx clock
|
||||
- description: port4_rx clock
|
||||
- description: port4_tx clock
|
||||
- description: port5_rx clock
|
||||
- description: port5_tx clock
|
||||
- description: CMN PLL reference clock
|
||||
|
||||
clock-names:
|
||||
minItems: 4
|
||||
maxItems: 12
|
||||
items:
|
||||
- const: ahb
|
||||
- const: sys
|
||||
- enum: [port1_rx, port5_rx, port6_rx]
|
||||
- enum: [port1_tx, port5_tx, port6_tx]
|
||||
- const: port2_rx
|
||||
- const: port2_tx
|
||||
- const: port3_rx
|
||||
- const: port3_tx
|
||||
- const: port4_rx
|
||||
- const: port4_tx
|
||||
- const: port5_rx
|
||||
- const: port5_tx
|
||||
- const: ref
|
||||
|
||||
clock-output-names:
|
||||
description: Name of the uniphy GCC clock for TX and RX.
|
||||
items:
|
||||
- enum:
|
||||
- uniphy0_gcc_tx_clk
|
||||
- uniphy1_gcc_tx_clk
|
||||
- uniphy2_gcc_tx_clk
|
||||
- enum:
|
||||
- uniphy0_gcc_rx_clk
|
||||
- uniphy1_gcc_rx_clk
|
||||
- uniphy2_gcc_rx_clk
|
||||
- const: uniphy0_ref_clk
|
||||
|
||||
'#clock-cells':
|
||||
const: 1
|
||||
|
||||
resets:
|
||||
minItems: 1
|
||||
items:
|
||||
- description: soft reset
|
||||
- description: xpcs reset
|
||||
|
||||
reset-names:
|
||||
minItems: 1
|
||||
items:
|
||||
- const: soft
|
||||
- const: xpcs
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- '#pcs-cells'
|
||||
- clocks
|
||||
- clock-names
|
||||
- resets
|
||||
- reset-names
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/clock/qcom,gcc-ipq8074.h>
|
||||
#include <dt-bindings/net/qca-uniphy.h>
|
||||
|
||||
ethernet-pcs@7a10000 {
|
||||
compatible = "qualcomm,ipq8074-uniphy";
|
||||
reg = <0x7a10000 0x10000>;
|
||||
|
||||
clocks = <&gcc GCC_UNIPHY1_AHB_CLK>,
|
||||
<&gcc GCC_UNIPHY1_SYS_CLK>,
|
||||
<&gcc GCC_UNIPHY1_PORT5_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY1_PORT5_TX_CLK>;
|
||||
clock-names = "ahb", "sys",
|
||||
"port5_rx", "port5_tx";
|
||||
|
||||
clock-output-names = "uniphy1_gcc_tx_clk",
|
||||
"uniphy1_gcc_rx_clk";
|
||||
|
||||
resets = <&gcc GCC_UNIPHY1_SOFT_RESET>,
|
||||
<&gcc GCC_UNIPHY1_XPCS_RESET>;
|
||||
reset-names = "soft", "xpcs";
|
||||
|
||||
#pcs-cells = <1>;
|
||||
};
|
||||
|
||||
- |
|
||||
#include <dt-bindings/clock/qcom,gcc-ipq6018.h>
|
||||
#include <dt-bindings/net/qca-uniphy.h>
|
||||
|
||||
ethernet-pcs@7a00000 {
|
||||
compatible = "qualcomm,ipq6018-uniphy";
|
||||
reg = <0x7a00000 0x10000>;
|
||||
qcom,psgmii;
|
||||
|
||||
clocks = <&gcc GCC_UNIPHY0_AHB_CLK>,
|
||||
<&gcc GCC_UNIPHY0_SYS_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT1_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT1_TX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT2_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT2_TX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT3_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT3_TX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT4_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT4_TX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT5_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY0_PORT5_TX_CLK>;
|
||||
clock-names = "ahb", "sys",
|
||||
"port1_rx", "port1_tx",
|
||||
"port2_rx", "port2_tx",
|
||||
"port3_rx", "port3_tx",
|
||||
"port4_rx", "port4_tx",
|
||||
"port5_rx", "port5_tx";
|
||||
|
||||
clock-output-names = "uniphy0_gcc_tx_clk",
|
||||
"uniphy0_gcc_rx_clk";
|
||||
|
||||
resets = <&gcc GCC_UNIPHY0_SOFT_RESET>,
|
||||
<&gcc GCC_UNIPHY0_XPCS_RESET>;
|
||||
reset-names = "soft", "xpcs";
|
||||
|
||||
#pcs-cells = <1>;
|
||||
};
|
||||
|
||||
- |
|
||||
#include <dt-bindings/clock/qcom,gcc-ipq5018.h>
|
||||
#include <dt-bindings/net/qca-uniphy.h>
|
||||
|
||||
ethernet-pcs@98000 {
|
||||
compatible = "qualcomm,ipq5018-uniphy";
|
||||
reg = <0x00098000 0x800>;
|
||||
#clock-cells = <1>;
|
||||
clocks = <&gcc GCC_UNIPHY_AHB_CLK>,
|
||||
<&gcc GCC_UNIPHY_SYS_CLK>,
|
||||
<&gcc GCC_UNIPHY_RX_CLK>,
|
||||
<&gcc GCC_UNIPHY_TX_CLK>,
|
||||
<&cmn_pll IPQ5018_ETH_50MHZ_CLK>;
|
||||
clock-names = "ahb",
|
||||
"sys",
|
||||
"port5_rx",
|
||||
"port5_tx",
|
||||
"ref";
|
||||
assigned-clocks = <&uniphy0 UNIPHY_CLK_REF>;
|
||||
assigned-clock-rates = <UNIPHY_REFCLK_25MHZ>;
|
||||
clock-output-names = "uniphy0_gcc_rx_clk",
|
||||
"uniphy0_gcc_tx_clk",
|
||||
"uniphy0_ref_clk";
|
||||
resets = <&gcc GCC_UNIPHY_SOFT_RESET>;
|
||||
reset-names = "soft";
|
||||
|
||||
#pcs-cells = <0>;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||
/*
|
||||
* Device Tree constants for the Qualcomm UNIPHY
|
||||
*/
|
||||
|
||||
#ifndef _DT_BINDINGS_QCA_UNIPHY_H
|
||||
#define _DT_BINDINGS_QCA_UNIPHY_H
|
||||
|
||||
#define UNIPHY_CLK_RX 0
|
||||
#define UNIPHY_CLK_TX 1
|
||||
#define UNIPHY_CLK_REF 2
|
||||
|
||||
#define UNIPHY_REFCLK_25MHZ 25000000
|
||||
#define UNIPHY_REFCLK_50MHZ 50000000
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,150 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef __QCA_UNIPHY_H
|
||||
#define __QCA_UNIPHY_H
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/clk-provider.h>
|
||||
#include <linux/phylink.h>
|
||||
#include <linux/reset.h>
|
||||
|
||||
#define QCA_UNIPHY_CHANNELS 5
|
||||
|
||||
#define IPQ5018_UNIPHY_REFCLK 0x74
|
||||
#define IPQ5018_UNIPHY_REFCLK_EN BIT(0)
|
||||
#define IPQ5018_UNIPHY_REFCLK_DIV BIT(1) /* 0 (default): 50MHz, 1: 25MHz */
|
||||
#define IPQ5018_UNIPHY_REFCLK_DS GENMASK(3, 2) /* 0: 2.8V, 1 (default): 1.5V */
|
||||
|
||||
#define UNIPHY_OFFSET_CALIB_4 0x1e0
|
||||
#define UNIPHY_CALIBRATION_DONE BIT(7)
|
||||
|
||||
#define UNIPHY_MISC2_PHY_MODE 0x218
|
||||
#define UNIPHY_MISC2_PHY_MODE_MASK GENMASK(6, 4)
|
||||
#define UNIPHY_MISC2_SGMII FIELD_PREP_CONST(UNIPHY_MISC2_PHY_MODE_MASK, 0x3)
|
||||
#define UNIPHY_MISC2_SGMIIPLUS FIELD_PREP_CONST(UNIPHY_MISC2_PHY_MODE_MASK, 0x5)
|
||||
#define UNIPHY_MISC2_USXGMII FIELD_PREP_CONST(UNIPHY_MISC2_PHY_MODE_MASK, 0x7)
|
||||
|
||||
#define UNIPHY_MODE_CTRL 0x46c
|
||||
#define UNIPHY_MODE_SEL_MASK GENMASK(12, 8)
|
||||
#define UNIPHY_XPCS_MODE BIT(12)
|
||||
#define UNIPHY_SGPLUS_MODE BIT(11)
|
||||
#define UNIPHY_SGMII_MODE BIT(10)
|
||||
#define UNIPHY_CH0_PSGMII_QSGMII BIT(9)
|
||||
#define UNIPHY_CH0_QSGMII_SGMII BIT(8)
|
||||
#define UNIPHY_CH0_MODE_CTRL_25M GENMASK(6, 4)
|
||||
#define UNIPHY_CH0_MODE_1000BASEX 0
|
||||
#define UNIPHY_CH0_MODE_MAC 2
|
||||
#define UNIPHY_AUTONEG_MODE_ATH BIT(0)
|
||||
|
||||
#define UNIPHY_PLL_POWER_ON_AND_RESET 0x780
|
||||
#define UNIPHY_PLL_RESET_ANALOG BIT(6)
|
||||
|
||||
#define UNIPHY_CH_BASE(ch) (0x480 + (ch) * 0x18)
|
||||
|
||||
#define UNIPHY_CH_CTRL(ch) (UNIPHY_CH_BASE(ch) + 0x0)
|
||||
#define UNIPHY_CH_SPEED_MODE GENMASK(2, 1)
|
||||
#define UNIPHY_CH_FORCE_MODE BIT(3)
|
||||
#define UNIPHY_CH_AN_ENABLE BIT(6)
|
||||
#define UNIPHY_CH_AN_RESTART BIT(7)
|
||||
#define UNIPHY_CH_ADP_SW_RSTN BIT(11)
|
||||
|
||||
#define UNIPHY_CH_STS(ch) (UNIPHY_CH_BASE(ch) + 0x8)
|
||||
#define UNIPHY_CH_STS_RX_PAUSE BIT(0)
|
||||
#define UNIPHY_CH_STS_TX_PAUSE BIT(1)
|
||||
#define UNIPHY_CH_STS_SPEED_MODE GENMASK(5, 4)
|
||||
#define UNIPHY_CH_STS_DUPLEX BIT(6)
|
||||
#define UNIPHY_CH_STS_LINK BIT(7)
|
||||
|
||||
#define XPCS_INDIRECT_ADDR 0x8000
|
||||
#define XPCS_INDIRECT_AHB_ADDR 0x83fc
|
||||
#define XPCS_INDIRECT_ADDR_H GENMASK(20, 8)
|
||||
#define XPCS_INDIRECT_ADDR_L GENMASK(7, 0)
|
||||
#define XPCS_INDIRECT_DATA_ADDR(reg) (FIELD_PREP(GENMASK(15, 10), 0x20) | \
|
||||
FIELD_PREP(GENMASK(9, 2), \
|
||||
FIELD_GET(XPCS_INDIRECT_ADDR_L, reg)))
|
||||
|
||||
#define XPCS_KR_STS1 0x30020
|
||||
#define XPCS_KR_STS1_PLU BIT(12)
|
||||
|
||||
#define XPCS_DIG_CTRL 0x38000
|
||||
#define XPCS_SOFT_RESET BIT(15)
|
||||
#define XPCS_USXG_ADPT_RESET BIT(10)
|
||||
#define XPCS_USXG_EN BIT(9)
|
||||
|
||||
#define XPCS_MII_CTRL 0x1f0000
|
||||
#define XPCS_MII1_CTRL(x) (0x1a0000 + 0x10000 * ((x) - 1))
|
||||
#define XPCS_MII_AN_EN BIT(12)
|
||||
#define XPCS_DUPLEX_FULL BIT(8)
|
||||
#define XPCS_SPEED_MASK (BIT(13) | BIT(6) | BIT(5))
|
||||
#define XPCS_SPEED_10000 (BIT(13) | BIT(6))
|
||||
#define XPCS_SPEED_5000 (BIT(13) | BIT(5))
|
||||
#define XPCS_SPEED_2500 BIT(5)
|
||||
#define XPCS_SPEED_1000 BIT(6)
|
||||
#define XPCS_SPEED_100 BIT(13)
|
||||
#define XPCS_SPEED_10 0
|
||||
|
||||
#define XPCS_MII_AN_CTRL 0x1f8001
|
||||
#define XPCS_MII1_AN_CTRL(x) (0x1a8001 + 0x10000 * ((x) - 1))
|
||||
#define XPCS_MII_AN_8BIT BIT(8)
|
||||
|
||||
#define XPCS_MII_AN_INTR_STS 0x1f8002
|
||||
#define XPCS_MII1_AN_INTR_STS(x) (0x1a8002 + 0x10000 * ((x) - 1))
|
||||
#define XPCS_USXG_AN_LINK_STS BIT(14)
|
||||
#define XPCS_USXG_AN_SPEED_MASK GENMASK(12, 10)
|
||||
#define XPCS_USXG_AN_SPEED_10 0
|
||||
#define XPCS_USXG_AN_SPEED_100 1
|
||||
#define XPCS_USXG_AN_SPEED_1000 2
|
||||
#define XPCS_USXG_AN_SPEED_2500 4
|
||||
#define XPCS_USXG_AN_SPEED_5000 5
|
||||
#define XPCS_USXG_AN_SPEED_10000 3
|
||||
|
||||
#define UNIPHY_CALIBRATION_TIMEOUT_US 100000
|
||||
#define UNIPHY_CALIBRATION_POLL_US 1000
|
||||
|
||||
enum qca_uniphy_type {
|
||||
UNIPHY_IPQ5018,
|
||||
UNIPHY_IPQ6018,
|
||||
UNIPHY_IPQ8074,
|
||||
};
|
||||
|
||||
struct qca_uniphy;
|
||||
|
||||
struct qca_uniphy_clk {
|
||||
struct clk_hw hw;
|
||||
struct qca_uniphy *uniphy;
|
||||
};
|
||||
|
||||
struct qca_uniphy_pcs {
|
||||
struct phylink_pcs pcs;
|
||||
struct qca_uniphy *uniphy;
|
||||
int channel;
|
||||
};
|
||||
|
||||
struct qca_uniphy_match_data {
|
||||
enum qca_uniphy_type uniphy_type;
|
||||
bool ref_clk_enable;
|
||||
};
|
||||
|
||||
#define to_qca_uniphy_pcs(n) container_of(pcs, struct qca_uniphy_pcs, pcs);
|
||||
|
||||
struct qca_uniphy {
|
||||
struct device *dev;
|
||||
void __iomem *base;
|
||||
struct regmap *regmap;
|
||||
struct reset_control *rst_ahb;
|
||||
struct reset_control *rst_soft;
|
||||
struct reset_control *rst_xpcs;
|
||||
struct clk_bulk_data *clks;
|
||||
int num_clks;
|
||||
struct qca_uniphy_pcs port_pcs[QCA_UNIPHY_CHANNELS];
|
||||
struct qca_uniphy_clk rx_clk;
|
||||
struct qca_uniphy_clk tx_clk;
|
||||
struct qca_uniphy_clk ref_clk;
|
||||
const struct qca_uniphy_match_data *data;
|
||||
phy_interface_t interface;
|
||||
};
|
||||
|
||||
#define port_rx_clk_idx(upcs) ((upcs)->channel * 2) + 2
|
||||
#define port_tx_clk_idx(upcs) (((upcs)->channel * 2) + 1) + 2
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,23 @@
|
||||
--- a/drivers/net/pcs/Kconfig
|
||||
+++ b/drivers/net/pcs/Kconfig
|
||||
@@ -51,4 +51,13 @@ config PCS_RZN1_MIIC
|
||||
on RZ/N1 SoCs. This PCS converts MII to RMII/RGMII or can be set in
|
||||
pass-through mode for MII.
|
||||
|
||||
+config PCS_QCA_UNIPHY
|
||||
+ tristate "Qualcomm 802.11ax UNIPHY PCS driver"
|
||||
+ select PHYLINK
|
||||
+ select FWNODE_PCS
|
||||
+ help
|
||||
+ PCS driver for the UNIPHY SerDes blocks in Qualcomm 802.11ax SoCs.
|
||||
+ Provides PSGMII and SGMII interface modes and registers the UNIPHY
|
||||
+ PLL clock providers needed for port clock muxing.
|
||||
+
|
||||
endmenu
|
||||
--- a/drivers/net/pcs/Makefile
|
||||
+++ b/drivers/net/pcs/Makefile
|
||||
@@ -10,3 +10,4 @@ obj-$(CONFIG_PCS_LYNX) += pcs-lynx.o
|
||||
obj-$(CONFIG_PCS_MTK_LYNXI) += pcs-mtk-lynxi.o
|
||||
obj-$(CONFIG_PCS_RZN1_MIIC) += pcs-rzn1-miic.o
|
||||
obj-$(CONFIG_PCS_MTK_USXGMII) += pcs-mtk-usxgmii.o
|
||||
+obj-$(CONFIG_PCS_QCA_UNIPHY) += pcs-qca-uniphy.o
|
||||
Reference in new issue
Block a user