summaryrefslogtreecommitdiff
path: root/drivers/net/mscc_eswitch/mscc_miim.c
blob: 6687787b1a6aa988e44b8b5494b1fc8dabe667b8 (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
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2018 Microsemi Corporation
 */

#include <miiphy.h>
#include <wait_bit.h>
#include <linux/bitops.h>
#include "mscc_miim.h"

#define MIIM_STATUS			0x0
#define		MIIM_STAT_BUSY			BIT(3)
#define MIIM_CMD			0x8
#define		MIIM_CMD_SCAN		BIT(0)
#define		MIIM_CMD_OPR_WRITE	BIT(1)
#define		MIIM_CMD_OPR_READ	BIT(2)
#define		MIIM_CMD_SINGLE_SCAN	BIT(3)
#define		MIIM_CMD_WRDATA(x)	((x) << 4)
#define		MIIM_CMD_REGAD(x)	((x) << 20)
#define		MIIM_CMD_PHYAD(x)	((x) << 25)
#define		MIIM_CMD_VLD		BIT(31)
#define MIIM_DATA			0xC
#define		MIIM_DATA_ERROR		(0x2 << 16)

static int mscc_miim_wait_ready(struct mscc_miim_dev *miim)
{
	return wait_for_bit_le32(miim->regs + MIIM_STATUS, MIIM_STAT_BUSY,
				 false, 250, false);
}

int mscc_miim_read(struct mii_dev *bus, int addr, int devad, int reg)
{
	struct mscc_miim_dev *miim = (struct mscc_miim_dev *)bus->priv;
	u32 val;
	int ret;

	ret = mscc_miim_wait_ready(miim);
	if (ret)
		goto out;

	writel(MIIM_CMD_VLD | MIIM_CMD_PHYAD(addr) |
	       MIIM_CMD_REGAD(reg) | MIIM_CMD_OPR_READ,
	       miim->regs + MIIM_CMD);

	ret = mscc_miim_wait_ready(miim);
	if (ret)
		goto out;

	val = readl(miim->regs + MIIM_DATA);
	if (val & MIIM_DATA_ERROR) {
		ret = -EIO;
		goto out;
	}

	ret = val & 0xFFFF;
 out:
	return ret;
}

int mscc_miim_write(struct mii_dev *bus, int addr, int devad, int reg,
		    u16 val)
{
	struct mscc_miim_dev *miim = (struct mscc_miim_dev *)bus->priv;
	int ret;

	ret = mscc_miim_wait_ready(miim);
	if (ret < 0)
		goto out;

	writel(MIIM_CMD_VLD | MIIM_CMD_PHYAD(addr) |
	       MIIM_CMD_REGAD(reg) | MIIM_CMD_WRDATA(val) |
	       MIIM_CMD_OPR_WRITE, miim->regs + MIIM_CMD);
 out:
	return ret;
}

struct mii_dev *mscc_mdiobus_init(struct mscc_miim_dev *miim, int *miim_count,
				  phys_addr_t miim_base,
				  unsigned long miim_size)
{
	struct mii_dev *bus;

	bus = mdio_alloc();

	if (!bus)
		return NULL;

	*miim_count += 1;
	sprintf(bus->name, "miim-bus%d", *miim_count);

	miim[*miim_count].regs = ioremap(miim_base, miim_size);
	miim[*miim_count].miim_base = miim_base;
	miim[*miim_count].miim_size = miim_size;
	bus->priv = &miim[*miim_count];
	bus->read = mscc_miim_read;
	bus->write = mscc_miim_write;

	if (mdio_register(bus))
		return NULL;

	miim[*miim_count].bus = bus;
	return bus;
}