aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/staging/octeon/ethernet-rgmii.c
blob: d91fd5ce9e68fdfeeef1f2e84172832b18dca14b (plain) (blame)
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
// SPDX-License-Identifier: GPL-2.0
/*
 * This file is based on code from OCTEON SDK by Cavium Networks.
 *
 * Copyright (c) 2003-2007 Cavium Networks
 */

#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/interrupt.h>
#include <linux/phy.h>
#include <linux/ratelimit.h>
#include <net/dst.h>

#include "octeon-ethernet.h"
#include "ethernet-defines.h"
#include "ethernet-util.h"
#include "ethernet-mdio.h"

static DEFINE_SPINLOCK(global_register_lock);

static void cvm_oct_set_hw_preamble(struct octeon_ethernet *priv, bool enable)
{
	union cvmx_gmxx_rxx_frm_ctl gmxx_rxx_frm_ctl;
	union cvmx_ipd_sub_port_fcs ipd_sub_port_fcs;
	union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;
	int interface = INTERFACE(priv->port);
	int index = INDEX(priv->port);

	/* Set preamble checking. */
	gmxx_rxx_frm_ctl.u64 = cvmx_read_csr(CVMX_GMXX_RXX_FRM_CTL(index,
								   interface));
	gmxx_rxx_frm_ctl.s.pre_chk = enable;
	cvmx_write_csr(CVMX_GMXX_RXX_FRM_CTL(index, interface),
		       gmxx_rxx_frm_ctl.u64);

	/* Set FCS stripping. */
	ipd_sub_port_fcs.u64 = cvmx_read_csr(CVMX_IPD_SUB_PORT_FCS);
	if (enable)
		ipd_sub_port_fcs.s.port_bit |= 1ull << priv->port;
	else
		ipd_sub_port_fcs.s.port_bit &=
					0xffffffffull ^ (1ull << priv->port);
	cvmx_write_csr(CVMX_IPD_SUB_PORT_FCS, ipd_sub_port_fcs.u64);

	/* Clear any error bits. */
	gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG(index,
								   interface));
	cvmx_write_csr(CVMX_GMXX_RXX_INT_REG(index, interface),
		       gmxx_rxx_int_reg.u64);
}

static void cvm_oct_check_preamble_errors(struct net_device *dev)
{
	struct octeon_ethernet *priv = netdev_priv(dev);
	cvmx_helper_link_info_t link_info;
	unsigned long flags;

	link_info.u64 = priv->link_info;

	/*
	 * Take the global register lock since we are going to
	 * touch registers that affect more than one port.
	 */
	spin_lock_irqsave(&global_register_lock, flags);

	if (link_info.s.speed == 10 && priv->last_speed == 10) {
		/*
		 * Read the GMXX_RXX_INT_REG[PCTERR] bit and see if we are
		 * getting preamble errors.
		 */
		int interface = INTERFACE(priv->port);
		int index = INDEX(priv->port);
		union cvmx_gmxx_rxx_int_reg gmxx_rxx_int_reg;

		gmxx_rxx_int_reg.u64 = cvmx_read_csr(CVMX_GMXX_RXX_INT_REG
							(index, interface));
		if (gmxx_rxx_int_reg.s.pcterr) {
			/*
			 * We are getting preamble errors at 10Mbps. Most
			 * likely the PHY is giving us packets with misaligned
			 * preambles. In order to get these packets we need to
			 * disable preamble checking and do it in software.
			 */
			cvm_oct_set_hw_preamble(priv, false);
			printk_ratelimited("%s: Using 10Mbps with software preamble removal\n",
					   dev->name);
		}
	} else {
		/*
		 * Since the 10Mbps preamble workaround is allowed we need to
		 * enable preamble checking, FCS stripping, and clear error
		 * bits on every speed change. If errors occur during 10Mbps
		 * operation the above code will change this stuff
		 */
		if (priv->last_speed != link_info.s.speed)
			cvm_oct_set_hw_preamble(priv, true);
		priv->last_speed = link_info.s.speed;
	}
	spin_unlock_irqrestore(&global_register_lock, flags);
}

static void cvm_oct_rgmii_poll(struct net_device *dev)
{
	struct octeon_ethernet *priv = netdev_priv(dev);
	cvmx_helper_link_info_t link_info;
	bool status_change;

	link_info = cvmx_helper_link_get(priv->port);
	if (priv->link_info != link_info.u64 &&
	    cvmx_helper_link_set(priv->port, link_info))
		link_info.u64 = priv->link_info;
	status_change = priv->link_info != link_info.u64;
	priv->link_info = link_info.u64;

	cvm_oct_check_preamble_errors(dev);

	if (likely(!status_change))
		return;

	/* Tell core. */
	if (link_info.s.link_up) {
		if (!netif_carrier_ok(dev))
			netif_carrier_on(dev);
	} else if (netif_carrier_ok(dev)) {
		netif_carrier_off(dev);
	}
	cvm_oct_note_carrier(priv, link_info);
}

int cvm_oct_rgmii_open(struct net_device *dev)
{
	struct octeon_ethernet *priv = netdev_priv(dev);
	int ret;

	ret = cvm_oct_common_open(dev, cvm_oct_rgmii_poll);
	if (ret)
		return ret;

	if (dev->phydev) {
		/*
		 * In phydev mode, we need still periodic polling for the
		 * preamble error checking, and we also need to call this
		 * function on every link state change.
		 *
		 * Only true RGMII ports need to be polled. In GMII mode, port
		 * 0 is really a RGMII port.
		 */
		if ((priv->imode == CVMX_HELPER_INTERFACE_MODE_GMII &&
		     priv->port  == 0) ||
		    (priv->imode == CVMX_HELPER_INTERFACE_MODE_RGMII)) {
			priv->poll = cvm_oct_check_preamble_errors;
			cvm_oct_check_preamble_errors(dev);
		}
	}

	return 0;
}