1/*
2 * Driver for Aquantia PHY
3 *
4 * Author: Shaohui Xie <Shaohui.Xie@freescale.com>
5 *
6 * Copyright 2015 Freescale Semiconductor, Inc.
7 *
8 * This file is licensed under the terms of the GNU General Public License
9 * version 2.  This program is licensed "as is" without any warranty of any
10 * kind, whether express or implied.
11 */
12
13#include <linux/kernel.h>
14#include <linux/module.h>
15#include <linux/delay.h>
16#include <linux/mii.h>
17#include <linux/ethtool.h>
18#include <linux/phy.h>
19#include <linux/mdio.h>
20
21#define PHY_ID_AQ1202	0x03a1b445
22#define PHY_ID_AQ2104	0x03a1b460
23#define PHY_ID_AQR105	0x03a1b4a2
24#define PHY_ID_AQR405	0x03a1b4b0
25
26#define PHY_AQUANTIA_FEATURES	(SUPPORTED_10000baseT_Full | \
27				 SUPPORTED_1000baseT_Full | \
28				 SUPPORTED_100baseT_Full | \
29				 PHY_DEFAULT_FEATURES)
30
31static int aquantia_config_aneg(struct phy_device *phydev)
32{
33	phydev->supported = PHY_AQUANTIA_FEATURES;
34	phydev->advertising = phydev->supported;
35
36	return 0;
37}
38
39static int aquantia_aneg_done(struct phy_device *phydev)
40{
41	int reg;
42
43	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
44	return (reg < 0) ? reg : (reg & BMSR_ANEGCOMPLETE);
45}
46
47static int aquantia_config_intr(struct phy_device *phydev)
48{
49	int err;
50
51	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
52		err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 1);
53		if (err < 0)
54			return err;
55
56		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 1);
57		if (err < 0)
58			return err;
59
60		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0x1001);
61	} else {
62		err = phy_write_mmd(phydev, MDIO_MMD_AN, 0xd401, 0);
63		if (err < 0)
64			return err;
65
66		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff00, 0);
67		if (err < 0)
68			return err;
69
70		err = phy_write_mmd(phydev, MDIO_MMD_VEND1, 0xff01, 0);
71	}
72
73	return err;
74}
75
76static int aquantia_ack_interrupt(struct phy_device *phydev)
77{
78	int reg;
79
80	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xcc01);
81	return (reg < 0) ? reg : 0;
82}
83
84static int aquantia_read_status(struct phy_device *phydev)
85{
86	int reg;
87
88	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
89	reg = phy_read_mmd(phydev, MDIO_MMD_AN, MDIO_STAT1);
90	if (reg & MDIO_STAT1_LSTATUS)
91		phydev->link = 1;
92	else
93		phydev->link = 0;
94
95	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800);
96	mdelay(10);
97	reg = phy_read_mmd(phydev, MDIO_MMD_AN, 0xc800);
98
99	switch (reg) {
100	case 0x9:
101		phydev->speed = SPEED_2500;
102		break;
103	case 0x5:
104		phydev->speed = SPEED_1000;
105		break;
106	case 0x3:
107		phydev->speed = SPEED_100;
108		break;
109	case 0x7:
110	default:
111		phydev->speed = SPEED_10000;
112		break;
113	}
114	phydev->duplex = DUPLEX_FULL;
115
116	return 0;
117}
118
119static struct phy_driver aquantia_driver[] = {
120{
121	.phy_id		= PHY_ID_AQ1202,
122	.phy_id_mask	= 0xfffffff0,
123	.name		= "Aquantia AQ1202",
124	.features	= PHY_AQUANTIA_FEATURES,
125	.flags		= PHY_HAS_INTERRUPT,
126	.aneg_done	= aquantia_aneg_done,
127	.config_aneg    = aquantia_config_aneg,
128	.config_intr	= aquantia_config_intr,
129	.ack_interrupt	= aquantia_ack_interrupt,
130	.read_status	= aquantia_read_status,
131	.driver		= { .owner = THIS_MODULE,},
132},
133{
134	.phy_id		= PHY_ID_AQ2104,
135	.phy_id_mask	= 0xfffffff0,
136	.name		= "Aquantia AQ2104",
137	.features	= PHY_AQUANTIA_FEATURES,
138	.flags		= PHY_HAS_INTERRUPT,
139	.aneg_done	= aquantia_aneg_done,
140	.config_aneg    = aquantia_config_aneg,
141	.config_intr	= aquantia_config_intr,
142	.ack_interrupt	= aquantia_ack_interrupt,
143	.read_status	= aquantia_read_status,
144	.driver		= { .owner = THIS_MODULE,},
145},
146{
147	.phy_id		= PHY_ID_AQR105,
148	.phy_id_mask	= 0xfffffff0,
149	.name		= "Aquantia AQR105",
150	.features	= PHY_AQUANTIA_FEATURES,
151	.flags		= PHY_HAS_INTERRUPT,
152	.aneg_done	= aquantia_aneg_done,
153	.config_aneg    = aquantia_config_aneg,
154	.config_intr	= aquantia_config_intr,
155	.ack_interrupt	= aquantia_ack_interrupt,
156	.read_status	= aquantia_read_status,
157	.driver		= { .owner = THIS_MODULE,},
158},
159{
160	.phy_id		= PHY_ID_AQR405,
161	.phy_id_mask	= 0xfffffff0,
162	.name		= "Aquantia AQR405",
163	.features	= PHY_AQUANTIA_FEATURES,
164	.flags		= PHY_HAS_INTERRUPT,
165	.aneg_done	= aquantia_aneg_done,
166	.config_aneg    = aquantia_config_aneg,
167	.config_intr	= aquantia_config_intr,
168	.ack_interrupt	= aquantia_ack_interrupt,
169	.read_status	= aquantia_read_status,
170	.driver		= { .owner = THIS_MODULE,},
171},
172};
173
174module_phy_driver(aquantia_driver);
175
176static struct mdio_device_id __maybe_unused aquantia_tbl[] = {
177	{ PHY_ID_AQ1202, 0xfffffff0 },
178	{ PHY_ID_AQ2104, 0xfffffff0 },
179	{ PHY_ID_AQR105, 0xfffffff0 },
180	{ PHY_ID_AQR405, 0xfffffff0 },
181	{ }
182};
183
184MODULE_DEVICE_TABLE(mdio, aquantia_tbl);
185
186MODULE_DESCRIPTION("Aquantia PHY driver");
187MODULE_AUTHOR("Shaohui Xie <Shaohui.Xie@freescale.com>");
188MODULE_LICENSE("GPL v2");
189