summaryrefslogtreecommitdiffstats
path: root/drivers/net/ethernet/sunplus/spl2sw_mdio.c
blob: 733ae17042694d4f89df63981b50e2f145e07d79 (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
// SPDX-License-Identifier: GPL-2.0
/* Copyright Sunplus Technology Co., Ltd.
 *       All rights reserved.
 */

#include <linux/platform_device.h>
#include <linux/netdevice.h>
#include <linux/bitfield.h>
#include <linux/of_mdio.h>

#include "spl2sw_register.h"
#include "spl2sw_define.h"
#include "spl2sw_mdio.h"

#define SPL2SW_MDIO_READ_CMD           0x02
#define SPL2SW_MDIO_WRITE_CMD          0x01

static int spl2sw_mdio_access(struct spl2sw_common *comm, u8 cmd, u8 addr, u8 regnum, u16 wdata)
{
	u32 reg, reg2;
	u32 val;
	int ret;

	/* Note that addr (of phy) should match either ext_phy0_addr
	 * or ext_phy1_addr, or mdio commands won't be sent out.
	 */
	reg = readl(comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
	reg &= ~MAC_EXT_PHY0_ADDR;
	reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, addr);

	reg2 = FIELD_PREP(MAC_CPU_PHY_WT_DATA, wdata) | FIELD_PREP(MAC_CPU_PHY_CMD, cmd) |
	       FIELD_PREP(MAC_CPU_PHY_REG_ADDR, regnum) | FIELD_PREP(MAC_CPU_PHY_ADDR, addr);

	/* Set ext_phy0_addr and then issue mdio command.
	 * No interrupt is allowed in between.
	 */
	spin_lock_irq(&comm->mdio_lock);
	writel(reg, comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
	writel(reg2, comm->l2sw_reg_base + L2SW_PHY_CNTL_REG0);
	spin_unlock_irq(&comm->mdio_lock);

	ret = read_poll_timeout(readl, val, val & cmd, 1, 1000, true,
				comm->l2sw_reg_base + L2SW_PHY_CNTL_REG1);

	/* Set ext_phy0_addr back to 31 to prevent
	 * from sending mdio command to phy by
	 * hardware auto-mdio function.
	 */
	reg = readl(comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);
	reg &= ~MAC_EXT_PHY0_ADDR;
	reg |= FIELD_PREP(MAC_EXT_PHY0_ADDR, 31);
	writel(reg, comm->l2sw_reg_base + L2SW_MAC_FORCE_MODE);

	if (ret == 0)
		return val >> 16;
	else
		return ret;
}

static int spl2sw_mii_read(struct mii_bus *bus, int addr, int regnum)
{
	struct spl2sw_common *comm = bus->priv;

	if (regnum & MII_ADDR_C45)
		return -EOPNOTSUPP;

	return spl2sw_mdio_access(comm, SPL2SW_MDIO_READ_CMD, addr, regnum, 0);
}

static int spl2sw_mii_write(struct mii_bus *bus, int addr, int regnum, u16 val)
{
	struct spl2sw_common *comm = bus->priv;
	int ret;

	if (regnum & MII_ADDR_C45)
		return -EOPNOTSUPP;

	ret = spl2sw_mdio_access(comm, SPL2SW_MDIO_WRITE_CMD, addr, regnum, val);
	if (ret < 0)
		return ret;

	return 0;
}

u32 spl2sw_mdio_init(struct spl2sw_common *comm)
{
	struct device_node *mdio_np;
	struct mii_bus *mii_bus;
	int ret;

	/* Get mdio child node. */
	mdio_np = of_get_child_by_name(comm->pdev->dev.of_node, "mdio");
	if (!mdio_np) {
		dev_err(&comm->pdev->dev, "No mdio child node found!\n");
		return -ENODEV;
	}

	/* Allocate and register mdio bus. */
	mii_bus = devm_mdiobus_alloc(&comm->pdev->dev);
	if (!mii_bus) {
		ret = -ENOMEM;
		goto out;
	}

	mii_bus->name = "sunplus_mii_bus";
	mii_bus->parent = &comm->pdev->dev;
	mii_bus->priv = comm;
	mii_bus->read = spl2sw_mii_read;
	mii_bus->write = spl2sw_mii_write;
	snprintf(mii_bus->id, MII_BUS_ID_SIZE, "%s-mii", dev_name(&comm->pdev->dev));

	ret = of_mdiobus_register(mii_bus, mdio_np);
	if (ret) {
		dev_err(&comm->pdev->dev, "Failed to register mdiobus!\n");
		goto out;
	}

	comm->mii_bus = mii_bus;

out:
	of_node_put(mdio_np);
	return ret;
}

void spl2sw_mdio_remove(struct spl2sw_common *comm)
{
	if (comm->mii_bus) {
		mdiobus_unregister(comm->mii_bus);
		comm->mii_bus = NULL;
	}
}