Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# PCS Layer Configuration
#
menu "PCS device drivers"
config PCS_XPCS
tristate "Synopsys DesignWare Ethernet XPCS"
select PHYLINK
help
This module provides a driver and helper functions for Synopsys
DesignWare XPCS controllers.
config PCS_LYNX
tristate
help
This module provides helpers to phylink for managing the Lynx PCS
which is part of the Layerscape and QorIQ Ethernet SERDES.
config PCS_MTK_LYNXI
tristate
select PHY_COMMON_PROPS
select REGMAP
help
This module provides helpers to phylink for managing the LynxI PCS
which is part of MediaTek's SoC and Ethernet switch ICs.
config PCS_RZN1_MIIC
tristate "Renesas RZ/N1, RZ/N2H, RZ/T2H MII converter"
depends on OF
depends on ARCH_RENESAS || COMPILE_TEST
help
This module provides a driver for the MII converter available on
Renesas RZ/N1, RZ/N2H, and RZ/T2H SoCs. This PCS converts MII to
RMII/RGMII, or can be set in pass-through mode for MII.
endmenu
+10
View File
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Linux PCS drivers
pcs_xpcs-$(CONFIG_PCS_XPCS) := pcs-xpcs.o pcs-xpcs-plat.o \
pcs-xpcs-nxp.o pcs-xpcs-wx.o
obj-$(CONFIG_PCS_XPCS) += pcs_xpcs.o
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
+375
View File
@@ -0,0 +1,375 @@
// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
/* Copyright 2020 NXP
* Lynx PCS MDIO helpers
*/
#include <linux/mdio.h>
#include <linux/phylink.h>
#include <linux/pcs-lynx.h>
#include <linux/property.h>
#define SGMII_CLOCK_PERIOD_NS 8 /* PCS is clocked at 125 MHz */
#define LINK_TIMER_VAL(ns) ((u32)((ns) / SGMII_CLOCK_PERIOD_NS))
#define LINK_TIMER_LO 0x12
#define LINK_TIMER_HI 0x13
#define IF_MODE 0x14
#define IF_MODE_SGMII_EN BIT(0)
#define IF_MODE_USE_SGMII_AN BIT(1)
#define IF_MODE_SPEED(x) (((x) << 2) & GENMASK(3, 2))
#define IF_MODE_SPEED_MSK GENMASK(3, 2)
#define IF_MODE_HALF_DUPLEX BIT(4)
struct lynx_pcs {
struct phylink_pcs pcs;
struct mdio_device *mdio;
};
enum sgmii_speed {
SGMII_SPEED_10 = 0,
SGMII_SPEED_100 = 1,
SGMII_SPEED_1000 = 2,
SGMII_SPEED_2500 = 2,
};
#define phylink_pcs_to_lynx(pl_pcs) container_of((pl_pcs), struct lynx_pcs, pcs)
#define lynx_to_phylink_pcs(lynx) (&(lynx)->pcs)
static unsigned int lynx_pcs_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
switch (interface) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
case PHY_INTERFACE_MODE_10GBASER:
return LINK_INBAND_DISABLE;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
return LINK_INBAND_ENABLE;
default:
return 0;
}
}
static void lynx_pcs_get_state_usxgmii(struct mdio_device *pcs,
struct phylink_link_state *state)
{
struct mii_bus *bus = pcs->bus;
int addr = pcs->addr;
int status, lpa;
status = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_BMSR);
if (status < 0)
return;
state->link = !!(status & MDIO_STAT1_LSTATUS);
state->an_complete = !!(status & MDIO_AN_STAT1_COMPLETE);
if (!state->link || !state->an_complete)
return;
lpa = mdiobus_c45_read(bus, addr, MDIO_MMD_VEND2, MII_LPA);
if (lpa < 0)
return;
phylink_decode_usxgmii_word(state, lpa);
}
static void lynx_pcs_get_state(struct phylink_pcs *pcs, unsigned int neg_mode,
struct phylink_link_state *state)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
switch (state->interface) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
phylink_mii_c22_pcs_get_state(lynx->mdio, neg_mode, state);
break;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
lynx_pcs_get_state_usxgmii(lynx->mdio, state);
break;
case PHY_INTERFACE_MODE_10GBASER:
phylink_mii_c45_pcs_get_state(lynx->mdio, state);
break;
default:
break;
}
dev_dbg(&lynx->mdio->dev,
"mode=%s/%s/%s link=%u an_complete=%u\n",
phy_modes(state->interface),
phy_speed_to_str(state->speed),
phy_duplex_to_str(state->duplex),
state->link, state->an_complete);
}
static int lynx_pcs_config_giga(struct mdio_device *pcs,
phy_interface_t interface,
const unsigned long *advertising,
unsigned int neg_mode)
{
int link_timer_ns;
u32 link_timer;
u16 if_mode;
int err;
link_timer_ns = phylink_get_link_timer_ns(interface);
if (link_timer_ns > 0) {
link_timer = LINK_TIMER_VAL(link_timer_ns);
mdiodev_write(pcs, LINK_TIMER_LO, link_timer & 0xffff);
mdiodev_write(pcs, LINK_TIMER_HI, link_timer >> 16);
}
if (interface == PHY_INTERFACE_MODE_1000BASEX ||
interface == PHY_INTERFACE_MODE_2500BASEX) {
if_mode = 0;
} else {
/* SGMII and QSGMII */
if_mode = IF_MODE_SGMII_EN;
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
if_mode |= IF_MODE_USE_SGMII_AN;
}
err = mdiodev_modify(pcs, IF_MODE,
IF_MODE_SGMII_EN | IF_MODE_USE_SGMII_AN,
if_mode);
if (err)
return err;
return phylink_mii_c22_pcs_config(pcs, interface, advertising,
neg_mode);
}
static int lynx_pcs_config_usxgmii(struct mdio_device *pcs,
phy_interface_t interface,
const unsigned long *advertising,
unsigned int neg_mode)
{
struct mii_bus *bus = pcs->bus;
int addr = pcs->addr;
if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
dev_err(&pcs->dev, "%s only supports in-band AN for now\n",
phy_modes(interface));
return -EOPNOTSUPP;
}
/* Configure device ability for the USXGMII Replicator */
return mdiobus_c45_write(bus, addr, MDIO_MMD_VEND2, MII_ADVERTISE,
MDIO_USXGMII_10G | MDIO_USXGMII_LINK |
MDIO_USXGMII_FULL_DUPLEX |
ADVERTISE_SGMII | ADVERTISE_LPACK);
}
static int lynx_pcs_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t ifmode,
const unsigned long *advertising, bool permit)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
switch (ifmode) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
case PHY_INTERFACE_MODE_2500BASEX:
return lynx_pcs_config_giga(lynx->mdio, ifmode, advertising,
neg_mode);
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
return lynx_pcs_config_usxgmii(lynx->mdio, ifmode, advertising,
neg_mode);
case PHY_INTERFACE_MODE_10GBASER:
/* Nothing to do here for 10GBASER */
break;
default:
return -EOPNOTSUPP;
}
return 0;
}
static void lynx_pcs_an_restart(struct phylink_pcs *pcs)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
phylink_mii_c22_pcs_an_restart(lynx->mdio);
}
static void lynx_pcs_link_up_sgmii(struct mdio_device *pcs,
unsigned int neg_mode,
int speed, int duplex)
{
u16 if_mode = 0, sgmii_speed;
/* The PCS needs to be configured manually only
* when not operating on in-band mode
*/
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED)
return;
if (duplex == DUPLEX_HALF)
if_mode |= IF_MODE_HALF_DUPLEX;
switch (speed) {
case SPEED_1000:
sgmii_speed = SGMII_SPEED_1000;
break;
case SPEED_100:
sgmii_speed = SGMII_SPEED_100;
break;
case SPEED_10:
sgmii_speed = SGMII_SPEED_10;
break;
case SPEED_UNKNOWN:
/* Silently don't do anything */
return;
default:
dev_err(&pcs->dev, "Invalid PCS speed %d\n", speed);
return;
}
if_mode |= IF_MODE_SPEED(sgmii_speed);
mdiodev_modify(pcs, IF_MODE,
IF_MODE_HALF_DUPLEX | IF_MODE_SPEED_MSK,
if_mode);
}
static void lynx_pcs_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
int speed, int duplex)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
switch (interface) {
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_QSGMII:
lynx_pcs_link_up_sgmii(lynx->mdio, neg_mode, speed, duplex);
break;
case PHY_INTERFACE_MODE_USXGMII:
case PHY_INTERFACE_MODE_10G_QXGMII:
/* At the moment, only in-band AN is supported for USXGMII
* so nothing to do in link_up
*/
break;
default:
break;
}
}
static const struct phylink_pcs_ops lynx_pcs_phylink_ops = {
.pcs_inband_caps = lynx_pcs_inband_caps,
.pcs_get_state = lynx_pcs_get_state,
.pcs_config = lynx_pcs_config,
.pcs_an_restart = lynx_pcs_an_restart,
.pcs_link_up = lynx_pcs_link_up,
};
static const phy_interface_t lynx_interfaces[] = {
PHY_INTERFACE_MODE_SGMII,
PHY_INTERFACE_MODE_QSGMII,
PHY_INTERFACE_MODE_1000BASEX,
PHY_INTERFACE_MODE_2500BASEX,
PHY_INTERFACE_MODE_10GBASER,
PHY_INTERFACE_MODE_USXGMII,
PHY_INTERFACE_MODE_10G_QXGMII,
};
static struct phylink_pcs *lynx_pcs_create(struct mdio_device *mdio)
{
struct lynx_pcs *lynx;
int i;
lynx = kzalloc_obj(*lynx);
if (!lynx)
return ERR_PTR(-ENOMEM);
mdio_device_get(mdio);
lynx->mdio = mdio;
lynx->pcs.ops = &lynx_pcs_phylink_ops;
lynx->pcs.poll = true;
for (i = 0; i < ARRAY_SIZE(lynx_interfaces); i++)
__set_bit(lynx_interfaces[i], lynx->pcs.supported_interfaces);
return lynx_to_phylink_pcs(lynx);
}
struct phylink_pcs *lynx_pcs_create_mdiodev(struct mii_bus *bus, int addr)
{
struct mdio_device *mdio;
struct phylink_pcs *pcs;
mdio = mdio_device_create(bus, addr);
if (IS_ERR(mdio))
return ERR_CAST(mdio);
pcs = lynx_pcs_create(mdio);
/* lynx_create() has taken a refcount on the mdiodev if it was
* successful. If lynx_create() fails, this will free the mdio
* device here. In any case, we don't need to hold our reference
* anymore, and putting it here will allow mdio_device_put() in
* lynx_destroy() to automatically free the mdio device.
*/
mdio_device_put(mdio);
return pcs;
}
EXPORT_SYMBOL(lynx_pcs_create_mdiodev);
/*
* lynx_pcs_create_fwnode() creates a lynx PCS instance from the fwnode
* device indicated by node.
*
* Returns:
* -ENODEV if the fwnode is marked unavailable
* -EPROBE_DEFER if we fail to find the device
* -ENOMEM if we fail to allocate memory
* pointer to a phylink_pcs on success
*/
struct phylink_pcs *lynx_pcs_create_fwnode(struct fwnode_handle *node)
{
struct mdio_device *mdio;
struct phylink_pcs *pcs;
if (!fwnode_device_is_available(node))
return ERR_PTR(-ENODEV);
mdio = fwnode_mdio_find_device(node);
if (!mdio)
return ERR_PTR(-EPROBE_DEFER);
pcs = lynx_pcs_create(mdio);
/* lynx_create() has taken a refcount on the mdiodev if it was
* successful. If lynx_create() fails, this will free the mdio
* device here. In any case, we don't need to hold our reference
* anymore, and putting it here will allow mdio_device_put() in
* lynx_destroy() to automatically free the mdio device.
*/
mdio_device_put(mdio);
return pcs;
}
EXPORT_SYMBOL_GPL(lynx_pcs_create_fwnode);
void lynx_pcs_destroy(struct phylink_pcs *pcs)
{
struct lynx_pcs *lynx = phylink_pcs_to_lynx(pcs);
mdio_device_put(lynx->mdio);
kfree(lynx);
}
EXPORT_SYMBOL(lynx_pcs_destroy);
MODULE_DESCRIPTION("NXP Lynx PCS phylink library");
MODULE_LICENSE("Dual BSD/GPL");
+373
View File
@@ -0,0 +1,373 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018-2019 MediaTek Inc.
/* A library for MediaTek SGMII circuit
*
* Author: Sean Wang <sean.wang@mediatek.com>
* Author: Alexander Couzens <lynxis@fe80.eu>
* Author: Daniel Golle <daniel@makrotopia.org>
*
*/
#include <linux/mdio.h>
#include <linux/of.h>
#include <linux/pcs/pcs-mtk-lynxi.h>
#include <linux/phy/phy-common-props.h>
#include <linux/phylink.h>
#include <linux/regmap.h>
/* SGMII subsystem config registers */
/* BMCR (low 16) BMSR (high 16) */
#define SGMSYS_PCS_CONTROL_1 0x0
#define SGMII_BMCR GENMASK(15, 0)
#define SGMII_BMSR GENMASK(31, 16)
#define SGMSYS_PCS_DEVICE_ID 0x4
#define SGMII_LYNXI_DEV_ID 0x4d544950
#define SGMSYS_PCS_ADVERTISE 0x8
#define SGMII_ADVERTISE GENMASK(15, 0)
#define SGMII_LPA GENMASK(31, 16)
#define SGMSYS_PCS_SCRATCH 0x14
#define SGMII_DEV_VERSION GENMASK(31, 16)
/* Register to programmable link timer, the unit in 2 * 8ns */
#define SGMSYS_PCS_LINK_TIMER 0x18
#define SGMII_LINK_TIMER_MASK GENMASK(19, 0)
#define SGMII_LINK_TIMER_VAL(ns) FIELD_PREP(SGMII_LINK_TIMER_MASK, \
((ns) / 2 / 8))
/* Register to control remote fault */
#define SGMSYS_SGMII_MODE 0x20
#define SGMII_IF_MODE_SGMII BIT(0)
#define SGMII_SPEED_DUPLEX_AN BIT(1)
#define SGMII_SPEED_MASK GENMASK(3, 2)
#define SGMII_SPEED_10 FIELD_PREP(SGMII_SPEED_MASK, 0)
#define SGMII_SPEED_100 FIELD_PREP(SGMII_SPEED_MASK, 1)
#define SGMII_SPEED_1000 FIELD_PREP(SGMII_SPEED_MASK, 2)
#define SGMII_DUPLEX_HALF BIT(4)
#define SGMII_REMOTE_FAULT_DIS BIT(8)
/* Register to reset SGMII design */
#define SGMSYS_RESERVED_0 0x34
#define SGMII_SW_RESET BIT(0)
/* Register to set SGMII speed, ANA RG_ Control Signals III */
#define SGMII_PHY_SPEED_MASK GENMASK(3, 2)
#define SGMII_PHY_SPEED_1_25G FIELD_PREP(SGMII_PHY_SPEED_MASK, 0)
#define SGMII_PHY_SPEED_3_125G FIELD_PREP(SGMII_PHY_SPEED_MASK, 1)
/* Register to power up QPHY */
#define SGMSYS_QPHY_PWR_STATE_CTRL 0xe8
#define SGMII_PHYA_PWD BIT(4)
/* Register to QPHY wrapper control */
#define SGMSYS_QPHY_WRAP_CTRL 0xec
#define SGMII_PN_SWAP_RX BIT(1)
#define SGMII_PN_SWAP_TX BIT(0)
/* struct mtk_pcs_lynxi - This structure holds each sgmii regmap andassociated
* data
* @regmap: The register map pointing at the range used to setup
* SGMII modes
* @dev: Pointer to device owning the PCS
* @ana_rgc3: The offset of register ANA_RGC3 relative to regmap
* @interface: Currently configured interface mode
* @pcs: Phylink PCS structure
* @flags: Flags indicating hardware properties
*/
struct mtk_pcs_lynxi {
struct regmap *regmap;
u32 ana_rgc3;
phy_interface_t interface;
struct phylink_pcs pcs;
u32 flags;
struct fwnode_handle *fwnode;
};
static struct mtk_pcs_lynxi *pcs_to_mtk_pcs_lynxi(struct phylink_pcs *pcs)
{
return container_of(pcs, struct mtk_pcs_lynxi, pcs);
}
static unsigned int mtk_pcs_lynxi_inband_caps(struct phylink_pcs *pcs,
phy_interface_t interface)
{
switch (interface) {
case PHY_INTERFACE_MODE_1000BASEX:
case PHY_INTERFACE_MODE_2500BASEX:
case PHY_INTERFACE_MODE_SGMII:
return LINK_INBAND_DISABLE | LINK_INBAND_ENABLE;
default:
return 0;
}
}
static void mtk_pcs_lynxi_get_state(struct phylink_pcs *pcs,
unsigned int neg_mode,
struct phylink_link_state *state)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
unsigned int bm, adv;
/* Read the BMSR and LPA */
regmap_read(mpcs->regmap, SGMSYS_PCS_CONTROL_1, &bm);
regmap_read(mpcs->regmap, SGMSYS_PCS_ADVERTISE, &adv);
phylink_mii_c22_pcs_decode_state(state, neg_mode,
FIELD_GET(SGMII_BMSR, bm),
FIELD_GET(SGMII_LPA, adv));
}
static int mtk_pcs_config_polarity(struct mtk_pcs_lynxi *mpcs,
phy_interface_t interface)
{
struct fwnode_handle *fwnode = mpcs->fwnode, *pcs_fwnode;
unsigned int pol, default_pol = PHY_POL_NORMAL;
unsigned int val = 0;
int ret;
if (!fwnode)
return 0;
if (fwnode_property_read_bool(fwnode, "mediatek,pnswap"))
default_pol = PHY_POL_INVERT;
pcs_fwnode = fwnode_get_named_child_node(fwnode, "pcs");
ret = phy_get_rx_polarity(pcs_fwnode, phy_modes(interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
if (ret) {
fwnode_handle_put(pcs_fwnode);
return ret;
}
if (pol == PHY_POL_INVERT)
val |= SGMII_PN_SWAP_RX;
ret = phy_get_tx_polarity(pcs_fwnode, phy_modes(interface),
BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT),
default_pol, &pol);
fwnode_handle_put(pcs_fwnode);
if (ret)
return ret;
if (pol == PHY_POL_INVERT)
val |= SGMII_PN_SWAP_TX;
return regmap_update_bits(mpcs->regmap, SGMSYS_QPHY_WRAP_CTRL,
SGMII_PN_SWAP_RX | SGMII_PN_SWAP_TX, val);
}
static int mtk_pcs_lynxi_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
bool mode_changed = false, changed;
unsigned int rgc3, sgm_mode, bmcr;
int advertise, link_timer;
int ret;
advertise = phylink_mii_c22_pcs_encode_advertisement(interface,
advertising);
if (advertise < 0)
return advertise;
/* Clearing IF_MODE_BIT0 switches the PCS to BASE-X mode, and
* we assume that fixes it's speed at bitrate = line rate (in
* other words, 1000Mbps or 2500Mbps).
*/
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode = SGMII_IF_MODE_SGMII;
else
sgm_mode = 0;
if (neg_mode & PHYLINK_PCS_NEG_INBAND)
sgm_mode |= SGMII_REMOTE_FAULT_DIS;
if (neg_mode == PHYLINK_PCS_NEG_INBAND_ENABLED) {
if (interface == PHY_INTERFACE_MODE_SGMII)
sgm_mode |= SGMII_SPEED_DUPLEX_AN;
bmcr = BMCR_ANENABLE;
} else {
bmcr = 0;
}
if (mpcs->interface != interface) {
link_timer = phylink_get_link_timer_ns(interface);
if (link_timer < 0)
return link_timer;
/* PHYA power down */
regmap_set_bits(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL,
SGMII_PHYA_PWD);
/* Reset SGMII PCS state */
regmap_set_bits(mpcs->regmap, SGMSYS_RESERVED_0,
SGMII_SW_RESET);
ret = mtk_pcs_config_polarity(mpcs, interface);
if (ret)
return ret;
if (interface == PHY_INTERFACE_MODE_2500BASEX)
rgc3 = SGMII_PHY_SPEED_3_125G;
else
rgc3 = SGMII_PHY_SPEED_1_25G;
/* Configure the underlying interface speed */
regmap_update_bits(mpcs->regmap, mpcs->ana_rgc3,
SGMII_PHY_SPEED_MASK, rgc3);
/* Setup the link timer */
regmap_write(mpcs->regmap, SGMSYS_PCS_LINK_TIMER,
SGMII_LINK_TIMER_VAL(link_timer));
mpcs->interface = interface;
mode_changed = true;
}
/* Update the advertisement, noting whether it has changed */
regmap_update_bits_check(mpcs->regmap, SGMSYS_PCS_ADVERTISE,
SGMII_ADVERTISE, advertise, &changed);
/* Update the sgmsys mode register */
regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
SGMII_REMOTE_FAULT_DIS | SGMII_SPEED_DUPLEX_AN |
SGMII_IF_MODE_SGMII, sgm_mode);
/* Update the BMCR */
regmap_update_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1,
BMCR_ANENABLE, bmcr);
/* Release PHYA power down state
* Only removing bit SGMII_PHYA_PWD isn't enough.
* There are cases when the SGMII_PHYA_PWD register contains 0x9 which
* prevents SGMII from working. The SGMII still shows link but no traffic
* can flow. Writing 0x0 to the PHYA_PWD register fix the issue. 0x0 was
* taken from a good working state of the SGMII interface.
* Unknown how much the QPHY needs but it is racy without a sleep.
* Tested on mt7622 & mt7986.
*/
usleep_range(50, 100);
regmap_write(mpcs->regmap, SGMSYS_QPHY_PWR_STATE_CTRL, 0);
return changed || mode_changed;
}
static void mtk_pcs_lynxi_restart_an(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
regmap_set_bits(mpcs->regmap, SGMSYS_PCS_CONTROL_1, BMCR_ANRESTART);
}
static void mtk_pcs_lynxi_link_up(struct phylink_pcs *pcs,
unsigned int neg_mode,
phy_interface_t interface, int speed,
int duplex)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
unsigned int sgm_mode;
if (neg_mode != PHYLINK_PCS_NEG_INBAND_ENABLED) {
/* Force the speed and duplex setting */
if (speed == SPEED_10)
sgm_mode = SGMII_SPEED_10;
else if (speed == SPEED_100)
sgm_mode = SGMII_SPEED_100;
else
sgm_mode = SGMII_SPEED_1000;
if (duplex != DUPLEX_FULL)
sgm_mode |= SGMII_DUPLEX_HALF;
regmap_update_bits(mpcs->regmap, SGMSYS_SGMII_MODE,
SGMII_DUPLEX_HALF | SGMII_SPEED_MASK,
sgm_mode);
}
}
static void mtk_pcs_lynxi_disable(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs = pcs_to_mtk_pcs_lynxi(pcs);
mpcs->interface = PHY_INTERFACE_MODE_NA;
}
static const struct phylink_pcs_ops mtk_pcs_lynxi_ops = {
.pcs_inband_caps = mtk_pcs_lynxi_inband_caps,
.pcs_get_state = mtk_pcs_lynxi_get_state,
.pcs_config = mtk_pcs_lynxi_config,
.pcs_an_restart = mtk_pcs_lynxi_restart_an,
.pcs_link_up = mtk_pcs_lynxi_link_up,
.pcs_disable = mtk_pcs_lynxi_disable,
};
struct phylink_pcs *mtk_pcs_lynxi_create(struct device *dev,
struct fwnode_handle *fwnode,
struct regmap *regmap, u32 ana_rgc3)
{
struct mtk_pcs_lynxi *mpcs;
u32 id, ver;
int ret;
ret = regmap_read(regmap, SGMSYS_PCS_DEVICE_ID, &id);
if (ret < 0)
return NULL;
if (id != SGMII_LYNXI_DEV_ID) {
dev_err(dev, "unknown PCS device id %08x\n", id);
return NULL;
}
ret = regmap_read(regmap, SGMSYS_PCS_SCRATCH, &ver);
if (ret < 0)
return NULL;
ver = FIELD_GET(SGMII_DEV_VERSION, ver);
if (ver != 0x1) {
dev_err(dev, "unknown PCS device version %04x\n", ver);
return NULL;
}
dev_dbg(dev, "MediaTek LynxI SGMII PCS (id 0x%08x, ver 0x%04x)\n", id,
ver);
mpcs = kzalloc_obj(*mpcs);
if (!mpcs)
return NULL;
mpcs->ana_rgc3 = ana_rgc3;
mpcs->regmap = regmap;
mpcs->pcs.ops = &mtk_pcs_lynxi_ops;
mpcs->pcs.poll = true;
mpcs->interface = PHY_INTERFACE_MODE_NA;
mpcs->fwnode = fwnode_handle_get(fwnode);
__set_bit(PHY_INTERFACE_MODE_SGMII, mpcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_1000BASEX, mpcs->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_2500BASEX, mpcs->pcs.supported_interfaces);
return &mpcs->pcs;
}
EXPORT_SYMBOL(mtk_pcs_lynxi_create);
void mtk_pcs_lynxi_destroy(struct phylink_pcs *pcs)
{
struct mtk_pcs_lynxi *mpcs;
if (!pcs)
return;
mpcs = pcs_to_mtk_pcs_lynxi(pcs);
fwnode_handle_put(mpcs->fwnode);
kfree(mpcs);
}
EXPORT_SYMBOL(mtk_pcs_lynxi_destroy);
MODULE_DESCRIPTION("MediaTek SGMII library for LynxI");
MODULE_LICENSE("GPL");
+868
View File
@@ -0,0 +1,868 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Schneider Electric
*
* Clément Léger <clement.leger@bootlin.com>
*/
#include <linux/array_size.h>
#include <linux/bits.h>
#include <linux/bitops.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/device/devres.h>
#include <linux/mdio.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/pcs-rzn1-miic.h>
#include <linux/phylink.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
#include <linux/slab.h>
#include <dt-bindings/net/pcs-rzn1-miic.h>
#include <dt-bindings/net/renesas,r9a09g077-pcs-miic.h>
#define MIIC_PRCMD 0x0
#define MIIC_ESID_CODE 0x4
#define MIIC_MODCTRL 0x8
#define MIIC_PHY_LINK 0x14
#define MIIC_CONVCTRL(port) (0x100 + (port) * 4)
#define MIIC_CONVCTRL_CONV_SPEED GENMASK(1, 0)
#define CONV_MODE_10MBPS 0
#define CONV_MODE_100MBPS 1
#define CONV_MODE_1000MBPS 2
#define MIIC_CONVCTRL_CONV_MODE GENMASK(3, 2)
#define CONV_MODE_MII 0
#define CONV_MODE_RMII 1
#define CONV_MODE_RGMII 2
#define MIIC_CONVCTRL_FULLD BIT(8)
#define MIIC_CONVCTRL_RGMII_LINK BIT(12)
#define MIIC_CONVCTRL_RGMII_DUPLEX BIT(13)
#define MIIC_CONVCTRL_RGMII_SPEED GENMASK(15, 14)
#define MIIC_CONVRST 0x114
#define MIIC_CONVRST_PHYIF_RST(port) BIT(port)
#define MIIC_CONVRST_PHYIF_RST_MASK GENMASK(4, 0)
#define MIIC_SWCTRL 0x304
#define MIIC_SWDUPC 0x308
#define MIIC_MODCTRL_CONF_CONV_MAX 6
#define MIIC_MODCTRL_CONF_NONE -1
#define MIIC_MAX_NUM_RSTS 2
/**
* struct modctrl_match - Matching table entry for convctrl configuration
* See section 8.2.1 of manual.
* @mode_cfg: Configuration value for convctrl
* @conv: Configuration of ethernet port muxes. First index is SWITCH_PORTIN,
* then index 1 - 5 are CONV1 - CONV5 for RZ/N1 SoCs. In case
* of RZ/T2H and RZ/N2H SoCs, the first index is SWITCH_PORTIN then
* index 0 - 3 are CONV0 - CONV3.
*/
struct modctrl_match {
u32 mode_cfg;
u8 conv[MIIC_MODCTRL_CONF_CONV_MAX];
};
static struct modctrl_match modctrl_match_table[] = {
{0x0, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}},
{0x1, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}},
{0x2, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}},
{0x3, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}},
{0x8, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}},
{0x9, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}},
{0xA, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}},
{0xB, {MIIC_RTOS_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}},
{0x10, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_SERCOS_PORTB, MIIC_SERCOS_PORTA}},
{0x11, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}},
{0x12, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_ETHERCAT_PORTC, MIIC_ETHERCAT_PORTB, MIIC_ETHERCAT_PORTA}},
{0x13, {MIIC_GMAC2_PORT, MIIC_GMAC1_PORT, MIIC_SWITCH_PORTD,
MIIC_SWITCH_PORTC, MIIC_SWITCH_PORTB, MIIC_SWITCH_PORTA}}
};
static const char * const conf_to_string[] = {
[MIIC_GMAC1_PORT] = "GMAC1_PORT",
[MIIC_GMAC2_PORT] = "GMAC2_PORT",
[MIIC_RTOS_PORT] = "RTOS_PORT",
[MIIC_SERCOS_PORTA] = "SERCOS_PORTA",
[MIIC_SERCOS_PORTB] = "SERCOS_PORTB",
[MIIC_ETHERCAT_PORTA] = "ETHERCAT_PORTA",
[MIIC_ETHERCAT_PORTB] = "ETHERCAT_PORTB",
[MIIC_ETHERCAT_PORTC] = "ETHERCAT_PORTC",
[MIIC_SWITCH_PORTA] = "SWITCH_PORTA",
[MIIC_SWITCH_PORTB] = "SWITCH_PORTB",
[MIIC_SWITCH_PORTC] = "SWITCH_PORTC",
[MIIC_SWITCH_PORTD] = "SWITCH_PORTD",
[MIIC_HSR_PORTA] = "HSR_PORTA",
[MIIC_HSR_PORTB] = "HSR_PORTB",
};
static const char * const index_to_string[] = {
"SWITCH_PORTIN",
"CONV1",
"CONV2",
"CONV3",
"CONV4",
"CONV5",
};
static struct modctrl_match rzt2h_modctrl_match_table[] = {
{0x0, {ETHSS_GMAC0_PORT, ETHSS_ETHSW_PORT0, ETHSS_ETHSW_PORT1,
ETHSS_ETHSW_PORT2, ETHSS_GMAC1_PORT}},
{0x1, {MIIC_MODCTRL_CONF_NONE, ETHSS_ESC_PORT0, ETHSS_ESC_PORT1,
ETHSS_GMAC2_PORT, ETHSS_GMAC1_PORT}},
{0x2, {ETHSS_GMAC0_PORT, ETHSS_ESC_PORT0, ETHSS_ESC_PORT1,
ETHSS_ETHSW_PORT2, ETHSS_GMAC1_PORT}},
{0x3, {MIIC_MODCTRL_CONF_NONE, ETHSS_ESC_PORT0, ETHSS_ESC_PORT1,
ETHSS_ESC_PORT2, ETHSS_GMAC1_PORT}},
{0x4, {ETHSS_GMAC0_PORT, ETHSS_ETHSW_PORT0, ETHSS_ESC_PORT1,
ETHSS_ESC_PORT2, ETHSS_GMAC1_PORT}},
{0x5, {ETHSS_GMAC0_PORT, ETHSS_ETHSW_PORT0, ETHSS_ESC_PORT1,
ETHSS_ETHSW_PORT2, ETHSS_GMAC1_PORT}},
{0x6, {ETHSS_GMAC0_PORT, ETHSS_ETHSW_PORT0, ETHSS_ETHSW_PORT1,
ETHSS_GMAC2_PORT, ETHSS_GMAC1_PORT}},
{0x7, {MIIC_MODCTRL_CONF_NONE, ETHSS_GMAC0_PORT, ETHSS_GMAC1_PORT,
ETHSS_GMAC2_PORT, MIIC_MODCTRL_CONF_NONE}}
};
static const char * const rzt2h_conf_to_string[] = {
[ETHSS_GMAC0_PORT] = "GMAC0_PORT",
[ETHSS_GMAC1_PORT] = "GMAC1_PORT",
[ETHSS_GMAC2_PORT] = "GMAC2_PORT",
[ETHSS_ESC_PORT0] = "ETHERCAT_PORT0",
[ETHSS_ESC_PORT1] = "ETHERCAT_PORT1",
[ETHSS_ESC_PORT2] = "ETHERCAT_PORT2",
[ETHSS_ETHSW_PORT0] = "SWITCH_PORT0",
[ETHSS_ETHSW_PORT1] = "SWITCH_PORT1",
[ETHSS_ETHSW_PORT2] = "SWITCH_PORT2",
};
static const char * const rzt2h_index_to_string[] = {
"SWITCH_PORTIN",
"CONV0",
"CONV1",
"CONV2",
"CONV3",
};
static const char * const rzt2h_reset_ids[] = {
"rst",
"crst",
};
/**
* struct miic_phy_link_cfg - MIIC PHY_LINK configuration
* @mask: Mask of phy_link bits
* @val: Value of phy_link bits
*/
struct miic_phy_link_cfg {
u32 mask;
u32 val;
};
/**
* struct miic - MII converter structure
* @base: base address of the MII converter
* @dev: Device associated to the MII converter
* @lock: Lock used for read-modify-write access
* @rsts: Reset controls for the MII converter
* @of_data: Pointer to OF data
* @link_cfg: MIIC PHY_LINK configuration
*/
struct miic {
void __iomem *base;
struct device *dev;
spinlock_t lock;
struct reset_control_bulk_data rsts[MIIC_MAX_NUM_RSTS];
const struct miic_of_data *of_data;
struct miic_phy_link_cfg link_cfg;
};
enum miic_type {
MIIC_TYPE_RZN1,
MIIC_TYPE_RZT2H,
};
/**
* struct miic_of_data - OF data for MII converter
* @match_table: Matching table for convctrl configuration
* @match_table_count: Number of entries in the matching table
* @conf_conv_count: Number of entries in the conf_conv array
* @conf_to_string: String representations of the configuration values
* @conf_to_string_count: Number of entries in the conf_to_string array
* @index_to_string: String representations of the index values
* @index_to_string_count: Number of entries in the index_to_string array
* @miic_port_start: MIIC port start number
* @miic_port_max: Maximum MIIC supported
* @sw_mode_mask: Switch mode mask
* @reset_ids: Reset names array
* @reset_count: Number of entries in the reset_ids array
* @init_unlock_lock_regs: Flag to indicate if registers need to be unlocked
* before access.
* @miic_write: Function pointer to write a value to a MIIC register
* @type: Type of MIIC
*/
struct miic_of_data {
struct modctrl_match *match_table;
u8 match_table_count;
u8 conf_conv_count;
const char * const *conf_to_string;
u8 conf_to_string_count;
const char * const *index_to_string;
u8 index_to_string_count;
u8 miic_port_start;
u8 miic_port_max;
u8 sw_mode_mask;
const char * const *reset_ids;
u8 reset_count;
bool init_unlock_lock_regs;
void (*miic_write)(struct miic *miic, int offset, u32 value);
enum miic_type type;
};
/**
* struct miic_port - Per port MII converter struct
* @miic: backiling to MII converter structure
* @pcs: PCS structure associated to the port
* @port: port number
* @interface: interface mode of the port
*/
struct miic_port {
struct miic *miic;
struct phylink_pcs pcs;
int port;
phy_interface_t interface;
};
static struct miic_port *phylink_pcs_to_miic_port(struct phylink_pcs *pcs)
{
return container_of(pcs, struct miic_port, pcs);
}
static void miic_unlock_regs(struct miic *miic)
{
/* Unprotect register writes */
writel(0x00A5, miic->base + MIIC_PRCMD);
writel(0x0001, miic->base + MIIC_PRCMD);
writel(0xFFFE, miic->base + MIIC_PRCMD);
writel(0x0001, miic->base + MIIC_PRCMD);
}
static void miic_lock_regs(struct miic *miic)
{
/* Protect register writes */
writel(0x0000, miic->base + MIIC_PRCMD);
}
static void miic_reg_writel_unlocked(struct miic *miic, int offset, u32 value)
{
writel(value, miic->base + offset);
}
static void miic_reg_writel_locked(struct miic *miic, int offset, u32 value)
{
miic_unlock_regs(miic);
writel(value, miic->base + offset);
miic_lock_regs(miic);
}
static void miic_reg_writel(struct miic *miic, int offset, u32 value)
{
miic->of_data->miic_write(miic, offset, value);
}
static u32 miic_reg_readl(struct miic *miic, int offset)
{
return readl(miic->base + offset);
}
static void miic_reg_rmw(struct miic *miic, int offset, u32 mask, u32 val)
{
u32 reg;
spin_lock(&miic->lock);
reg = miic_reg_readl(miic, offset);
reg &= ~mask;
reg |= val;
miic_reg_writel(miic, offset, reg);
spin_unlock(&miic->lock);
}
static void miic_converter_enable(struct miic *miic, int port, int enable)
{
u32 val = 0;
if (enable)
val = MIIC_CONVRST_PHYIF_RST(port);
miic_reg_rmw(miic, MIIC_CONVRST, MIIC_CONVRST_PHYIF_RST(port), val);
}
static int miic_config(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface,
const unsigned long *advertising, bool permit)
{
struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs);
struct miic *miic = miic_port->miic;
u32 speed, conv_mode, val, mask;
int port = miic_port->port;
switch (interface) {
case PHY_INTERFACE_MODE_RMII:
conv_mode = CONV_MODE_RMII;
speed = CONV_MODE_100MBPS;
break;
case PHY_INTERFACE_MODE_RGMII:
case PHY_INTERFACE_MODE_RGMII_ID:
case PHY_INTERFACE_MODE_RGMII_TXID:
case PHY_INTERFACE_MODE_RGMII_RXID:
conv_mode = CONV_MODE_RGMII;
speed = CONV_MODE_1000MBPS;
break;
case PHY_INTERFACE_MODE_MII:
conv_mode = CONV_MODE_MII;
/* When in MII mode, speed should be set to 0 (which is actually
* CONV_MODE_10MBPS)
*/
speed = CONV_MODE_10MBPS;
break;
default:
return -EOPNOTSUPP;
}
val = FIELD_PREP(MIIC_CONVCTRL_CONV_MODE, conv_mode);
mask = MIIC_CONVCTRL_CONV_MODE;
/* Update speed only if we are going to change the interface because
* the link might already be up and it would break it if the speed is
* changed.
*/
if (interface != miic_port->interface) {
val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, speed);
mask |= MIIC_CONVCTRL_CONV_SPEED;
miic_port->interface = interface;
}
miic_reg_rmw(miic, MIIC_CONVCTRL(port), mask, val);
miic_converter_enable(miic, miic_port->port, 1);
return 0;
}
static void miic_link_up(struct phylink_pcs *pcs, unsigned int neg_mode,
phy_interface_t interface, int speed, int duplex)
{
struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs);
struct miic *miic = miic_port->miic;
u32 conv_speed = 0, val = 0;
int port = miic_port->port;
if (duplex == DUPLEX_FULL)
val |= MIIC_CONVCTRL_FULLD;
/* No speed in MII through-mode */
if (interface != PHY_INTERFACE_MODE_MII) {
switch (speed) {
case SPEED_1000:
conv_speed = CONV_MODE_1000MBPS;
break;
case SPEED_100:
conv_speed = CONV_MODE_100MBPS;
break;
case SPEED_10:
conv_speed = CONV_MODE_10MBPS;
break;
default:
return;
}
}
val |= FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, conv_speed);
miic_reg_rmw(miic, MIIC_CONVCTRL(port),
(MIIC_CONVCTRL_CONV_SPEED | MIIC_CONVCTRL_FULLD), val);
}
static int miic_pre_init(struct phylink_pcs *pcs)
{
struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs);
struct miic *miic = miic_port->miic;
u32 val, mask;
/* Start RX clock if required */
if (pcs->rxc_always_on) {
/* In MII through mode, the clock signals will be driven by the
* external PHY, which might not be initialized yet. Set RMII
* as default mode to ensure that a reference clock signal is
* generated.
*/
miic_port->interface = PHY_INTERFACE_MODE_RMII;
val = FIELD_PREP(MIIC_CONVCTRL_CONV_MODE, CONV_MODE_RMII) |
FIELD_PREP(MIIC_CONVCTRL_CONV_SPEED, CONV_MODE_100MBPS);
mask = MIIC_CONVCTRL_CONV_MODE | MIIC_CONVCTRL_CONV_SPEED;
miic_reg_rmw(miic, MIIC_CONVCTRL(miic_port->port), mask, val);
miic_converter_enable(miic, miic_port->port, 1);
}
return 0;
}
static const struct phylink_pcs_ops miic_phylink_ops = {
.pcs_config = miic_config,
.pcs_link_up = miic_link_up,
.pcs_pre_init = miic_pre_init,
};
struct phylink_pcs *miic_create(struct device *dev, struct device_node *np)
{
const struct miic_of_data *of_data;
struct platform_device *pdev;
struct miic_port *miic_port;
struct device_node *pcs_np;
struct miic *miic;
u32 port;
if (!of_device_is_available(np))
return ERR_PTR(-ENODEV);
if (of_property_read_u32(np, "reg", &port))
return ERR_PTR(-EINVAL);
/* The PCS pdev is attached to the parent node */
pcs_np = of_get_parent(np);
if (!pcs_np)
return ERR_PTR(-ENODEV);
if (!of_device_is_available(pcs_np)) {
of_node_put(pcs_np);
return ERR_PTR(-ENODEV);
}
pdev = of_find_device_by_node(pcs_np);
of_node_put(pcs_np);
if (!pdev || !platform_get_drvdata(pdev)) {
if (pdev)
put_device(&pdev->dev);
return ERR_PTR(-EPROBE_DEFER);
}
miic = platform_get_drvdata(pdev);
of_data = miic->of_data;
if (port > of_data->miic_port_max || port < of_data->miic_port_start) {
put_device(&pdev->dev);
return ERR_PTR(-EINVAL);
}
miic_port = kzalloc_obj(*miic_port);
if (!miic_port) {
put_device(&pdev->dev);
return ERR_PTR(-ENOMEM);
}
device_link_add(dev, miic->dev, DL_FLAG_AUTOREMOVE_CONSUMER);
put_device(&pdev->dev);
miic_port->miic = miic;
miic_port->port = port - of_data->miic_port_start;
miic_port->pcs.ops = &miic_phylink_ops;
phy_interface_set_rgmii(miic_port->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_RMII, miic_port->pcs.supported_interfaces);
__set_bit(PHY_INTERFACE_MODE_MII, miic_port->pcs.supported_interfaces);
return &miic_port->pcs;
}
EXPORT_SYMBOL(miic_create);
void miic_destroy(struct phylink_pcs *pcs)
{
struct miic_port *miic_port = phylink_pcs_to_miic_port(pcs);
miic_converter_enable(miic_port->miic, miic_port->port, 0);
kfree(miic_port);
}
EXPORT_SYMBOL(miic_destroy);
static int miic_init_hw(struct miic *miic, u32 cfg_mode)
{
u8 sw_mode_mask = miic->of_data->sw_mode_mask;
int port;
/* Unlock write access to accessory registers (cf datasheet). If this
* is going to be used in conjunction with the Cortex-M3, this sequence
* will have to be moved in register write
*/
if (miic->of_data->init_unlock_lock_regs)
miic_unlock_regs(miic);
/* TODO: Replace with FIELD_PREP() when compile-time constant
* restriction is lifted. Currently __ffs() returns 0 for sw_mode_mask.
*/
miic_reg_writel(miic, MIIC_MODCTRL,
((cfg_mode << __ffs(sw_mode_mask)) & sw_mode_mask));
for (port = 0; port < miic->of_data->miic_port_max; port++) {
miic_converter_enable(miic, port, 0);
/* Disable speed/duplex control from these registers, datasheet
* says switch registers should be used to setup switch port
* speed and duplex.
*/
miic_reg_writel(miic, MIIC_SWCTRL, 0x0);
miic_reg_writel(miic, MIIC_SWDUPC, 0x0);
}
return 0;
}
static bool miic_modctrl_match(s8 *table_val, s8 *dt_val, u8 count)
{
int i;
for (i = 0; i < count; i++) {
if (dt_val[i] == MIIC_MODCTRL_CONF_NONE)
continue;
if (dt_val[i] != table_val[i])
return false;
}
return true;
}
static void miic_dump_conf(struct miic *miic, s8 *conf)
{
const struct miic_of_data *of_data = miic->of_data;
const char *conf_name;
int i;
for (i = 0; i < of_data->conf_conv_count; i++) {
if (conf[i] != MIIC_MODCTRL_CONF_NONE)
conf_name = of_data->conf_to_string[conf[i]];
else
conf_name = "NONE";
dev_err(miic->dev, "%s: %s\n",
of_data->index_to_string[i], conf_name);
}
}
static int miic_match_dt_conf(struct miic *miic, s8 *dt_val, u32 *mode_cfg)
{
const struct miic_of_data *of_data = miic->of_data;
struct modctrl_match *table_entry;
int i;
for (i = 0; i < of_data->match_table_count; i++) {
table_entry = &of_data->match_table[i];
if (miic_modctrl_match(table_entry->conv, dt_val,
miic->of_data->conf_conv_count)) {
*mode_cfg = table_entry->mode_cfg;
return 0;
}
}
dev_err(miic->dev, "Failed to apply requested configuration\n");
miic_dump_conf(miic, dt_val);
return -EINVAL;
}
static void miic_configure_phy_link(struct miic *miic, u32 conf,
u32 port, bool active_low)
{
bool polarity_active_high;
u32 mask, shift;
/* determine shift and polarity for this conf */
if (miic->of_data->type == MIIC_TYPE_RZN1) {
switch (conf) {
/* switch ports => bits [3:0] (shift 0), active when low */
case MIIC_SWITCH_PORTA:
case MIIC_SWITCH_PORTB:
case MIIC_SWITCH_PORTC:
case MIIC_SWITCH_PORTD:
shift = 0;
polarity_active_high = false;
break;
/* EtherCAT ports => bits [7:4] (shift 4), active when high */
case MIIC_ETHERCAT_PORTA:
case MIIC_ETHERCAT_PORTB:
case MIIC_ETHERCAT_PORTC:
shift = 4;
polarity_active_high = true;
break;
/* Sercos ports => bits [11:8] (shift 8), active when high */
case MIIC_SERCOS_PORTA:
case MIIC_SERCOS_PORTB:
shift = 8;
polarity_active_high = true;
break;
default:
return;
}
} else {
switch (conf) {
/* ETHSW ports => bits [3:0] (shift 0), active when low */
case ETHSS_ETHSW_PORT0:
case ETHSS_ETHSW_PORT1:
case ETHSS_ETHSW_PORT2:
shift = 0;
polarity_active_high = false;
break;
/* ESC ports => bits [7:4] (shift 4), active when high */
case ETHSS_ESC_PORT0:
case ETHSS_ESC_PORT1:
case ETHSS_ESC_PORT2:
shift = 4;
polarity_active_high = true;
break;
default:
return;
}
}
mask = BIT(port + shift);
miic->link_cfg.mask |= mask;
if (polarity_active_high != active_low)
miic->link_cfg.val |= mask;
else
miic->link_cfg.val &= ~mask;
}
static int miic_parse_dt(struct miic *miic, u32 *mode_cfg)
{
struct device_node *np = miic->dev->of_node;
struct device_node *conv;
bool active_low;
int port, ret;
s8 *dt_val;
u32 conf;
dt_val = kmalloc_objs(*dt_val, miic->of_data->conf_conv_count);
if (!dt_val)
return -ENOMEM;
memset(dt_val, MIIC_MODCTRL_CONF_NONE, sizeof(*dt_val));
if (of_property_read_u32(np, "renesas,miic-switch-portin", &conf) == 0)
dt_val[0] = conf;
for_each_available_child_of_node(np, conv) {
if (of_property_read_u32(conv, "reg", &port))
continue;
if (of_property_read_u32(conv, "renesas,miic-input", &conf))
continue;
/* Adjust for 0 based index */
dt_val[port + !miic->of_data->miic_port_start] = conf;
active_low = of_property_read_bool(conv, "renesas,miic-phy-link-active-low");
miic_configure_phy_link(miic, conf, port, active_low);
}
ret = miic_match_dt_conf(miic, dt_val, mode_cfg);
kfree(dt_val);
return ret;
}
static void miic_reset_control_bulk_assert(void *data)
{
struct miic *miic = data;
int ret;
ret = reset_control_bulk_assert(miic->of_data->reset_count, miic->rsts);
if (ret)
dev_err(miic->dev, "failed to assert reset lines\n");
}
static int miic_reset_control_init(struct miic *miic)
{
const struct miic_of_data *of_data = miic->of_data;
struct device *dev = miic->dev;
int ret;
u8 i;
if (!of_data->reset_count)
return 0;
for (i = 0; i < of_data->reset_count; i++)
miic->rsts[i].id = of_data->reset_ids[i];
ret = devm_reset_control_bulk_get_exclusive(dev, of_data->reset_count,
miic->rsts);
if (ret)
return dev_err_probe(dev, ret,
"failed to get bulk reset lines\n");
ret = reset_control_bulk_deassert(of_data->reset_count, miic->rsts);
if (ret)
return dev_err_probe(dev, ret,
"failed to deassert reset lines\n");
ret = devm_add_action_or_reset(dev, miic_reset_control_bulk_assert,
miic);
if (ret)
return dev_err_probe(dev, ret, "failed to add reset action\n");
return 0;
}
static int miic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct miic *miic;
u32 mode_cfg;
int ret;
miic = devm_kzalloc(dev, sizeof(*miic), GFP_KERNEL);
if (!miic)
return -ENOMEM;
miic->of_data = of_device_get_match_data(dev);
miic->dev = dev;
ret = miic_parse_dt(miic, &mode_cfg);
if (ret < 0)
return ret;
spin_lock_init(&miic->lock);
miic->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(miic->base))
return PTR_ERR(miic->base);
ret = miic_reset_control_init(miic);
if (ret)
return ret;
ret = devm_pm_runtime_enable(dev);
if (ret < 0)
return ret;
ret = pm_runtime_resume_and_get(dev);
if (ret < 0)
return ret;
ret = miic_init_hw(miic, mode_cfg);
if (ret)
goto disable_runtime_pm;
miic_reg_rmw(miic, MIIC_PHY_LINK, miic->link_cfg.mask, miic->link_cfg.val);
/* miic_create() relies on that fact that data are attached to the
* platform device to determine if the driver is ready so this needs to
* be the last thing to be done after everything is initialized
* properly.
*/
platform_set_drvdata(pdev, miic);
return 0;
disable_runtime_pm:
pm_runtime_put(dev);
return ret;
}
static void miic_remove(struct platform_device *pdev)
{
pm_runtime_put(&pdev->dev);
}
static struct miic_of_data rzn1_miic_of_data = {
.match_table = modctrl_match_table,
.match_table_count = ARRAY_SIZE(modctrl_match_table),
.conf_conv_count = MIIC_MODCTRL_CONF_CONV_MAX,
.conf_to_string = conf_to_string,
.conf_to_string_count = ARRAY_SIZE(conf_to_string),
.index_to_string = index_to_string,
.index_to_string_count = ARRAY_SIZE(index_to_string),
.miic_port_start = 1,
.miic_port_max = 5,
.sw_mode_mask = GENMASK(4, 0),
.init_unlock_lock_regs = true,
.miic_write = miic_reg_writel_unlocked,
.type = MIIC_TYPE_RZN1,
};
static struct miic_of_data rzt2h_miic_of_data = {
.match_table = rzt2h_modctrl_match_table,
.match_table_count = ARRAY_SIZE(rzt2h_modctrl_match_table),
.conf_conv_count = 5,
.conf_to_string = rzt2h_conf_to_string,
.conf_to_string_count = ARRAY_SIZE(rzt2h_conf_to_string),
.index_to_string = rzt2h_index_to_string,
.index_to_string_count = ARRAY_SIZE(rzt2h_index_to_string),
.miic_port_start = 0,
.miic_port_max = 4,
.sw_mode_mask = GENMASK(2, 0),
.reset_ids = rzt2h_reset_ids,
.reset_count = ARRAY_SIZE(rzt2h_reset_ids),
.miic_write = miic_reg_writel_locked,
.type = MIIC_TYPE_RZT2H,
};
static const struct of_device_id miic_of_mtable[] = {
{ .compatible = "renesas,r9a09g077-miic", .data = &rzt2h_miic_of_data },
{ .compatible = "renesas,rzn1-miic", .data = &rzn1_miic_of_data },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, miic_of_mtable);
static struct platform_driver miic_driver = {
.driver = {
.name = "rzn1_miic",
.suppress_bind_attrs = true,
.of_match_table = miic_of_mtable,
},
.probe = miic_probe,
.remove = miic_remove,
};
module_platform_driver(miic_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Renesas MII converter PCS driver");
MODULE_AUTHOR("Clément Léger <clement.leger@bootlin.com>");
+177
View File
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright 2021 NXP
*/
#include <linux/pcs/pcs-xpcs.h>
#include "pcs-xpcs.h"
/* LANE_DRIVER1_0 register */
#define SJA1110_LANE_DRIVER1_0 0x8038
#define SJA1110_TXDRV(x) (((x) << 12) & GENMASK(14, 12))
/* LANE_DRIVER2_0 register */
#define SJA1110_LANE_DRIVER2_0 0x803a
#define SJA1110_TXDRVTRIM_LSB(x) ((x) & GENMASK_ULL(15, 0))
/* LANE_DRIVER2_1 register */
#define SJA1110_LANE_DRIVER2_1 0x803b
#define SJA1110_LANE_DRIVER2_1_RSV BIT(9)
#define SJA1110_TXDRVTRIM_MSB(x) (((x) & GENMASK_ULL(23, 16)) >> 16)
/* LANE_TRIM register */
#define SJA1110_LANE_TRIM 0x8040
#define SJA1110_TXTEN BIT(11)
#define SJA1110_TXRTRIM(x) (((x) << 8) & GENMASK(10, 8))
#define SJA1110_TXPLL_BWSEL BIT(7)
#define SJA1110_RXTEN BIT(6)
#define SJA1110_RXRTRIM(x) (((x) << 3) & GENMASK(5, 3))
#define SJA1110_CDR_GAIN BIT(2)
#define SJA1110_ACCOUPLE_RXVCM_EN BIT(0)
/* LANE_DATAPATH_1 register */
#define SJA1110_LANE_DATAPATH_1 0x8037
/* POWERDOWN_ENABLE register */
#define SJA1110_POWERDOWN_ENABLE 0x8041
#define SJA1110_TXPLL_PD BIT(12)
#define SJA1110_TXPD BIT(11)
#define SJA1110_RXPKDETEN BIT(10)
#define SJA1110_RXCH_PD BIT(9)
#define SJA1110_RXBIAS_PD BIT(8)
#define SJA1110_RESET_SER_EN BIT(7)
#define SJA1110_RESET_SER BIT(6)
#define SJA1110_RESET_DES BIT(5)
#define SJA1110_RCVEN BIT(4)
/* RXPLL_CTRL0 register */
#define SJA1110_RXPLL_CTRL0 0x8065
#define SJA1110_RXPLL_FBDIV(x) (((x) << 2) & GENMASK(9, 2))
/* RXPLL_CTRL1 register */
#define SJA1110_RXPLL_CTRL1 0x8066
#define SJA1110_RXPLL_REFDIV(x) ((x) & GENMASK(4, 0))
/* TXPLL_CTRL0 register */
#define SJA1110_TXPLL_CTRL0 0x806d
#define SJA1110_TXPLL_FBDIV(x) ((x) & GENMASK(11, 0))
/* TXPLL_CTRL1 register */
#define SJA1110_TXPLL_CTRL1 0x806e
#define SJA1110_TXPLL_REFDIV(x) ((x) & GENMASK(5, 0))
/* RX_DATA_DETECT register */
#define SJA1110_RX_DATA_DETECT 0x8045
/* RX_CDR_CTLE register */
#define SJA1110_RX_CDR_CTLE 0x8042
/* In NXP SJA1105, the PCS is integrated with a PMA that has the TX lane
* polarity inverted by default (PLUS is MINUS, MINUS is PLUS). To obtain
* normal non-inverted behavior, the TX lane polarity must be inverted in the
* PCS, via the DIGITAL_CONTROL_2 register.
*/
int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs)
{
return xpcs_write(xpcs, MDIO_MMD_VEND2, DW_VR_MII_DIG_CTRL2,
DW_VR_MII_DIG_CTRL2_TX_POL_INV);
}
static int nxp_sja1110_pma_config(struct dw_xpcs *xpcs,
u16 txpll_fbdiv, u16 txpll_refdiv,
u16 rxpll_fbdiv, u16 rxpll_refdiv,
u16 rx_cdr_ctle)
{
u16 val;
int ret;
/* Program TX PLL feedback divider and reference divider settings for
* correct oscillation frequency.
*/
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL0,
SJA1110_TXPLL_FBDIV(txpll_fbdiv));
if (ret < 0)
return ret;
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_TXPLL_CTRL1,
SJA1110_TXPLL_REFDIV(txpll_refdiv));
if (ret < 0)
return ret;
/* Program transmitter amplitude and disable amplitude trimming */
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DRIVER1_0,
SJA1110_TXDRV(0x5));
if (ret < 0)
return ret;
val = SJA1110_TXDRVTRIM_LSB(0xffffffull);
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DRIVER2_0, val);
if (ret < 0)
return ret;
val = SJA1110_TXDRVTRIM_MSB(0xffffffull) | SJA1110_LANE_DRIVER2_1_RSV;
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DRIVER2_1, val);
if (ret < 0)
return ret;
/* Enable input and output resistor terminations for low BER. */
val = SJA1110_ACCOUPLE_RXVCM_EN | SJA1110_CDR_GAIN |
SJA1110_RXRTRIM(4) | SJA1110_RXTEN | SJA1110_TXPLL_BWSEL |
SJA1110_TXRTRIM(3) | SJA1110_TXTEN;
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_TRIM, val);
if (ret < 0)
return ret;
/* Select PCS as transmitter data source. */
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_LANE_DATAPATH_1, 0);
if (ret < 0)
return ret;
/* Program RX PLL feedback divider and reference divider for correct
* oscillation frequency.
*/
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL0,
SJA1110_RXPLL_FBDIV(rxpll_fbdiv));
if (ret < 0)
return ret;
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RXPLL_CTRL1,
SJA1110_RXPLL_REFDIV(rxpll_refdiv));
if (ret < 0)
return ret;
/* Program threshold for receiver signal detector.
* Enable control of RXPLL by receiver signal detector to disable RXPLL
* when an input signal is not present.
*/
ret = xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RX_DATA_DETECT, 0x0005);
if (ret < 0)
return ret;
/* Enable TX and RX PLLs and circuits.
* Release reset of PMA to enable data flow to/from PCS.
*/
ret = xpcs_modify(xpcs, MDIO_MMD_VEND2, SJA1110_POWERDOWN_ENABLE,
SJA1110_TXPLL_PD | SJA1110_TXPD | SJA1110_RXCH_PD |
SJA1110_RXBIAS_PD | SJA1110_RESET_SER_EN |
SJA1110_RESET_SER | SJA1110_RESET_DES |
SJA1110_RXPKDETEN | SJA1110_RCVEN,
SJA1110_RXPKDETEN | SJA1110_RCVEN);
if (ret < 0)
return ret;
/* Program continuous-time linear equalizer (CTLE) settings. */
return xpcs_write(xpcs, MDIO_MMD_VEND2, SJA1110_RX_CDR_CTLE,
rx_cdr_ctle);
}
int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs)
{
return nxp_sja1110_pma_config(xpcs, 0x19, 0x1, 0x19, 0x1, 0x212a);
}
int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs)
{
return nxp_sja1110_pma_config(xpcs, 0x7d, 0x2, 0x7d, 0x2, 0x732a);
}
+457
View File
@@ -0,0 +1,457 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Synopsys DesignWare XPCS platform device driver
*
* Copyright (C) 2024 Serge Semin
*/
#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mdio.h>
#include <linux/module.h>
#include <linux/pcs/pcs-xpcs.h>
#include <linux/phy.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/property.h>
#include <linux/sizes.h>
#include "pcs-xpcs.h"
/* Page select register for the indirect MMIO CSRs access */
#define DW_VR_CSR_VIEWPORT 0xff
struct dw_xpcs_plat {
struct platform_device *pdev;
struct mii_bus *bus;
bool reg_indir;
int reg_width;
void __iomem *reg_base;
struct clk *cclk;
};
static ptrdiff_t xpcs_mmio_addr_format(int dev, int reg)
{
return FIELD_PREP(0x1f0000, dev) | FIELD_PREP(0xffff, reg);
}
static u16 xpcs_mmio_addr_page(ptrdiff_t csr)
{
return FIELD_GET(0x1fff00, csr);
}
static ptrdiff_t xpcs_mmio_addr_offset(ptrdiff_t csr)
{
return FIELD_GET(0xff, csr);
}
static int xpcs_mmio_read_reg_indirect(struct dw_xpcs_plat *pxpcs,
int dev, int reg)
{
ptrdiff_t csr, ofs;
u16 page;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
page = xpcs_mmio_addr_page(csr);
ofs = xpcs_mmio_addr_offset(csr);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
writel(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 2));
ret = readl(pxpcs->reg_base + (ofs << 2)) & 0xffff;
break;
default:
writew(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 1));
ret = readw(pxpcs->reg_base + (ofs << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return ret;
}
static int xpcs_mmio_write_reg_indirect(struct dw_xpcs_plat *pxpcs,
int dev, int reg, u16 val)
{
ptrdiff_t csr, ofs;
u16 page;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
page = xpcs_mmio_addr_page(csr);
ofs = xpcs_mmio_addr_offset(csr);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
writel(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 2));
writel(val, pxpcs->reg_base + (ofs << 2));
break;
default:
writew(page, pxpcs->reg_base + (DW_VR_CSR_VIEWPORT << 1));
writew(val, pxpcs->reg_base + (ofs << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return 0;
}
static int xpcs_mmio_read_reg_direct(struct dw_xpcs_plat *pxpcs,
int dev, int reg)
{
ptrdiff_t csr;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
ret = readl(pxpcs->reg_base + (csr << 2)) & 0xffff;
break;
default:
ret = readw(pxpcs->reg_base + (csr << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return ret;
}
static int xpcs_mmio_write_reg_direct(struct dw_xpcs_plat *pxpcs,
int dev, int reg, u16 val)
{
ptrdiff_t csr;
int ret;
csr = xpcs_mmio_addr_format(dev, reg);
ret = pm_runtime_resume_and_get(&pxpcs->pdev->dev);
if (ret)
return ret;
switch (pxpcs->reg_width) {
case 4:
writel(val, pxpcs->reg_base + (csr << 2));
break;
default:
writew(val, pxpcs->reg_base + (csr << 1));
break;
}
pm_runtime_put(&pxpcs->pdev->dev);
return 0;
}
static int xpcs_mmio_read_c22(struct mii_bus *bus, int addr, int reg)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_read_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg);
else
return xpcs_mmio_read_reg_direct(pxpcs, MDIO_MMD_VEND2, reg);
}
static int xpcs_mmio_write_c22(struct mii_bus *bus, int addr, int reg, u16 val)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_write_reg_indirect(pxpcs, MDIO_MMD_VEND2, reg, val);
else
return xpcs_mmio_write_reg_direct(pxpcs, MDIO_MMD_VEND2, reg, val);
}
static int xpcs_mmio_read_c45(struct mii_bus *bus, int addr, int dev, int reg)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_read_reg_indirect(pxpcs, dev, reg);
else
return xpcs_mmio_read_reg_direct(pxpcs, dev, reg);
}
static int xpcs_mmio_write_c45(struct mii_bus *bus, int addr, int dev,
int reg, u16 val)
{
struct dw_xpcs_plat *pxpcs = bus->priv;
if (addr != 0)
return -ENODEV;
if (pxpcs->reg_indir)
return xpcs_mmio_write_reg_indirect(pxpcs, dev, reg, val);
else
return xpcs_mmio_write_reg_direct(pxpcs, dev, reg, val);
}
static struct dw_xpcs_plat *xpcs_plat_create_data(struct platform_device *pdev)
{
struct dw_xpcs_plat *pxpcs;
pxpcs = devm_kzalloc(&pdev->dev, sizeof(*pxpcs), GFP_KERNEL);
if (!pxpcs)
return ERR_PTR(-ENOMEM);
pxpcs->pdev = pdev;
dev_set_drvdata(&pdev->dev, pxpcs);
return pxpcs;
}
static int xpcs_plat_init_res(struct dw_xpcs_plat *pxpcs)
{
struct platform_device *pdev = pxpcs->pdev;
struct device *dev = &pdev->dev;
resource_size_t spc_size;
struct resource *res;
if (!device_property_read_u32(dev, "reg-io-width", &pxpcs->reg_width)) {
if (pxpcs->reg_width != 2 && pxpcs->reg_width != 4) {
dev_err(dev, "Invalid reg-space data width\n");
return -EINVAL;
}
} else {
pxpcs->reg_width = 2;
}
res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "direct") ?:
platform_get_resource_byname(pdev, IORESOURCE_MEM, "indirect");
if (!res) {
dev_err(dev, "No reg-space found\n");
return -EINVAL;
}
if (!strcmp(res->name, "indirect"))
pxpcs->reg_indir = true;
if (pxpcs->reg_indir)
spc_size = pxpcs->reg_width * SZ_256;
else
spc_size = pxpcs->reg_width * SZ_2M;
if (resource_size(res) < spc_size) {
dev_err(dev, "Invalid reg-space size\n");
return -EINVAL;
}
pxpcs->reg_base = devm_ioremap_resource(dev, res);
if (IS_ERR(pxpcs->reg_base)) {
dev_err(dev, "Failed to map reg-space\n");
return PTR_ERR(pxpcs->reg_base);
}
return 0;
}
static int xpcs_plat_init_clk(struct dw_xpcs_plat *pxpcs)
{
struct device *dev = &pxpcs->pdev->dev;
int ret;
pxpcs->cclk = devm_clk_get_optional(dev, "csr");
if (IS_ERR(pxpcs->cclk))
return dev_err_probe(dev, PTR_ERR(pxpcs->cclk),
"Failed to get CSR clock\n");
pm_runtime_set_active(dev);
ret = devm_pm_runtime_enable(dev);
if (ret) {
dev_err(dev, "Failed to enable runtime-PM\n");
return ret;
}
return 0;
}
static int xpcs_plat_init_bus(struct dw_xpcs_plat *pxpcs)
{
struct device *dev = &pxpcs->pdev->dev;
static atomic_t id = ATOMIC_INIT(-1);
int ret;
pxpcs->bus = devm_mdiobus_alloc_size(dev, 0);
if (!pxpcs->bus)
return -ENOMEM;
pxpcs->bus->name = "DW XPCS MCI/APB3";
pxpcs->bus->read = xpcs_mmio_read_c22;
pxpcs->bus->write = xpcs_mmio_write_c22;
pxpcs->bus->read_c45 = xpcs_mmio_read_c45;
pxpcs->bus->write_c45 = xpcs_mmio_write_c45;
pxpcs->bus->phy_mask = ~0;
pxpcs->bus->parent = dev;
pxpcs->bus->priv = pxpcs;
snprintf(pxpcs->bus->id, MII_BUS_ID_SIZE,
"dwxpcs-%x", atomic_inc_return(&id));
/* MDIO-bus here serves as just a back-end engine abstracting out
* the MDIO and MCI/APB3 IO interfaces utilized for the DW XPCS CSRs
* access.
*/
ret = devm_mdiobus_register(dev, pxpcs->bus);
if (ret) {
dev_err(dev, "Failed to create MDIO bus\n");
return ret;
}
return 0;
}
/* Note there is no need in the next function antagonist because the MDIO-bus
* de-registration will effectively remove and destroy all the MDIO-devices
* registered on the bus.
*/
static int xpcs_plat_init_dev(struct dw_xpcs_plat *pxpcs)
{
struct device *dev = &pxpcs->pdev->dev;
struct mdio_device *mdiodev;
int ret;
/* There is a single memory-mapped DW XPCS device */
mdiodev = mdio_device_create(pxpcs->bus, 0);
if (IS_ERR(mdiodev))
return PTR_ERR(mdiodev);
/* Associate the FW-node with the device structure so it can be looked
* up later. Make sure DD-core is aware of the OF-node being re-used.
*/
device_set_node(&mdiodev->dev, fwnode_handle_get(dev_fwnode(dev)));
dev_set_of_node_reused(&mdiodev->dev);
/* Pass the data further so the DW XPCS driver core could use it */
mdiodev->dev.platform_data = (void *)device_get_match_data(dev);
ret = mdio_device_register(mdiodev);
if (ret) {
dev_err(dev, "Failed to register MDIO device\n");
goto err_clean_data;
}
return 0;
err_clean_data:
mdiodev->dev.platform_data = NULL;
mdio_device_free(mdiodev);
return ret;
}
static int xpcs_plat_probe(struct platform_device *pdev)
{
struct dw_xpcs_plat *pxpcs;
int ret;
pxpcs = xpcs_plat_create_data(pdev);
if (IS_ERR(pxpcs))
return PTR_ERR(pxpcs);
ret = xpcs_plat_init_res(pxpcs);
if (ret)
return ret;
ret = xpcs_plat_init_clk(pxpcs);
if (ret)
return ret;
ret = xpcs_plat_init_bus(pxpcs);
if (ret)
return ret;
ret = xpcs_plat_init_dev(pxpcs);
if (ret)
return ret;
return 0;
}
static int __maybe_unused xpcs_plat_pm_runtime_suspend(struct device *dev)
{
struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev);
clk_disable_unprepare(pxpcs->cclk);
return 0;
}
static int __maybe_unused xpcs_plat_pm_runtime_resume(struct device *dev)
{
struct dw_xpcs_plat *pxpcs = dev_get_drvdata(dev);
return clk_prepare_enable(pxpcs->cclk);
}
static const struct dev_pm_ops xpcs_plat_pm_ops = {
SET_RUNTIME_PM_OPS(xpcs_plat_pm_runtime_suspend,
xpcs_plat_pm_runtime_resume,
NULL)
};
DW_XPCS_INFO_DECLARE(xpcs_generic, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_ID_NATIVE);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen1_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN1_3G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_3G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen2_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN2_6G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_3g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_3G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen4_6g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN4_6G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_10g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_10G_ID);
DW_XPCS_INFO_DECLARE(xpcs_pma_gen5_12g, DW_XPCS_ID_NATIVE, DW_XPCS_PMA_GEN5_12G_ID);
static const struct of_device_id xpcs_of_ids[] = {
{ .compatible = "snps,dw-xpcs", .data = &xpcs_generic },
{ .compatible = "snps,dw-xpcs-gen1-3g", .data = &xpcs_pma_gen1_3g },
{ .compatible = "snps,dw-xpcs-gen2-3g", .data = &xpcs_pma_gen2_3g },
{ .compatible = "snps,dw-xpcs-gen2-6g", .data = &xpcs_pma_gen2_6g },
{ .compatible = "snps,dw-xpcs-gen4-3g", .data = &xpcs_pma_gen4_3g },
{ .compatible = "snps,dw-xpcs-gen4-6g", .data = &xpcs_pma_gen4_6g },
{ .compatible = "snps,dw-xpcs-gen5-10g", .data = &xpcs_pma_gen5_10g },
{ .compatible = "snps,dw-xpcs-gen5-12g", .data = &xpcs_pma_gen5_12g },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, xpcs_of_ids);
static struct platform_driver xpcs_plat_driver = {
.probe = xpcs_plat_probe,
.driver = {
.name = "dwxpcs",
.pm = &xpcs_plat_pm_ops,
.of_match_table = xpcs_of_ids,
},
};
module_platform_driver(xpcs_plat_driver);
MODULE_DESCRIPTION("Synopsys DesignWare XPCS platform device driver");
MODULE_AUTHOR("Serge Semin <fancer.lancer@gmail.com>");
MODULE_LICENSE("GPL");
+197
View File
@@ -0,0 +1,197 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2015 - 2023 Beijing WangXun Technology Co., Ltd. */
#include <linux/pcs/pcs-xpcs.h>
#include <linux/mdio.h>
#include "pcs-xpcs.h"
/* VR_XS_PMA_MMD */
#define TXGBE_PMA_MMD 0x8020
#define TXGBE_TX_GENCTL1 0x11
#define TXGBE_TX_GENCTL1_VBOOST_LVL GENMASK(10, 8)
#define TXGBE_TX_GENCTL1_VBOOST_EN0 BIT(4)
#define TXGBE_TX_GEN_CTL2 0x12
#define TXGBE_TX_GEN_CTL2_TX0_WIDTH(v) FIELD_PREP(GENMASK(9, 8), v)
#define TXGBE_TX_RATE_CTL 0x14
#define TXGBE_TX_RATE_CTL_TX0_RATE(v) FIELD_PREP(GENMASK(2, 0), v)
#define TXGBE_RX_GEN_CTL2 0x32
#define TXGBE_RX_GEN_CTL2_RX0_WIDTH(v) FIELD_PREP(GENMASK(9, 8), v)
#define TXGBE_RX_GEN_CTL3 0x33
#define TXGBE_RX_GEN_CTL3_LOS_TRSHLD0 GENMASK(2, 0)
#define TXGBE_RX_RATE_CTL 0x34
#define TXGBE_RX_RATE_CTL_RX0_RATE(v) FIELD_PREP(GENMASK(1, 0), v)
#define TXGBE_RX_EQ_ATTN_CTL 0x37
#define TXGBE_RX_EQ_ATTN_LVL0 GENMASK(2, 0)
#define TXGBE_RX_EQ_CTL0 0x38
#define TXGBE_RX_EQ_CTL0_VGA1_GAIN(v) FIELD_PREP(GENMASK(15, 12), v)
#define TXGBE_RX_EQ_CTL0_VGA2_GAIN(v) FIELD_PREP(GENMASK(11, 8), v)
#define TXGBE_RX_EQ_CTL0_CTLE_POLE(v) FIELD_PREP(GENMASK(7, 5), v)
#define TXGBE_RX_EQ_CTL0_CTLE_BOOST(v) FIELD_PREP(GENMASK(4, 0), v)
#define TXGBE_RX_EQ_CTL4 0x3C
#define TXGBE_RX_EQ_CTL4_CONT_OFF_CAN0 BIT(4)
#define TXGBE_RX_EQ_CTL4_CONT_ADAPT0 BIT(0)
#define TXGBE_AFE_DFE_ENABLE 0x3D
#define TXGBE_DFE_EN_0 BIT(4)
#define TXGBE_AFE_EN_0 BIT(0)
#define TXGBE_DFE_TAP_CTL0 0x3E
#define TXGBE_MPLLA_CTL0 0x51
#define TXGBE_MPLLA_CTL2 0x53
#define TXGBE_MPLLA_CTL2_DIV16P5_CLK_EN BIT(10)
#define TXGBE_MPLLA_CTL2_DIV10_CLK_EN BIT(9)
#define TXGBE_MPLLA_CTL3 0x57
#define TXGBE_MISC_CTL0 0x70
#define TXGBE_MISC_CTL0_PLL BIT(15)
#define TXGBE_MISC_CTL0_CR_PARA_SEL BIT(14)
#define TXGBE_MISC_CTL0_RX_VREF(v) FIELD_PREP(GENMASK(12, 8), v)
#define TXGBE_VCO_CAL_LD0 0x72
#define TXGBE_VCO_CAL_REF0 0x76
static int txgbe_write_pma(struct dw_xpcs *xpcs, int reg, u16 val)
{
return xpcs_write(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg, val);
}
static int txgbe_modify_pma(struct dw_xpcs *xpcs, int reg, u16 mask, u16 set)
{
return xpcs_modify(xpcs, MDIO_MMD_PMAPMD, TXGBE_PMA_MMD + reg, mask,
set);
}
static void txgbe_pma_config_10gbaser(struct dw_xpcs *xpcs)
{
txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x21);
txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0);
txgbe_modify_pma(xpcs, TXGBE_TX_GENCTL1, TXGBE_TX_GENCTL1_VBOOST_LVL,
FIELD_PREP(TXGBE_TX_GENCTL1_VBOOST_LVL, 0x5));
txgbe_write_pma(xpcs, TXGBE_MISC_CTL0, TXGBE_MISC_CTL0_PLL |
TXGBE_MISC_CTL0_CR_PARA_SEL | TXGBE_MISC_CTL0_RX_VREF(0xF));
txgbe_write_pma(xpcs, TXGBE_VCO_CAL_LD0, 0x549);
txgbe_write_pma(xpcs, TXGBE_VCO_CAL_REF0, 0x29);
txgbe_write_pma(xpcs, TXGBE_TX_RATE_CTL, 0);
txgbe_write_pma(xpcs, TXGBE_RX_RATE_CTL, 0);
txgbe_write_pma(xpcs, TXGBE_TX_GEN_CTL2, TXGBE_TX_GEN_CTL2_TX0_WIDTH(3));
txgbe_write_pma(xpcs, TXGBE_RX_GEN_CTL2, TXGBE_RX_GEN_CTL2_RX0_WIDTH(3));
txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL2, TXGBE_MPLLA_CTL2_DIV16P5_CLK_EN |
TXGBE_MPLLA_CTL2_DIV10_CLK_EN);
txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL0, TXGBE_RX_EQ_CTL0_CTLE_POLE(2) |
TXGBE_RX_EQ_CTL0_CTLE_BOOST(5));
txgbe_modify_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, TXGBE_RX_EQ_ATTN_LVL0, 0);
txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0xBE);
txgbe_modify_pma(xpcs, TXGBE_AFE_DFE_ENABLE,
TXGBE_DFE_EN_0 | TXGBE_AFE_EN_0, 0);
txgbe_modify_pma(xpcs, TXGBE_RX_EQ_CTL4, TXGBE_RX_EQ_CTL4_CONT_ADAPT0,
0);
}
static void txgbe_pma_config_1g(struct dw_xpcs *xpcs)
{
txgbe_modify_pma(xpcs, TXGBE_TX_GENCTL1,
TXGBE_TX_GENCTL1_VBOOST_LVL |
TXGBE_TX_GENCTL1_VBOOST_EN0,
FIELD_PREP(TXGBE_TX_GENCTL1_VBOOST_LVL, 0x5));
txgbe_write_pma(xpcs, TXGBE_MISC_CTL0, TXGBE_MISC_CTL0_PLL |
TXGBE_MISC_CTL0_CR_PARA_SEL | TXGBE_MISC_CTL0_RX_VREF(0xF));
txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL0, TXGBE_RX_EQ_CTL0_VGA1_GAIN(7) |
TXGBE_RX_EQ_CTL0_VGA2_GAIN(7) | TXGBE_RX_EQ_CTL0_CTLE_BOOST(6));
txgbe_modify_pma(xpcs, TXGBE_RX_EQ_ATTN_CTL, TXGBE_RX_EQ_ATTN_LVL0, 0);
txgbe_write_pma(xpcs, TXGBE_DFE_TAP_CTL0, 0);
txgbe_modify_pma(xpcs, TXGBE_RX_GEN_CTL3, TXGBE_RX_GEN_CTL3_LOS_TRSHLD0,
FIELD_PREP(TXGBE_RX_GEN_CTL3_LOS_TRSHLD0, 0x4));
txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL0, 0x20);
txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL3, 0x46);
txgbe_write_pma(xpcs, TXGBE_VCO_CAL_LD0, 0x540);
txgbe_write_pma(xpcs, TXGBE_VCO_CAL_REF0, 0x2A);
txgbe_write_pma(xpcs, TXGBE_AFE_DFE_ENABLE, 0);
txgbe_write_pma(xpcs, TXGBE_RX_EQ_CTL4, TXGBE_RX_EQ_CTL4_CONT_OFF_CAN0);
txgbe_write_pma(xpcs, TXGBE_TX_RATE_CTL, TXGBE_TX_RATE_CTL_TX0_RATE(3));
txgbe_write_pma(xpcs, TXGBE_RX_RATE_CTL, TXGBE_RX_RATE_CTL_RX0_RATE(3));
txgbe_write_pma(xpcs, TXGBE_TX_GEN_CTL2, TXGBE_TX_GEN_CTL2_TX0_WIDTH(1));
txgbe_write_pma(xpcs, TXGBE_RX_GEN_CTL2, TXGBE_RX_GEN_CTL2_RX0_WIDTH(1));
txgbe_write_pma(xpcs, TXGBE_MPLLA_CTL2, TXGBE_MPLLA_CTL2_DIV10_CLK_EN);
}
static int txgbe_pcs_poll_power_up(struct dw_xpcs *xpcs)
{
int val, ret;
/* Wait xpcs power-up good */
ret = read_poll_timeout(xpcs_read_vpcs, val,
(val & DW_PSEQ_ST) == DW_PSEQ_ST_GOOD,
10000, 1000000, false,
xpcs, DW_VR_XS_PCS_DIG_STS);
if (ret < 0)
dev_err(&xpcs->mdiodev->dev, "xpcs power-up timeout\n");
return ret;
}
static int txgbe_pma_init_done(struct dw_xpcs *xpcs)
{
int val, ret;
xpcs_write_vpcs(xpcs, DW_VR_XS_PCS_DIG_CTRL1, DW_VR_RST | DW_EN_VSMMD1);
/* wait pma initialization done */
ret = read_poll_timeout(xpcs_read_vpcs, val, !(val & DW_VR_RST),
100000, 10000000, false,
xpcs, DW_VR_XS_PCS_DIG_CTRL1);
if (ret < 0)
dev_err(&xpcs->mdiodev->dev, "xpcs pma initialization timeout\n");
return ret;
}
static bool txgbe_xpcs_mode_quirk(struct dw_xpcs *xpcs)
{
int ret;
/* When txgbe do LAN reset, PCS will change to default 10GBASE-R mode */
ret = xpcs_read(xpcs, MDIO_MMD_PCS, MDIO_CTRL2);
ret &= MDIO_PCS_CTRL2_TYPE;
if ((ret == MDIO_PCS_CTRL2_10GBR &&
xpcs->interface != PHY_INTERFACE_MODE_10GBASER) ||
xpcs->interface == PHY_INTERFACE_MODE_SGMII)
return true;
return false;
}
int txgbe_xpcs_switch_mode(struct dw_xpcs *xpcs, phy_interface_t interface)
{
int ret;
switch (interface) {
case PHY_INTERFACE_MODE_10GBASER:
case PHY_INTERFACE_MODE_SGMII:
case PHY_INTERFACE_MODE_1000BASEX:
break;
default:
return 0;
}
if (xpcs->interface == interface && !txgbe_xpcs_mode_quirk(xpcs))
return 0;
xpcs->interface = interface;
ret = txgbe_pcs_poll_power_up(xpcs);
if (ret < 0)
return ret;
if (interface == PHY_INTERFACE_MODE_10GBASER) {
xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, MDIO_PCS_CTRL2_10GBR);
xpcs_modify(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1,
MDIO_CTRL1_SPEED10G, MDIO_CTRL1_SPEED10G);
txgbe_pma_config_10gbaser(xpcs);
} else {
xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL2, MDIO_PCS_CTRL2_10GBX);
xpcs_write(xpcs, MDIO_MMD_PMAPMD, MDIO_CTRL1, 0);
xpcs_write(xpcs, MDIO_MMD_PCS, MDIO_CTRL1, 0);
txgbe_pma_config_1g(xpcs);
}
return txgbe_pma_init_done(xpcs);
}
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2020 Synopsys, Inc. and/or its affiliates.
* Synopsys DesignWare XPCS helpers
*
* Author: Jose Abreu <Jose.Abreu@synopsys.com>
*/
#include <linux/bits.h>
#include <linux/pcs/pcs-xpcs.h>
/* Vendor regs access */
#define DW_VENDOR BIT(15)
/* VR_XS_PCS */
#define DW_USXGMII_RST BIT(10)
#define DW_USXGMII_EN BIT(9)
#define DW_VR_XS_PCS_DIG_CTRL1 0x0000
#define DW_VR_RST BIT(15)
#define DW_EN_VSMMD1 BIT(13)
#define DW_CL37_BP BIT(12)
#define DW_VR_XS_PCS_DIG_STS 0x0010
#define DW_RXFIFO_ERR GENMASK(6, 5)
#define DW_PSEQ_ST GENMASK(4, 2)
#define DW_PSEQ_ST_GOOD FIELD_PREP(GENMASK(4, 2), 0x4)
/* SR_MII */
#define DW_USXGMII_FULL BIT(8)
#define DW_USXGMII_SS_MASK (BIT(13) | BIT(6) | BIT(5))
#define DW_USXGMII_10000 (BIT(13) | BIT(6))
#define DW_USXGMII_5000 (BIT(13) | BIT(5))
#define DW_USXGMII_2500 (BIT(5))
#define DW_USXGMII_1000 (BIT(6))
#define DW_USXGMII_100 (BIT(13))
#define DW_USXGMII_10 (0)
/* SR_AN */
#define DW_SR_AN_ADV1 0x10
#define DW_SR_AN_ADV2 0x11
#define DW_SR_AN_ADV3 0x12
/* Clause 73 Defines */
/* AN_LP_ABL1 */
#define DW_C73_PAUSE BIT(10)
#define DW_C73_ASYM_PAUSE BIT(11)
#define DW_C73_AN_ADV_SF 0x1
/* AN_LP_ABL2 */
#define DW_C73_1000KX BIT(5)
#define DW_C73_10000KX4 BIT(6)
#define DW_C73_10000KR BIT(7)
/* AN_LP_ABL3 */
#define DW_C73_2500KX BIT(0)
#define DW_C73_5000KR BIT(1)
/* Clause 37 Defines */
/* VR MII MMD registers offsets */
#define DW_VR_MII_DIG_CTRL1 0x8000
#define DW_VR_MII_DIG_CTRL1_MAC_AUTO_SW BIT(9)
#define DW_VR_MII_DIG_CTRL1_2G5_EN BIT(2)
#define DW_VR_MII_DIG_CTRL1_PHY_MODE_CTRL BIT(0)
#define DW_VR_MII_AN_CTRL 0x8001
#define DW_VR_MII_AN_CTRL_8BIT BIT(8)
#define DW_VR_MII_TX_CONFIG_MASK BIT(3)
#define DW_VR_MII_TX_CONFIG_PHY_SIDE_SGMII 0x1
#define DW_VR_MII_TX_CONFIG_MAC_SIDE_SGMII 0x0
#define DW_VR_MII_PCS_MODE_MASK GENMASK(2, 1)
#define DW_VR_MII_PCS_MODE_C37_1000BASEX 0x0
#define DW_VR_MII_PCS_MODE_C37_SGMII 0x2
#define DW_VR_MII_AN_INTR_EN BIT(0)
#define DW_VR_MII_AN_INTR_STS 0x8002
#define DW_VR_MII_AN_STS_C37_ANCMPLT_INTR BIT(0)
#define DW_VR_MII_AN_STS_C37_ANSGM_FD BIT(1)
#define DW_VR_MII_AN_STS_C37_ANSGM_SP GENMASK(3, 2)
#define DW_VR_MII_C37_ANSGM_SP_10 0x0
#define DW_VR_MII_C37_ANSGM_SP_100 0x1
#define DW_VR_MII_C37_ANSGM_SP_1000 0x2
#define DW_VR_MII_C37_ANSGM_SP_LNKSTS BIT(4)
#define DW_VR_MII_EEE_MCTRL0 0x8006
#define DW_VR_MII_EEE_LTX_EN BIT(0) /* LPI Tx Enable */
#define DW_VR_MII_EEE_LRX_EN BIT(1) /* LPI Rx Enable */
#define DW_VR_MII_EEE_TX_QUIET_EN BIT(2) /* Tx Quiet Enable */
#define DW_VR_MII_EEE_RX_QUIET_EN BIT(3) /* Rx Quiet Enable */
#define DW_VR_MII_EEE_TX_EN_CTRL BIT(4) /* Tx Control Enable */
#define DW_VR_MII_EEE_RX_EN_CTRL BIT(7) /* Rx Control Enable */
#define DW_VR_MII_EEE_MULT_FACT_100NS GENMASK(11, 8)
#define DW_VR_MII_EEE_MCTRL1 0x800b
#define DW_VR_MII_EEE_TRN_LPI BIT(0) /* Transparent Mode Enable */
#define DW_VR_MII_DIG_CTRL2 0x80e1
#define DW_VR_MII_DIG_CTRL2_TX_POL_INV BIT(4)
#define DW_VR_MII_DIG_CTRL2_RX_POL_INV BIT(0)
#define DW_XPCS_INFO_DECLARE(_name, _pcs, _pma) \
static const struct dw_xpcs_info _name = { .pcs = _pcs, .pma = _pma }
struct dw_xpcs_desc;
enum dw_xpcs_clock {
DW_XPCS_CORE_CLK,
DW_XPCS_PAD_CLK,
DW_XPCS_NUM_CLKS,
};
struct dw_xpcs {
struct dw_xpcs_info info;
const struct dw_xpcs_desc *desc;
struct mdio_device *mdiodev;
struct clk_bulk_data clks[DW_XPCS_NUM_CLKS];
struct phylink_pcs pcs;
phy_interface_t interface;
bool need_reset;
u8 eee_mult_fact;
};
int xpcs_read(struct dw_xpcs *xpcs, int dev, u32 reg);
int xpcs_write(struct dw_xpcs *xpcs, int dev, u32 reg, u16 val);
int xpcs_modify(struct dw_xpcs *xpcs, int dev, u32 reg, u16 mask, u16 set);
int xpcs_read_vpcs(struct dw_xpcs *xpcs, int reg);
int xpcs_write_vpcs(struct dw_xpcs *xpcs, int reg, u16 val);
int nxp_sja1105_sgmii_pma_config(struct dw_xpcs *xpcs);
int nxp_sja1110_sgmii_pma_config(struct dw_xpcs *xpcs);
int nxp_sja1110_2500basex_pma_config(struct dw_xpcs *xpcs);
int txgbe_xpcs_switch_mode(struct dw_xpcs *xpcs, phy_interface_t interface);