]>
Commit | Line | Data |
---|---|---|
7334b3e4 NA |
1 | /* |
2 | * Amlogic Meson GXL Internal PHY Driver | |
3 | * | |
4 | * Copyright (C) 2015 Amlogic, Inc. All rights reserved. | |
5 | * Copyright (C) 2016 BayLibre, SAS. All rights reserved. | |
6 | * Author: Neil Armstrong <narmstrong@baylibre.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License as published by | |
10 | * the Free Software Foundation; either version 2 of the License, or | |
11 | * (at your option) any later version. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
14 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
15 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
16 | * more details. | |
17 | * | |
18 | */ | |
19 | #include <linux/kernel.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/mii.h> | |
22 | #include <linux/ethtool.h> | |
23 | #include <linux/phy.h> | |
24 | #include <linux/netdevice.h> | |
f1e2400a | 25 | #include <linux/bitfield.h> |
7334b3e4 | 26 | |
00fd73eb JB |
27 | #define TSTCNTL 20 |
28 | #define TSTCNTL_READ BIT(15) | |
29 | #define TSTCNTL_WRITE BIT(14) | |
30 | #define TSTCNTL_REG_BANK_SEL GENMASK(12, 11) | |
31 | #define TSTCNTL_TEST_MODE BIT(10) | |
32 | #define TSTCNTL_READ_ADDRESS GENMASK(9, 5) | |
33 | #define TSTCNTL_WRITE_ADDRESS GENMASK(4, 0) | |
34 | #define TSTREAD1 21 | |
35 | #define TSTWRITE 23 | |
cf127ff2 JB |
36 | #define INTSRC_FLAG 29 |
37 | #define INTSRC_ANEG_PR BIT(1) | |
38 | #define INTSRC_PARALLEL_FAULT BIT(2) | |
39 | #define INTSRC_ANEG_LP_ACK BIT(3) | |
40 | #define INTSRC_LINK_DOWN BIT(4) | |
41 | #define INTSRC_REMOTE_FAULT BIT(5) | |
42 | #define INTSRC_ANEG_COMPLETE BIT(6) | |
43 | #define INTSRC_MASK 30 | |
00fd73eb JB |
44 | |
45 | #define BANK_ANALOG_DSP 0 | |
46 | #define BANK_WOL 1 | |
47 | #define BANK_BIST 3 | |
48 | ||
00fd73eb JB |
49 | /* WOL Registers */ |
50 | #define LPI_STATUS 0xc | |
51 | #define LPI_STATUS_RSV12 BIT(12) | |
52 | ||
53 | /* BIST Registers */ | |
54 | #define FR_PLL_CONTROL 0x1b | |
55 | #define FR_PLL_DIV0 0x1c | |
56 | #define FR_PLL_DIV1 0x1d | |
57 | ||
fdaa84c3 | 58 | static int meson_gxl_open_banks(struct phy_device *phydev) |
7334b3e4 | 59 | { |
9042b46e JB |
60 | int ret; |
61 | ||
fdaa84c3 JB |
62 | /* Enable Analog and DSP register Bank access by |
63 | * toggling TSTCNTL_TEST_MODE bit in the TSTCNTL register | |
64 | */ | |
00fd73eb | 65 | ret = phy_write(phydev, TSTCNTL, 0); |
9042b46e JB |
66 | if (ret) |
67 | return ret; | |
00fd73eb | 68 | ret = phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); |
9042b46e JB |
69 | if (ret) |
70 | return ret; | |
00fd73eb | 71 | ret = phy_write(phydev, TSTCNTL, 0); |
9042b46e JB |
72 | if (ret) |
73 | return ret; | |
fdaa84c3 JB |
74 | return phy_write(phydev, TSTCNTL, TSTCNTL_TEST_MODE); |
75 | } | |
7334b3e4 | 76 | |
fdaa84c3 JB |
77 | static void meson_gxl_close_banks(struct phy_device *phydev) |
78 | { | |
79 | phy_write(phydev, TSTCNTL, 0); | |
80 | } | |
81 | ||
82 | static int meson_gxl_read_reg(struct phy_device *phydev, | |
83 | unsigned int bank, unsigned int reg) | |
84 | { | |
85 | int ret; | |
86 | ||
87 | ret = meson_gxl_open_banks(phydev); | |
9042b46e | 88 | if (ret) |
fdaa84c3 JB |
89 | goto out; |
90 | ||
91 | ret = phy_write(phydev, TSTCNTL, TSTCNTL_READ | | |
92 | FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | | |
93 | TSTCNTL_TEST_MODE | | |
94 | FIELD_PREP(TSTCNTL_READ_ADDRESS, reg)); | |
9042b46e | 95 | if (ret) |
fdaa84c3 | 96 | goto out; |
7334b3e4 | 97 | |
fdaa84c3 JB |
98 | ret = phy_read(phydev, TSTREAD1); |
99 | out: | |
100 | /* Close the bank access on our way out */ | |
101 | meson_gxl_close_banks(phydev); | |
102 | return ret; | |
103 | } | |
104 | ||
105 | static int meson_gxl_write_reg(struct phy_device *phydev, | |
106 | unsigned int bank, unsigned int reg, | |
107 | uint16_t value) | |
108 | { | |
109 | int ret; | |
110 | ||
111 | ret = meson_gxl_open_banks(phydev); | |
9042b46e | 112 | if (ret) |
fdaa84c3 JB |
113 | goto out; |
114 | ||
115 | ret = phy_write(phydev, TSTWRITE, value); | |
9042b46e | 116 | if (ret) |
fdaa84c3 | 117 | goto out; |
7334b3e4 | 118 | |
fdaa84c3 JB |
119 | ret = phy_write(phydev, TSTCNTL, TSTCNTL_WRITE | |
120 | FIELD_PREP(TSTCNTL_REG_BANK_SEL, bank) | | |
121 | TSTCNTL_TEST_MODE | | |
122 | FIELD_PREP(TSTCNTL_WRITE_ADDRESS, reg)); | |
123 | ||
124 | out: | |
125 | /* Close the bank access on our way out */ | |
126 | meson_gxl_close_banks(phydev); | |
127 | return ret; | |
128 | } | |
129 | ||
130 | static int meson_gxl_config_init(struct phy_device *phydev) | |
131 | { | |
132 | int ret; | |
133 | ||
fdaa84c3 JB |
134 | /* Enable fractional PLL */ |
135 | ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_CONTROL, 0x5); | |
9042b46e JB |
136 | if (ret) |
137 | return ret; | |
7334b3e4 NA |
138 | |
139 | /* Program fraction FR_PLL_DIV1 */ | |
fdaa84c3 | 140 | ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV1, 0x029a); |
9042b46e JB |
141 | if (ret) |
142 | return ret; | |
fdaa84c3 JB |
143 | |
144 | /* Program fraction FR_PLL_DIV1 */ | |
145 | ret = meson_gxl_write_reg(phydev, BANK_BIST, FR_PLL_DIV0, 0xaaaa); | |
9042b46e JB |
146 | if (ret) |
147 | return ret; | |
7334b3e4 | 148 | |
c1e53551 | 149 | return genphy_config_init(phydev); |
7334b3e4 NA |
150 | } |
151 | ||
f1e2400a JB |
152 | /* This function is provided to cope with the possible failures of this phy |
153 | * during aneg process. When aneg fails, the PHY reports that aneg is done | |
154 | * but the value found in MII_LPA is wrong: | |
155 | * - Early failures: MII_LPA is just 0x0001. if MII_EXPANSION reports that | |
156 | * the link partner (LP) supports aneg but the LP never acked our base | |
157 | * code word, it is likely that we never sent it to begin with. | |
158 | * - Late failures: MII_LPA is filled with a value which seems to make sense | |
159 | * but it actually is not what the LP is advertising. It seems that we | |
160 | * can detect this using a magic bit in the WOL bank (reg 12 - bit 12). | |
161 | * If this particular bit is not set when aneg is reported being done, | |
162 | * it means MII_LPA is likely to be wrong. | |
163 | * | |
164 | * In both case, forcing a restart of the aneg process solve the problem. | |
165 | * When this failure happens, the first retry is usually successful but, | |
166 | * in some cases, it may take up to 6 retries to get a decent result | |
167 | */ | |
3b3397e2 | 168 | static int meson_gxl_read_status(struct phy_device *phydev) |
f1e2400a JB |
169 | { |
170 | int ret, wol, lpa, exp; | |
171 | ||
172 | if (phydev->autoneg == AUTONEG_ENABLE) { | |
173 | ret = genphy_aneg_done(phydev); | |
174 | if (ret < 0) | |
175 | return ret; | |
176 | else if (!ret) | |
177 | goto read_status_continue; | |
178 | ||
fdaa84c3 JB |
179 | /* Aneg is done, let's check everything is fine */ |
180 | wol = meson_gxl_read_reg(phydev, BANK_WOL, LPI_STATUS); | |
f1e2400a JB |
181 | if (wol < 0) |
182 | return wol; | |
183 | ||
184 | lpa = phy_read(phydev, MII_LPA); | |
185 | if (lpa < 0) | |
186 | return lpa; | |
187 | ||
188 | exp = phy_read(phydev, MII_EXPANSION); | |
189 | if (exp < 0) | |
190 | return exp; | |
191 | ||
00fd73eb | 192 | if (!(wol & LPI_STATUS_RSV12) || |
f1e2400a JB |
193 | ((exp & EXPANSION_NWAY) && !(lpa & LPA_LPACK))) { |
194 | /* Looks like aneg failed after all */ | |
195 | phydev_dbg(phydev, "LPA corruption - aneg restart\n"); | |
196 | return genphy_restart_aneg(phydev); | |
197 | } | |
198 | } | |
199 | ||
200 | read_status_continue: | |
201 | return genphy_read_status(phydev); | |
202 | } | |
203 | ||
cf127ff2 JB |
204 | static int meson_gxl_ack_interrupt(struct phy_device *phydev) |
205 | { | |
206 | int ret = phy_read(phydev, INTSRC_FLAG); | |
207 | ||
208 | return ret < 0 ? ret : 0; | |
209 | } | |
210 | ||
211 | static int meson_gxl_config_intr(struct phy_device *phydev) | |
212 | { | |
213 | u16 val; | |
214 | ||
215 | if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { | |
216 | val = INTSRC_ANEG_PR | |
217 | | INTSRC_PARALLEL_FAULT | |
218 | | INTSRC_ANEG_LP_ACK | |
219 | | INTSRC_LINK_DOWN | |
220 | | INTSRC_REMOTE_FAULT | |
221 | | INTSRC_ANEG_COMPLETE; | |
222 | } else { | |
223 | val = 0; | |
224 | } | |
225 | ||
226 | return phy_write(phydev, INTSRC_MASK, val); | |
227 | } | |
228 | ||
7334b3e4 NA |
229 | static struct phy_driver meson_gxl_phy[] = { |
230 | { | |
231 | .phy_id = 0x01814400, | |
232 | .phy_id_mask = 0xfffffff0, | |
233 | .name = "Meson GXL Internal PHY", | |
234 | .features = PHY_BASIC_FEATURES, | |
cf127ff2 | 235 | .flags = PHY_IS_INTERNAL | PHY_HAS_INTERRUPT, |
7334b3e4 | 236 | .config_init = meson_gxl_config_init, |
7334b3e4 | 237 | .aneg_done = genphy_aneg_done, |
f1e2400a | 238 | .read_status = meson_gxl_read_status, |
cf127ff2 JB |
239 | .ack_interrupt = meson_gxl_ack_interrupt, |
240 | .config_intr = meson_gxl_config_intr, | |
7334b3e4 NA |
241 | .suspend = genphy_suspend, |
242 | .resume = genphy_resume, | |
243 | }, | |
244 | }; | |
245 | ||
246 | static struct mdio_device_id __maybe_unused meson_gxl_tbl[] = { | |
247 | { 0x01814400, 0xfffffff0 }, | |
248 | { } | |
249 | }; | |
250 | ||
251 | module_phy_driver(meson_gxl_phy); | |
252 | ||
253 | MODULE_DEVICE_TABLE(mdio, meson_gxl_tbl); | |
254 | ||
255 | MODULE_DESCRIPTION("Amlogic Meson GXL Internal PHY driver"); | |
256 | MODULE_AUTHOR("Baoqi wang"); | |
257 | MODULE_AUTHOR("Neil Armstrong <narmstrong@baylibre.com>"); | |
258 | MODULE_LICENSE("GPL"); |