summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/samsung/sxgbe/sxgbe_mdio.c
blob: 43ccb4a6de15a3fe2dedc9198a3866fd89143f47 (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
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/* 10G controller driver for Samsung SoCs
 *
 * Copyright (C) 2013 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 *
 * Author: Siva Reddy Kallam <siva.kallam@samsung.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <linux/io.h>
#include <linux/mii.h>
#include <linux/netdevice.h>
#include <linux/platform_device.h>
#include <linux/phy.h>
#include <linux/slab.h>
#include <linux/sxgbe_platform.h>

#include "sxgbe_common.h"
#include "sxgbe_reg.h"

#define SXGBE_SMA_WRITE_CMD	0x01 /* write command */
#define SXGBE_SMA_PREAD_CMD	0x02 /* post read  increament address */
#define SXGBE_SMA_READ_CMD	0x03 /* read command */
#define SXGBE_SMA_SKIP_ADDRFRM	0x00040000 /* skip the address frame */
#define SXGBE_MII_BUSY		0x00400000 /* mii busy */

static int sxgbe_mdio_busy_wait(void __iomem *ioaddr, unsigned int mii_data)
{
	unsigned long fin_time = jiffies + 3 * HZ; /* 3 seconds */

	while (!time_after(jiffies, fin_time)) {
		if (!(readl(ioaddr + mii_data) & SXGBE_MII_BUSY))
			return 0;
		cpu_relax();
	}

	return -EBUSY;
}

static void sxgbe_mdio_ctrl_data(struct sxgbe_priv_data *sp, u32 cmd,
				 u16 phydata)
{
	u32 reg = phydata;

	reg |= (cmd << 16) | SXGBE_SMA_SKIP_ADDRFRM |
	       ((sp->clk_csr & 0x7) << 19) | SXGBE_MII_BUSY;
	writel(reg, sp->ioaddr + sp->hw->mii.data);
}

static void sxgbe_mdio_c45(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr,
			   int phyreg, u16 phydata)
{
	u32 reg;

	/* set mdio address register */
	reg = ((phyreg >> 16) & 0x1f) << 21;
	reg |= (phyaddr << 16) | (phyreg & 0xffff);
	writel(reg, sp->ioaddr + sp->hw->mii.addr);

	sxgbe_mdio_ctrl_data(sp, cmd, phydata);
}

static void sxgbe_mdio_c22(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr,
			   int phyreg, u16 phydata)
{
	u32 reg;

	writel(1 << phyaddr, sp->ioaddr + SXGBE_MDIO_CLAUSE22_PORT_REG);

	/* set mdio address register */
	reg = (phyaddr << 16) | (phyreg & 0x1f);
	writel(reg, sp->ioaddr + sp->hw->mii.addr);

	sxgbe_mdio_ctrl_data(sp, cmd, phydata);
}

static int sxgbe_mdio_access(struct sxgbe_priv_data *sp, u32 cmd, int phyaddr,
			     int phyreg, u16 phydata)
{
	const struct mii_regs *mii = &sp->hw->mii;
	int rc;

	rc = sxgbe_mdio_busy_wait(sp->ioaddr, mii->data);
	if (rc < 0)
		return rc;

	if (phyreg & MII_ADDR_C45) {
		sxgbe_mdio_c45(sp, cmd, phyaddr, phyreg, phydata);
	} else {
		 /* Ports 0-3 only support C22. */
		if (phyaddr >= 4)
			return -ENODEV;

		sxgbe_mdio_c22(sp, cmd, phyaddr, phyreg, phydata);
	}

	return sxgbe_mdio_busy_wait(sp->ioaddr, mii->data);
}

/**
 * sxgbe_mdio_read
 * @bus: points to the mii_bus structure
 * @phyaddr: address of phy port
 * @phyreg: address of register with in phy register
 * Description: this function used for C45 and C22 MDIO Read
 */
static int sxgbe_mdio_read(struct mii_bus *bus, int phyaddr, int phyreg)
{
	struct net_device *ndev = bus->priv;
	struct sxgbe_priv_data *priv = netdev_priv(ndev);
	int rc;

	rc = sxgbe_mdio_access(priv, SXGBE_SMA_READ_CMD, phyaddr, phyreg, 0);
	if (rc < 0)
		return rc;

	return readl(priv->ioaddr + priv->hw->mii.data) & 0xffff;
}

/**
 * sxgbe_mdio_write
 * @bus: points to the mii_bus structure
 * @phyaddr: address of phy port
 * @phyreg: address of phy registers
 * @phydata: data to be written into phy register
 * Description: this function is used for C45 and C22 MDIO write
 */
static int sxgbe_mdio_write(struct mii_bus *bus, int phyaddr, int phyreg,
			     u16 phydata)
{
	struct net_device *ndev = bus->priv;
	struct sxgbe_priv_data *priv = netdev_priv(ndev);

	return sxgbe_mdio_access(priv, SXGBE_SMA_WRITE_CMD, phyaddr, phyreg,
				 phydata);
}

int sxgbe_mdio_register(struct net_device *ndev)
{
	struct mii_bus *mdio_bus;
	struct sxgbe_priv_data *priv = netdev_priv(ndev);
	struct sxgbe_mdio_bus_data *mdio_data = priv->plat->mdio_bus_data;
	int err, phy_addr;
	int *irqlist;
	bool phy_found = false;
	bool act;

	/* allocate the new mdio bus */
	mdio_bus = mdiobus_alloc();
	if (!mdio_bus) {
		netdev_err(ndev, "%s: mii bus allocation failed\n", __func__);
		return -ENOMEM;
	}

	if (mdio_data->irqs)
		irqlist = mdio_data->irqs;
	else
		irqlist = priv->mii_irq;

	/* assign mii bus fields */
	mdio_bus->name = "sxgbe";
	mdio_bus->read = &sxgbe_mdio_read;
	mdio_bus->write = &sxgbe_mdio_write;
	snprintf(mdio_bus->id, MII_BUS_ID_SIZE, "%s-%x",
		 mdio_bus->name, priv->plat->bus_id);
	mdio_bus->priv = ndev;
	mdio_bus->phy_mask = mdio_data->phy_mask;
	mdio_bus->parent = priv->device;

	/* register with kernel subsystem */
	err = mdiobus_register(mdio_bus);
	if (err != 0) {
		netdev_err(ndev, "mdiobus register failed\n");
		goto mdiobus_err;
	}

	for (phy_addr = 0; phy_addr < PHY_MAX_ADDR; phy_addr++) {
		struct phy_device *phy = mdio_bus->phy_map[phy_addr];

		if (phy) {
			char irq_num[4];
			char *irq_str;
			/* If an IRQ was provided to be assigned after
			 * the bus probe, do it here.
			 */
			if ((mdio_data->irqs == NULL) &&
			    (mdio_data->probed_phy_irq > 0)) {
				irqlist[phy_addr] = mdio_data->probed_phy_irq;
				phy->irq = mdio_data->probed_phy_irq;
			}

			/* If we're  going to bind the MAC to this PHY bus,
			 * and no PHY number was provided to the MAC,
			 * use the one probed here.
			 */
			if (priv->plat->phy_addr == -1)
				priv->plat->phy_addr = phy_addr;

			act = (priv->plat->phy_addr == phy_addr);
			switch (phy->irq) {
			case PHY_POLL:
				irq_str = "POLL";
				break;
			case PHY_IGNORE_INTERRUPT:
				irq_str = "IGNORE";
				break;
			default:
				sprintf(irq_num, "%d", phy->irq);
				irq_str = irq_num;
				break;
			}
			netdev_info(ndev, "PHY ID %08x at %d IRQ %s (%s)%s\n",
				    phy->phy_id, phy_addr, irq_str,
				    dev_name(&phy->dev), act ? " active" : "");
			phy_found = true;
		}
	}

	if (!phy_found) {
		netdev_err(ndev, "PHY not found\n");
		goto phyfound_err;
	}

	priv->mii = mdio_bus;

	return 0;

phyfound_err:
	err = -ENODEV;
	mdiobus_unregister(mdio_bus);
mdiobus_err:
	mdiobus_free(mdio_bus);
	return err;
}

int sxgbe_mdio_unregister(struct net_device *ndev)
{
	struct sxgbe_priv_data *priv = netdev_priv(ndev);

	if (!priv->mii)
		return 0;

	mdiobus_unregister(priv->mii);
	priv->mii->priv = NULL;
	mdiobus_free(priv->mii);
	priv->mii = NULL;

	return 0;
}
OpenPOWER on IntegriCloud