summaryrefslogtreecommitdiff
path: root/drivers/net/ethernet/tehuti/tn40_mdio.c
blob: fb1a4a2e4dbc552a90934ab92300fcd98ef903c4 (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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
// SPDX-License-Identifier: GPL-2.0+
/* Copyright (c) Tehuti Networks Ltd. */

#include <linux/netdevice.h>
#include <linux/pci.h>
#include <linux/phylink.h>

#include "tn40.h"

#define TN40_MDIO_DEVAD_MASK GENMASK(4, 0)
#define TN40_MDIO_PRTAD_MASK GENMASK(9, 5)
#define TN40_MDIO_CMD_VAL(device, port)			\
	(FIELD_PREP(TN40_MDIO_DEVAD_MASK, (device)) |	\
	 (FIELD_PREP(TN40_MDIO_PRTAD_MASK, (port))))
#define TN40_MDIO_CMD_READ BIT(15)

#define AQR105_FIRMWARE "tehuti/aqr105-tn40xx.cld"

static void tn40_mdio_set_speed(struct tn40_priv *priv, u32 speed)
{
	void __iomem *regs = priv->regs;
	int mdio_cfg;

	if (speed == TN40_MDIO_SPEED_1MHZ)
		mdio_cfg = (0x7d << 7) | 0x08;	/* 1MHz */
	else
		mdio_cfg = 0xA08;	/* 6MHz */
	mdio_cfg |= (1 << 6);
	writel(mdio_cfg, regs + TN40_REG_MDIO_CMD_STAT);
	msleep(100);
}

static u32 tn40_mdio_stat(struct tn40_priv *priv)
{
	void __iomem *regs = priv->regs;

	return readl(regs + TN40_REG_MDIO_CMD_STAT);
}

static int tn40_mdio_wait_nobusy(struct tn40_priv *priv, u32 *val)
{
	u32 stat;
	int ret;

	ret = readx_poll_timeout_atomic(tn40_mdio_stat, priv, stat,
					TN40_GET_MDIO_BUSY(stat) == 0, 10,
					10000);
	if (val)
		*val = stat;
	return ret;
}

static int tn40_mdio_read(struct tn40_priv *priv, int port, int device,
			  u16 regnum)
{
	void __iomem *regs = priv->regs;
	u32 i;

	/* wait until MDIO is not busy */
	if (tn40_mdio_wait_nobusy(priv, NULL))
		return -EIO;

	i = TN40_MDIO_CMD_VAL(device, port);
	writel(i, regs + TN40_REG_MDIO_CMD);
	writel((u32)regnum, regs + TN40_REG_MDIO_ADDR);
	if (tn40_mdio_wait_nobusy(priv, NULL))
		return -EIO;

	writel(TN40_MDIO_CMD_READ | i, regs + TN40_REG_MDIO_CMD);
	/* read CMD_STAT until not busy */
	if (tn40_mdio_wait_nobusy(priv, NULL))
		return -EIO;

	return lower_16_bits(readl(regs + TN40_REG_MDIO_DATA));
}

static int tn40_mdio_write(struct tn40_priv *priv, int port, int device,
			   u16 regnum, u16 data)
{
	void __iomem *regs = priv->regs;
	u32 tmp_reg = 0;
	int ret;

	/* wait until MDIO is not busy */
	if (tn40_mdio_wait_nobusy(priv, NULL))
		return -EIO;
	writel(TN40_MDIO_CMD_VAL(device, port), regs + TN40_REG_MDIO_CMD);
	writel((u32)regnum, regs + TN40_REG_MDIO_ADDR);
	if (tn40_mdio_wait_nobusy(priv, NULL))
		return -EIO;
	writel((u32)data, regs + TN40_REG_MDIO_DATA);
	/* read CMD_STAT until not busy */
	ret = tn40_mdio_wait_nobusy(priv, &tmp_reg);
	if (ret)
		return -EIO;

	if (TN40_GET_MDIO_RD_ERR(tmp_reg)) {
		dev_err(&priv->pdev->dev, "MDIO error after write command\n");
		return -EIO;
	}
	return 0;
}

static int tn40_mdio_read_c45(struct mii_bus *mii_bus, int addr, int devnum,
			      int regnum)
{
	return tn40_mdio_read(mii_bus->priv, addr, devnum, regnum);
}

static int tn40_mdio_write_c45(struct mii_bus *mii_bus, int addr, int devnum,
			       int regnum, u16 val)
{
	return  tn40_mdio_write(mii_bus->priv, addr, devnum, regnum, val);
}

/* registers an mdio node and an aqr105 PHY at address 1
 * tn40_mdio-%id {
 *	ethernet-phy@1 {
 *		compatible = "ethernet-phy-id03a1.b4a3";
 *		reg = <1>;
 *		firmware-name = AQR105_FIRMWARE;
 *	};
 * };
 */
static int tn40_swnodes_register(struct tn40_priv *priv)
{
	struct tn40_nodes *nodes = &priv->nodes;
	struct pci_dev *pdev = priv->pdev;
	struct software_node *swnodes;
	u32 id;

	id = pci_dev_id(pdev);

	snprintf(nodes->phy_name, sizeof(nodes->phy_name), "ethernet-phy@1");
	snprintf(nodes->mdio_name, sizeof(nodes->mdio_name), "tn40_mdio-%x",
		 id);

	swnodes = nodes->swnodes;

	swnodes[SWNODE_MDIO] = NODE_PROP(nodes->mdio_name, NULL);

	nodes->phy_props[0] = PROPERTY_ENTRY_STRING("compatible",
						    "ethernet-phy-id03a1.b4a3");
	nodes->phy_props[1] = PROPERTY_ENTRY_U32("reg", 1);
	nodes->phy_props[2] = PROPERTY_ENTRY_STRING("firmware-name",
						    AQR105_FIRMWARE);
	swnodes[SWNODE_PHY] = NODE_PAR_PROP(nodes->phy_name,
					    &swnodes[SWNODE_MDIO],
					    nodes->phy_props);

	nodes->group[SWNODE_PHY] = &swnodes[SWNODE_PHY];
	nodes->group[SWNODE_MDIO] = &swnodes[SWNODE_MDIO];
	return software_node_register_node_group(nodes->group);
}

void tn40_swnodes_cleanup(struct tn40_priv *priv)
{
	/* cleanup of swnodes is only needed for AQR105-based cards */
	if (priv->pdev->device == PCI_DEVICE_ID_TEHUTI_TN9510) {
		fwnode_handle_put(dev_fwnode(&priv->mdio->dev));
		device_remove_software_node(&priv->mdio->dev);
		software_node_unregister_node_group(priv->nodes.group);
	}
}

int tn40_mdiobus_init(struct tn40_priv *priv)
{
	struct pci_dev *pdev = priv->pdev;
	struct mii_bus *bus;
	int ret;

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

	bus->name = TN40_DRV_NAME;
	bus->parent = &pdev->dev;
	snprintf(bus->id, MII_BUS_ID_SIZE, "tn40xx-%x-%x",
		 pci_domain_nr(pdev->bus), pci_dev_id(pdev));
	bus->priv = priv;

	bus->read_c45 = tn40_mdio_read_c45;
	bus->write_c45 = tn40_mdio_write_c45;
	priv->mdio = bus;

	/* provide swnodes for AQR105-based cards only */
	if (pdev->device == PCI_DEVICE_ID_TEHUTI_TN9510) {
		ret = tn40_swnodes_register(priv);
		if (ret) {
			pr_err("swnodes failed\n");
			return ret;
		}

		ret = device_add_software_node(&bus->dev,
					       priv->nodes.group[SWNODE_MDIO]);
		if (ret) {
			dev_err(&pdev->dev,
				"device_add_software_node failed: %d\n", ret);
			goto err_swnodes_unregister;
		}
	}

	tn40_mdio_set_speed(priv, TN40_MDIO_SPEED_6MHZ);
	ret = devm_mdiobus_register(&pdev->dev, bus);
	if (ret) {
		dev_err(&pdev->dev, "failed to register mdiobus %d %u %u\n",
			ret, bus->state, MDIOBUS_UNREGISTERED);
		goto err_swnodes_cleanup;
	}
	return 0;

err_swnodes_unregister:
	software_node_unregister_node_group(priv->nodes.group);
	return ret;
err_swnodes_cleanup:
	tn40_swnodes_cleanup(priv);
	return ret;
}

MODULE_FIRMWARE(AQR105_FIRMWARE);