summaryrefslogtreecommitdiff
path: root/drivers/net/pcs/pcs-altera-tse.c
blob: d616749761f41129b90a4e77c8b308ce658ca07e (plain)
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
// 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_LINK_TIMER_0	0x12
#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_HALF_DUPLEX	BIT(4)
#define   PCS_IF_MODE_SGMI_PHY_AN	BIT(5)
#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)
{
	u16 bmcr;

	/* Reset PCS block */
	bmcr = tse_pcs_read(tse_pcs, MII_BMCR);
	bmcr |= BMCR_RESET;
	tse_pcs_write(tse_pcs, MII_BMCR, bmcr);

	return read_poll_timeout(tse_pcs_read, bmcr, (bmcr & BMCR_RESET),
				 10, SGMII_PCS_SW_RESET_TIMEOUT, 1,
				 tse_pcs, MII_BMCR);
}

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);
	}

	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>");