summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/meta/fbnic/fbnic_mdio.c
blob: 709041f7fc43df558744b43bc3d27563ada685e2 (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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) Meta Platforms, Inc. and affiliates. */

#include <linux/mdio.h>
#include <linux/pcs/pcs-xpcs.h>

#include "fbnic.h"
#include "fbnic_netdev.h"

#define DW_VENDOR		BIT(15)
#define FBNIC_PCS_VENDOR	BIT(9)
#define FBNIC_PCS_ZERO_MASK	(DW_VENDOR - FBNIC_PCS_VENDOR)

static int
fbnic_mdio_read_pmd(struct fbnic_dev *fbd, int addr, int regnum)
{
	u8 aui = FBNIC_AUI_UNKNOWN;
	struct fbnic_net *fbn;
	int ret = 0;

	/* We don't need a second PMD, just one can handle both lanes */
	if (addr)
		return 0;

	if (fbd->netdev) {
		fbn = netdev_priv(fbd->netdev);
		if (fbn->aui < FBNIC_AUI_UNKNOWN)
			aui = fbn->aui;
	}

	switch (regnum) {
	case MDIO_DEVID1:
		ret = MP_FBNIC_XPCS_PMA_100G_ID >> 16;
		break;
	case MDIO_DEVID2:
		ret = MP_FBNIC_XPCS_PMA_100G_ID & 0xffff;
		break;
	case MDIO_DEVS1:
		ret = MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS;
		break;
	case MDIO_STAT2:
		ret = MDIO_STAT2_DEVPRST_VAL;
		break;
	case MDIO_PMA_RXDET:
		/* If training isn't complete default to 0 */
		if (fbd->pmd_state != FBNIC_PMD_SEND_DATA)
			break;
		/* Report either 1 or 2 lanes detected depending on config */
		ret = (MDIO_PMD_RXDET_GLOBAL | MDIO_PMD_RXDET_0) |
		      ((aui & FBNIC_AUI_MODE_R2) *
		       (MDIO_PMD_RXDET_1 / FBNIC_AUI_MODE_R2));
		break;
	default:
		break;
	}

	dev_dbg(fbd->dev,
		"SWMII PMD Rd: Addr: %d RegNum: %d Value: 0x%04x\n",
		addr, regnum, ret);

	return ret;
}

static int
fbnic_mdio_read_pcs(struct fbnic_dev *fbd, int addr, int regnum)
{
	int ret, offset = 0;

	/* We will need access to both PCS instances to get config info */
	if (addr >= 2)
		return 0;

	/* Report 0 for reserved registers */
	if (regnum & FBNIC_PCS_ZERO_MASK)
		return 0;

	/* Intercept and return correct ID for PCS */
	if (regnum == MDIO_DEVID1)
		return DW_XPCS_ID >> 16;
	if (regnum == MDIO_DEVID2)
		return DW_XPCS_ID & 0xffff;
	if (regnum == MDIO_DEVS1)
		return MDIO_DEVS_PMAPMD | MDIO_DEVS_PCS;

	/* Swap vendor page bit for FBNIC PCS vendor page bit */
	if (regnum & DW_VENDOR)
		offset ^= DW_VENDOR | FBNIC_PCS_VENDOR;

	ret = fbnic_rd32(fbd, FBNIC_PCS_PAGE(addr) + (regnum ^ offset));

	dev_dbg(fbd->dev,
		"SWMII PCS Rd: Addr: %d RegNum: %d Value: 0x%04x\n",
		addr, regnum, ret);

	return ret;
}

static int
fbnic_mdio_read_c45(struct mii_bus *bus, int addr, int devnum, int regnum)
{
	struct fbnic_dev *fbd = bus->priv;

	if (devnum == MDIO_MMD_PMAPMD)
		return fbnic_mdio_read_pmd(fbd, addr, regnum);

	if (devnum == MDIO_MMD_PCS)
		return fbnic_mdio_read_pcs(fbd, addr, regnum);

	return 0;
}

static void
fbnic_mdio_write_pmd(struct fbnic_dev *fbd, int addr, int regnum, u16 val)
{
	dev_dbg(fbd->dev,
		"SWMII PMD Wr: Addr: %d RegNum: %d Value: 0x%04x\n",
		addr, regnum, val);
}

static void
fbnic_mdio_write_pcs(struct fbnic_dev *fbd, int addr, int regnum, u16 val)
{
	dev_dbg(fbd->dev,
		"SWMII PCS Wr: Addr: %d RegNum: %d Value: 0x%04x\n",
		addr, regnum, val);

	/* Allow access to both halves of PCS for 50R2 config */
	if (addr > 2)
		return;

	/* Skip write for reserved registers */
	if (regnum & FBNIC_PCS_ZERO_MASK)
		return;

	/* Swap vendor page bit for FBNIC PCS vendor page bit */
	if (regnum & DW_VENDOR)
		regnum ^= DW_VENDOR | FBNIC_PCS_VENDOR;

	fbnic_wr32(fbd, FBNIC_PCS_PAGE(addr) + regnum, val);
}

static int
fbnic_mdio_write_c45(struct mii_bus *bus, int addr, int devnum,
		     int regnum, u16 val)
{
	struct fbnic_dev *fbd = bus->priv;

	if (devnum == MDIO_MMD_PMAPMD)
		fbnic_mdio_write_pmd(fbd, addr, regnum, val);

	if (devnum == MDIO_MMD_PCS)
		fbnic_mdio_write_pcs(fbd, addr, regnum, val);

	return 0;
}

/**
 * fbnic_mdiobus_create - Create an MDIO bus to allow interfacing w/ PHYs
 * @fbd: Pointer to FBNIC device structure to populate bus on
 *
 * Initialize an MDIO bus and place a pointer to it on the fbd struct. This bus
 * will be used to interface with the PMA/PMD and PCS.
 *
 * Return: 0 on success, negative on failure
 **/
int fbnic_mdiobus_create(struct fbnic_dev *fbd)
{
	struct mii_bus *bus;
	int err;

	bus = devm_mdiobus_alloc(fbd->dev);
	if (!bus)
		return -ENOMEM;

	bus->name = "fbnic_mii_bus";
	bus->read_c45 = &fbnic_mdio_read_c45;
	bus->write_c45 = &fbnic_mdio_write_c45;

	/* Disable PHY auto probing. We will add PCS manually */
	bus->phy_mask = ~0;

	bus->parent = fbd->dev;
	bus->priv = fbd;
	snprintf(bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(fbd->dev));

	err = devm_mdiobus_register(fbd->dev, bus);
	if (err) {
		dev_err(fbd->dev, "Failed to create MDIO bus: %d\n", err);
		return err;
	}

	fbd->mdio_bus = bus;

	return 0;
}