1/**************************************************************************
2SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3
4Copyright (c) 2008, Chelsio Inc.
5All rights reserved.
6
7Redistribution and use in source and binary forms, with or without
8modification, are permitted provided that the following conditions are met:
9
10 1. Redistributions of source code must retain the above copyright notice,
11    this list of conditions and the following disclaimer.
12
13 2. Neither the name of the Chelsio Corporation nor the names of its
14    contributors may be used to endorse or promote products derived from
15    this software without specific prior written permission.
16
17THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27POSSIBILITY OF SUCH DAMAGE.
28
29***************************************************************************/
30
31#include <sys/cdefs.h>
32__FBSDID("$FreeBSD$");
33
34#include <cxgb_include.h>
35
36#undef msleep
37#define msleep t3_os_sleep
38
39/* TN1010 PHY specific registers. */
40enum {
41	TN1010_VEND1_STAT = 1,
42};
43
44/* IEEE auto-negotiation 10GBASE-T registers */
45enum {
46	ANEG_ADVER    = 16,
47	ANEG_LPA      = 19,
48	ANEG_10G_CTRL = 32,
49	ANEG_10G_STAT = 33
50};
51
52#define ADVERTISE_ENPAGE      (1 << 12)
53#define ADVERTISE_10000FULL   (1 << 12)
54#define ADVERTISE_LOOP_TIMING (1 << 0)
55
56/* vendor specific status register fields */
57#define F_XS_LANE_ALIGN_STAT (1 << 0)
58#define F_PCS_BLK_LOCK       (1 << 1)
59#define F_PMD_SIGNAL_OK      (1 << 2)
60#define F_LINK_STAT          (1 << 3)
61#define F_ANEG_SPEED_1G      (1 << 4)
62#define F_ANEG_MASTER        (1 << 5)
63
64#define S_ANEG_STAT    6
65#define M_ANEG_STAT    0x3
66#define G_ANEG_STAT(x) (((x) >> S_ANEG_STAT) & M_ANEG_STAT)
67
68enum {                        /* autonegotiation status */
69	ANEG_IN_PROGR = 0,
70	ANEG_COMPLETE = 1,
71	ANEG_FAILED   = 3
72};
73
74/*
75 * Reset the PHY.  May take up to 500ms to complete.
76 */
77static int tn1010_reset(struct cphy *phy, int wait)
78{
79	int err = t3_phy_reset(phy, MDIO_DEV_PMA_PMD, wait);
80	msleep(500);
81	return err;
82}
83
84static int tn1010_power_down(struct cphy *phy, int enable)
85{
86	return t3_mdio_change_bits(phy, MDIO_DEV_PMA_PMD, MII_BMCR,
87				   BMCR_PDOWN, enable ? BMCR_PDOWN : 0);
88}
89
90static int tn1010_autoneg_enable(struct cphy *phy)
91{
92	int err;
93
94	err = tn1010_power_down(phy, 0);
95	if (!err)
96		err = t3_mdio_change_bits(phy, MDIO_DEV_ANEG, MII_BMCR, 0,
97					  BMCR_ANENABLE | BMCR_ANRESTART);
98	return err;
99}
100
101static int tn1010_autoneg_restart(struct cphy *phy)
102{
103	int err;
104
105	err = tn1010_power_down(phy, 0);
106	if (!err)
107		err = t3_mdio_change_bits(phy, MDIO_DEV_ANEG, MII_BMCR, 0,
108					  BMCR_ANRESTART);
109	return err;
110}
111
112static int tn1010_advertise(struct cphy *phy, unsigned int advert)
113{
114	int err, val;
115
116	if (!(advert & ADVERTISED_1000baseT_Full))
117		return -EINVAL;               /* PHY can't disable 1000BASE-T */
118
119	val = ADVERTISE_CSMA | ADVERTISE_ENPAGE | ADVERTISE_NPAGE;
120	if (advert & ADVERTISED_Pause)
121		val |= ADVERTISE_PAUSE_CAP;
122	if (advert & ADVERTISED_Asym_Pause)
123		val |= ADVERTISE_PAUSE_ASYM;
124	err = mdio_write(phy, MDIO_DEV_ANEG, ANEG_ADVER, val);
125	if (err)
126		return err;
127
128	val = (advert & ADVERTISED_10000baseT_Full) ? ADVERTISE_10000FULL : 0;
129	return mdio_write(phy, MDIO_DEV_ANEG, ANEG_10G_CTRL, val |
130			  ADVERTISE_LOOP_TIMING);
131}
132
133static int tn1010_get_link_status(struct cphy *phy, int *link_state,
134				  int *speed, int *duplex, int *fc)
135{
136	unsigned int status, lpa, adv;
137	int err, sp = -1, pause = 0;
138
139	err = mdio_read(phy, MDIO_DEV_VEND1, TN1010_VEND1_STAT, &status);
140	if (err)
141		return err;
142
143	if (link_state)
144		*link_state = status & F_LINK_STAT ? PHY_LINK_UP :
145		    PHY_LINK_DOWN;
146
147	if (G_ANEG_STAT(status) == ANEG_COMPLETE) {
148		sp = (status & F_ANEG_SPEED_1G) ? SPEED_1000 : SPEED_10000;
149
150		if (fc) {
151			err = mdio_read(phy, MDIO_DEV_ANEG, ANEG_LPA, &lpa);
152			if (!err)
153				err = mdio_read(phy, MDIO_DEV_ANEG, ANEG_ADVER,
154						&adv);
155			if (err)
156				return err;
157
158			if (lpa & adv & ADVERTISE_PAUSE_CAP)
159				pause = PAUSE_RX | PAUSE_TX;
160			else if ((lpa & ADVERTISE_PAUSE_CAP) &&
161				 (lpa & ADVERTISE_PAUSE_ASYM) &&
162				 (adv & ADVERTISE_PAUSE_ASYM))
163				pause = PAUSE_TX;
164			else if ((lpa & ADVERTISE_PAUSE_ASYM) &&
165				 (adv & ADVERTISE_PAUSE_CAP))
166				pause = PAUSE_RX;
167		}
168	}
169	if (speed)
170		*speed = sp;
171	if (duplex)
172		*duplex = DUPLEX_FULL;
173	if (fc)
174		*fc = pause;
175	return 0;
176}
177
178static int tn1010_set_speed_duplex(struct cphy *phy, int speed, int duplex)
179{
180	return -EINVAL;    /* require autoneg */
181}
182
183#ifdef C99_NOT_SUPPORTED
184static struct cphy_ops tn1010_ops = {
185	tn1010_reset,
186	t3_phy_lasi_intr_enable,
187	t3_phy_lasi_intr_disable,
188	t3_phy_lasi_intr_clear,
189	t3_phy_lasi_intr_handler,
190	tn1010_autoneg_enable,
191	tn1010_autoneg_restart,
192	tn1010_advertise,
193	NULL,
194	tn1010_set_speed_duplex,
195	tn1010_get_link_status,
196	tn1010_power_down,
197};
198#else
199static struct cphy_ops tn1010_ops = {
200	.reset             = tn1010_reset,
201	.intr_enable       = t3_phy_lasi_intr_enable,
202	.intr_disable      = t3_phy_lasi_intr_disable,
203	.intr_clear        = t3_phy_lasi_intr_clear,
204	.intr_handler      = t3_phy_lasi_intr_handler,
205	.autoneg_enable    = tn1010_autoneg_enable,
206	.autoneg_restart   = tn1010_autoneg_restart,
207	.advertise         = tn1010_advertise,
208	.set_speed_duplex  = tn1010_set_speed_duplex,
209	.get_link_status   = tn1010_get_link_status,
210	.power_down        = tn1010_power_down,
211};
212#endif
213
214int t3_tn1010_phy_prep(pinfo_t *pinfo, int phy_addr,
215		       const struct mdio_ops *mdio_ops)
216{
217	cphy_init(&pinfo->phy, pinfo->adapter, pinfo, phy_addr, &tn1010_ops, mdio_ops,
218		  SUPPORTED_1000baseT_Full | SUPPORTED_10000baseT_Full |
219		  SUPPORTED_Autoneg | SUPPORTED_AUI | SUPPORTED_TP,
220		  "1000/10GBASE-T");
221	msleep(500);    /* PHY needs up to 500ms to start responding to MDIO */
222	return 0;
223}
224