Linux Audio

Check our new training course

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