1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
|
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Bootlin
*
* Maxime Chevallier <maxime.chevallier@bootlin.com>
*/
#include <linux/netdevice.h>
#include <linux/phy.h>
#include <linux/phylink.h>
#include <linux/pcs-altera-tse.h>
/* SGMII PCS register addresses
*/
#define SGMII_PCS_SCRATCH 0x10
#define SGMII_PCS_REV 0x11
#define SGMII_PCS_LINK_TIMER_0 0x12
#define SGMII_PCS_LINK_TIMER_REG(x) (0x12 + (x))
#define SGMII_PCS_LINK_TIMER_1 0x13
#define SGMII_PCS_IF_MODE 0x14
#define PCS_IF_MODE_SGMII_ENA BIT(0)
#define PCS_IF_MODE_USE_SGMII_AN BIT(1)
#define PCS_IF_MODE_SGMI_SPEED_MASK GENMASK(3, 2)
#define PCS_IF_MODE_SGMI_SPEED_10 (0 << 2)
#define PCS_IF_MODE_SGMI_SPEED_100 (1 << 2)
#define PCS_IF_MODE_SGMI_SPEED_1000 (2 << 2)
#define PCS_IF_MODE_SGMI_HALF_DUPLEX BIT(4)
#define PCS_IF_MODE_SGMI_PHY_AN BIT(5)
#define SGMII_PCS_DIS_READ_TO 0x15
#define SGMII_PCS_READ_TO 0x16
#define SGMII_PCS_SW_RESET_TIMEOUT 100 /* usecs */
struct altera_tse_pcs {
struct phylink_pcs pcs;
void __iomem *base;
int reg_width;
};
static struct altera_tse_pcs *phylink_pcs_to_tse_pcs(struct phylink_pcs *pcs)
{
return container_of(pcs, struct altera_tse_pcs, pcs);
}
static u16 tse_pcs_read(struct altera_tse_pcs *tse_pcs, int regnum)
{
if (tse_pcs->reg_width == 4)
return readl(tse_pcs->base + regnum * 4);
else
return readw(tse_pcs->base + regnum * 2);
}
static void tse_pcs_write(struct altera_tse_pcs *tse_pcs, int regnum,
u16 value)
{
if (tse_pcs->reg_width == 4)
writel(value, tse_pcs->base + regnum * 4);
else
writew(value, tse_pcs->base + regnum * 2);
}
static int tse_pcs_reset(struct altera_tse_pcs *tse_pcs)
{
int i = 0;
u16 bmcr;
/* Reset PCS block */
bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
bmcr |= BMCR_RESET;
tse_pcs_write(tse_pcs, MII_BMCR, bmcr);
for (i = 0; i < SGMII_PCS_SW_RESET_TIMEOUT; i++) {
if (!(tse_pcs_read(tse_pcs, MII_BMCR) & BMCR_RESET))
return 0;
udelay(1);
}
return -ETIMEDOUT;
}
static int alt_tse_pcs_validate(struct phylink_pcs *pcs,
unsigned long *supported,
const struct phylink_link_state *state)
{
if (state->interface == PHY_INTERFACE_MODE_SGMII ||
state->interface == PHY_INTERFACE_MODE_1000BASEX)
return 1;
return -EINVAL;
}
static int alt_tse_pcs_config(struct phylink_pcs *pcs, unsigned int mode,
phy_interface_t interface,
const unsigned long *advertising,
bool permit_pause_to_mac)
{
struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
u32 ctrl, if_mode;
ctrl = tse_pcs_read(tse_pcs, MII_BMCR);
if_mode = tse_pcs_read(tse_pcs, SGMII_PCS_IF_MODE);
/* Set link timer to 1.6ms, as per the MegaCore Function User Guide */
tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_0, 0x0D40);
tse_pcs_write(tse_pcs, SGMII_PCS_LINK_TIMER_1, 0x03);
if (interface == PHY_INTERFACE_MODE_SGMII) {
if_mode |= PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA;
} else if (interface == PHY_INTERFACE_MODE_1000BASEX) {
if_mode &= ~(PCS_IF_MODE_USE_SGMII_AN | PCS_IF_MODE_SGMII_ENA);
if_mode |= PCS_IF_MODE_SGMI_SPEED_1000;
}
ctrl |= (BMCR_SPEED1000 | BMCR_FULLDPLX | BMCR_ANENABLE);
tse_pcs_write(tse_pcs, MII_BMCR, ctrl);
tse_pcs_write(tse_pcs, SGMII_PCS_IF_MODE, if_mode);
return tse_pcs_reset(tse_pcs);
}
static void alt_tse_pcs_get_state(struct phylink_pcs *pcs,
struct phylink_link_state *state)
{
struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
u16 bmsr, lpa;
bmsr = tse_pcs_read(tse_pcs, MII_BMSR);
lpa = tse_pcs_read(tse_pcs, MII_LPA);
phylink_mii_c22_pcs_decode_state(state, bmsr, lpa);
}
static void alt_tse_pcs_an_restart(struct phylink_pcs *pcs)
{
struct altera_tse_pcs *tse_pcs = phylink_pcs_to_tse_pcs(pcs);
u16 bmcr;
bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
bmcr |= BMCR_ANRESTART;
tse_pcs_write(tse_pcs, MII_BMCR, bmcr);
/* This PCS seems to require a soft reset to re-sync the AN logic */
tse_pcs_reset(tse_pcs);
}
static const struct phylink_pcs_ops alt_tse_pcs_ops = {
.pcs_validate = alt_tse_pcs_validate,
.pcs_get_state = alt_tse_pcs_get_state,
.pcs_config = alt_tse_pcs_config,
.pcs_an_restart = alt_tse_pcs_an_restart,
};
struct phylink_pcs *alt_tse_pcs_create(struct net_device *ndev,
void __iomem *pcs_base, int reg_width)
{
struct altera_tse_pcs *tse_pcs;
if (reg_width != 4 && reg_width != 2)
return ERR_PTR(-EINVAL);
tse_pcs = devm_kzalloc(&ndev->dev, sizeof(*tse_pcs), GFP_KERNEL);
if (!tse_pcs)
return ERR_PTR(-ENOMEM);
tse_pcs->pcs.ops = &alt_tse_pcs_ops;
tse_pcs->base = pcs_base;
tse_pcs->reg_width = reg_width;
return &tse_pcs->pcs;
}
EXPORT_SYMBOL_GPL(alt_tse_pcs_create);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Altera TSE PCS driver");
MODULE_AUTHOR("Maxime Chevallier <maxime.chevallier@bootlin.com>");
|