diff options
Diffstat (limited to 'drivers/net/usb/smsc95xx.c')
-rw-r--r-- | drivers/net/usb/smsc95xx.c | 706 |
1 files changed, 482 insertions, 224 deletions
diff --git a/drivers/net/usb/smsc95xx.c b/drivers/net/usb/smsc95xx.c index 362cb8cfeb92..79d495d15546 100644 --- a/drivers/net/usb/smsc95xx.c +++ b/drivers/net/usb/smsc95xx.c | |||
@@ -26,6 +26,8 @@ | |||
26 | #include <linux/ethtool.h> | 26 | #include <linux/ethtool.h> |
27 | #include <linux/mii.h> | 27 | #include <linux/mii.h> |
28 | #include <linux/usb.h> | 28 | #include <linux/usb.h> |
29 | #include <linux/bitrev.h> | ||
30 | #include <linux/crc16.h> | ||
29 | #include <linux/crc32.h> | 31 | #include <linux/crc32.h> |
30 | #include <linux/usb/usbnet.h> | 32 | #include <linux/usb/usbnet.h> |
31 | #include <linux/slab.h> | 33 | #include <linux/slab.h> |
@@ -46,7 +48,12 @@ | |||
46 | #define SMSC95XX_INTERNAL_PHY_ID (1) | 48 | #define SMSC95XX_INTERNAL_PHY_ID (1) |
47 | #define SMSC95XX_TX_OVERHEAD (8) | 49 | #define SMSC95XX_TX_OVERHEAD (8) |
48 | #define SMSC95XX_TX_OVERHEAD_CSUM (12) | 50 | #define SMSC95XX_TX_OVERHEAD_CSUM (12) |
49 | #define SUPPORTED_WAKE (WAKE_MAGIC) | 51 | #define SUPPORTED_WAKE (WAKE_PHY | WAKE_UCAST | WAKE_BCAST | \ |
52 | WAKE_MCAST | WAKE_ARP | WAKE_MAGIC) | ||
53 | |||
54 | #define FEATURE_8_WAKEUP_FILTERS (0x01) | ||
55 | #define FEATURE_PHY_NLP_CROSSOVER (0x02) | ||
56 | #define FEATURE_AUTOSUSPEND (0x04) | ||
50 | 57 | ||
51 | #define check_warn(ret, fmt, args...) \ | 58 | #define check_warn(ret, fmt, args...) \ |
52 | ({ if (ret < 0) netdev_warn(dev->net, fmt, ##args); }) | 59 | ({ if (ret < 0) netdev_warn(dev->net, fmt, ##args); }) |
@@ -63,80 +70,98 @@ struct smsc95xx_priv { | |||
63 | u32 hash_lo; | 70 | u32 hash_lo; |
64 | u32 wolopts; | 71 | u32 wolopts; |
65 | spinlock_t mac_cr_lock; | 72 | spinlock_t mac_cr_lock; |
66 | }; | 73 | u8 features; |
67 | |||
68 | struct usb_context { | ||
69 | struct usb_ctrlrequest req; | ||
70 | struct usbnet *dev; | ||
71 | }; | 74 | }; |
72 | 75 | ||
73 | static bool turbo_mode = true; | 76 | static bool turbo_mode = true; |
74 | module_param(turbo_mode, bool, 0644); | 77 | module_param(turbo_mode, bool, 0644); |
75 | MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction"); | 78 | MODULE_PARM_DESC(turbo_mode, "Enable multiple frames per Rx transaction"); |
76 | 79 | ||
77 | static int __must_check smsc95xx_read_reg(struct usbnet *dev, u32 index, | 80 | static int __must_check __smsc95xx_read_reg(struct usbnet *dev, u32 index, |
78 | u32 *data) | 81 | u32 *data, int in_pm) |
79 | { | 82 | { |
80 | u32 *buf = kmalloc(4, GFP_KERNEL); | 83 | u32 buf; |
81 | int ret; | 84 | int ret; |
85 | int (*fn)(struct usbnet *, u8, u8, u16, u16, void *, u16); | ||
82 | 86 | ||
83 | BUG_ON(!dev); | 87 | BUG_ON(!dev); |
84 | 88 | ||
85 | if (!buf) | 89 | if (!in_pm) |
86 | return -ENOMEM; | 90 | fn = usbnet_read_cmd; |
87 | 91 | else | |
88 | ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), | 92 | fn = usbnet_read_cmd_nopm; |
89 | USB_VENDOR_REQUEST_READ_REGISTER, | ||
90 | USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
91 | 00, index, buf, 4, USB_CTRL_GET_TIMEOUT); | ||
92 | 93 | ||
94 | ret = fn(dev, USB_VENDOR_REQUEST_READ_REGISTER, USB_DIR_IN | ||
95 | | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
96 | 0, index, &buf, 4); | ||
93 | if (unlikely(ret < 0)) | 97 | if (unlikely(ret < 0)) |
94 | netdev_warn(dev->net, "Failed to read register index 0x%08x\n", index); | 98 | netdev_warn(dev->net, "Failed to read reg index 0x%08x: %d\n", |
99 | index, ret); | ||
95 | 100 | ||
96 | le32_to_cpus(buf); | 101 | le32_to_cpus(&buf); |
97 | *data = *buf; | 102 | *data = buf; |
98 | kfree(buf); | ||
99 | 103 | ||
100 | return ret; | 104 | return ret; |
101 | } | 105 | } |
102 | 106 | ||
103 | static int __must_check smsc95xx_write_reg(struct usbnet *dev, u32 index, | 107 | static int __must_check __smsc95xx_write_reg(struct usbnet *dev, u32 index, |
104 | u32 data) | 108 | u32 data, int in_pm) |
105 | { | 109 | { |
106 | u32 *buf = kmalloc(4, GFP_KERNEL); | 110 | u32 buf; |
107 | int ret; | 111 | int ret; |
112 | int (*fn)(struct usbnet *, u8, u8, u16, u16, const void *, u16); | ||
108 | 113 | ||
109 | BUG_ON(!dev); | 114 | BUG_ON(!dev); |
110 | 115 | ||
111 | if (!buf) | 116 | if (!in_pm) |
112 | return -ENOMEM; | 117 | fn = usbnet_write_cmd; |
113 | 118 | else | |
114 | *buf = data; | 119 | fn = usbnet_write_cmd_nopm; |
115 | cpu_to_le32s(buf); | ||
116 | 120 | ||
117 | ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), | 121 | buf = data; |
118 | USB_VENDOR_REQUEST_WRITE_REGISTER, | 122 | cpu_to_le32s(&buf); |
119 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
120 | 00, index, buf, 4, USB_CTRL_SET_TIMEOUT); | ||
121 | 123 | ||
124 | ret = fn(dev, USB_VENDOR_REQUEST_WRITE_REGISTER, USB_DIR_OUT | ||
125 | | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | ||
126 | 0, index, &buf, 4); | ||
122 | if (unlikely(ret < 0)) | 127 | if (unlikely(ret < 0)) |
123 | netdev_warn(dev->net, "Failed to write register index 0x%08x\n", index); | 128 | netdev_warn(dev->net, "Failed to write reg index 0x%08x: %d\n", |
124 | 129 | index, ret); | |
125 | kfree(buf); | ||
126 | 130 | ||
127 | return ret; | 131 | return ret; |
128 | } | 132 | } |
129 | 133 | ||
134 | static int __must_check smsc95xx_read_reg_nopm(struct usbnet *dev, u32 index, | ||
135 | u32 *data) | ||
136 | { | ||
137 | return __smsc95xx_read_reg(dev, index, data, 1); | ||
138 | } | ||
139 | |||
140 | static int __must_check smsc95xx_write_reg_nopm(struct usbnet *dev, u32 index, | ||
141 | u32 data) | ||
142 | { | ||
143 | return __smsc95xx_write_reg(dev, index, data, 1); | ||
144 | } | ||
145 | |||
146 | static int __must_check smsc95xx_read_reg(struct usbnet *dev, u32 index, | ||
147 | u32 *data) | ||
148 | { | ||
149 | return __smsc95xx_read_reg(dev, index, data, 0); | ||
150 | } | ||
151 | |||
152 | static int __must_check smsc95xx_write_reg(struct usbnet *dev, u32 index, | ||
153 | u32 data) | ||
154 | { | ||
155 | return __smsc95xx_write_reg(dev, index, data, 0); | ||
156 | } | ||
130 | static int smsc95xx_set_feature(struct usbnet *dev, u32 feature) | 157 | static int smsc95xx_set_feature(struct usbnet *dev, u32 feature) |
131 | { | 158 | { |
132 | if (WARN_ON_ONCE(!dev)) | 159 | if (WARN_ON_ONCE(!dev)) |
133 | return -EINVAL; | 160 | return -EINVAL; |
134 | 161 | ||
135 | cpu_to_le32s(&feature); | 162 | return usbnet_write_cmd_nopm(dev, USB_REQ_SET_FEATURE, |
136 | 163 | USB_RECIP_DEVICE, feature, 0, | |
137 | return usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), | 164 | NULL, 0); |
138 | USB_REQ_SET_FEATURE, USB_RECIP_DEVICE, feature, 0, NULL, 0, | ||
139 | USB_CTRL_SET_TIMEOUT); | ||
140 | } | 165 | } |
141 | 166 | ||
142 | static int smsc95xx_clear_feature(struct usbnet *dev, u32 feature) | 167 | static int smsc95xx_clear_feature(struct usbnet *dev, u32 feature) |
@@ -144,24 +169,23 @@ static int smsc95xx_clear_feature(struct usbnet *dev, u32 feature) | |||
144 | if (WARN_ON_ONCE(!dev)) | 169 | if (WARN_ON_ONCE(!dev)) |
145 | return -EINVAL; | 170 | return -EINVAL; |
146 | 171 | ||
147 | cpu_to_le32s(&feature); | 172 | return usbnet_write_cmd_nopm(dev, USB_REQ_CLEAR_FEATURE, |
148 | 173 | USB_RECIP_DEVICE, feature, | |
149 | return usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), | 174 | 0, NULL, 0); |
150 | USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE, feature, 0, NULL, 0, | ||
151 | USB_CTRL_SET_TIMEOUT); | ||
152 | } | 175 | } |
153 | 176 | ||
154 | /* Loop until the read is completed with timeout | 177 | /* Loop until the read is completed with timeout |
155 | * called with phy_mutex held */ | 178 | * called with phy_mutex held */ |
156 | static int __must_check smsc95xx_phy_wait_not_busy(struct usbnet *dev) | 179 | static int __must_check __smsc95xx_phy_wait_not_busy(struct usbnet *dev, |
180 | int in_pm) | ||
157 | { | 181 | { |
158 | unsigned long start_time = jiffies; | 182 | unsigned long start_time = jiffies; |
159 | u32 val; | 183 | u32 val; |
160 | int ret; | 184 | int ret; |
161 | 185 | ||
162 | do { | 186 | do { |
163 | ret = smsc95xx_read_reg(dev, MII_ADDR, &val); | 187 | ret = __smsc95xx_read_reg(dev, MII_ADDR, &val, in_pm); |
164 | check_warn_return(ret, "Error reading MII_ACCESS"); | 188 | check_warn_return(ret, "Error reading MII_ACCESS\n"); |
165 | if (!(val & MII_BUSY_)) | 189 | if (!(val & MII_BUSY_)) |
166 | return 0; | 190 | return 0; |
167 | } while (!time_after(jiffies, start_time + HZ)); | 191 | } while (!time_after(jiffies, start_time + HZ)); |
@@ -169,7 +193,8 @@ static int __must_check smsc95xx_phy_wait_not_busy(struct usbnet *dev) | |||
169 | return -EIO; | 193 | return -EIO; |
170 | } | 194 | } |
171 | 195 | ||
172 | static int smsc95xx_mdio_read(struct net_device *netdev, int phy_id, int idx) | 196 | static int __smsc95xx_mdio_read(struct net_device *netdev, int phy_id, int idx, |
197 | int in_pm) | ||
173 | { | 198 | { |
174 | struct usbnet *dev = netdev_priv(netdev); | 199 | struct usbnet *dev = netdev_priv(netdev); |
175 | u32 val, addr; | 200 | u32 val, addr; |
@@ -178,21 +203,21 @@ static int smsc95xx_mdio_read(struct net_device *netdev, int phy_id, int idx) | |||
178 | mutex_lock(&dev->phy_mutex); | 203 | mutex_lock(&dev->phy_mutex); |
179 | 204 | ||
180 | /* confirm MII not busy */ | 205 | /* confirm MII not busy */ |
181 | ret = smsc95xx_phy_wait_not_busy(dev); | 206 | ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); |
182 | check_warn_goto_done(ret, "MII is busy in smsc95xx_mdio_read"); | 207 | check_warn_goto_done(ret, "MII is busy in smsc95xx_mdio_read\n"); |
183 | 208 | ||
184 | /* set the address, index & direction (read from PHY) */ | 209 | /* set the address, index & direction (read from PHY) */ |
185 | phy_id &= dev->mii.phy_id_mask; | 210 | phy_id &= dev->mii.phy_id_mask; |
186 | idx &= dev->mii.reg_num_mask; | 211 | idx &= dev->mii.reg_num_mask; |
187 | addr = (phy_id << 11) | (idx << 6) | MII_READ_ | MII_BUSY_; | 212 | addr = (phy_id << 11) | (idx << 6) | MII_READ_ | MII_BUSY_; |
188 | ret = smsc95xx_write_reg(dev, MII_ADDR, addr); | 213 | ret = __smsc95xx_write_reg(dev, MII_ADDR, addr, in_pm); |
189 | check_warn_goto_done(ret, "Error writing MII_ADDR"); | 214 | check_warn_goto_done(ret, "Error writing MII_ADDR\n"); |
190 | 215 | ||
191 | ret = smsc95xx_phy_wait_not_busy(dev); | 216 | ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); |
192 | check_warn_goto_done(ret, "Timed out reading MII reg %02X", idx); | 217 | check_warn_goto_done(ret, "Timed out reading MII reg %02X\n", idx); |
193 | 218 | ||
194 | ret = smsc95xx_read_reg(dev, MII_DATA, &val); | 219 | ret = __smsc95xx_read_reg(dev, MII_DATA, &val, in_pm); |
195 | check_warn_goto_done(ret, "Error reading MII_DATA"); | 220 | check_warn_goto_done(ret, "Error reading MII_DATA\n"); |
196 | 221 | ||
197 | ret = (u16)(val & 0xFFFF); | 222 | ret = (u16)(val & 0xFFFF); |
198 | 223 | ||
@@ -201,8 +226,8 @@ done: | |||
201 | return ret; | 226 | return ret; |
202 | } | 227 | } |
203 | 228 | ||
204 | static void smsc95xx_mdio_write(struct net_device *netdev, int phy_id, int idx, | 229 | static void __smsc95xx_mdio_write(struct net_device *netdev, int phy_id, |
205 | int regval) | 230 | int idx, int regval, int in_pm) |
206 | { | 231 | { |
207 | struct usbnet *dev = netdev_priv(netdev); | 232 | struct usbnet *dev = netdev_priv(netdev); |
208 | u32 val, addr; | 233 | u32 val, addr; |
@@ -211,27 +236,50 @@ static void smsc95xx_mdio_write(struct net_device *netdev, int phy_id, int idx, | |||
211 | mutex_lock(&dev->phy_mutex); | 236 | mutex_lock(&dev->phy_mutex); |
212 | 237 | ||
213 | /* confirm MII not busy */ | 238 | /* confirm MII not busy */ |
214 | ret = smsc95xx_phy_wait_not_busy(dev); | 239 | ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); |
215 | check_warn_goto_done(ret, "MII is busy in smsc95xx_mdio_write"); | 240 | check_warn_goto_done(ret, "MII is busy in smsc95xx_mdio_write\n"); |
216 | 241 | ||
217 | val = regval; | 242 | val = regval; |
218 | ret = smsc95xx_write_reg(dev, MII_DATA, val); | 243 | ret = __smsc95xx_write_reg(dev, MII_DATA, val, in_pm); |
219 | check_warn_goto_done(ret, "Error writing MII_DATA"); | 244 | check_warn_goto_done(ret, "Error writing MII_DATA\n"); |
220 | 245 | ||
221 | /* set the address, index & direction (write to PHY) */ | 246 | /* set the address, index & direction (write to PHY) */ |
222 | phy_id &= dev->mii.phy_id_mask; | 247 | phy_id &= dev->mii.phy_id_mask; |
223 | idx &= dev->mii.reg_num_mask; | 248 | idx &= dev->mii.reg_num_mask; |
224 | addr = (phy_id << 11) | (idx << 6) | MII_WRITE_ | MII_BUSY_; | 249 | addr = (phy_id << 11) | (idx << 6) | MII_WRITE_ | MII_BUSY_; |
225 | ret = smsc95xx_write_reg(dev, MII_ADDR, addr); | 250 | ret = __smsc95xx_write_reg(dev, MII_ADDR, addr, in_pm); |
226 | check_warn_goto_done(ret, "Error writing MII_ADDR"); | 251 | check_warn_goto_done(ret, "Error writing MII_ADDR\n"); |
227 | 252 | ||
228 | ret = smsc95xx_phy_wait_not_busy(dev); | 253 | ret = __smsc95xx_phy_wait_not_busy(dev, in_pm); |
229 | check_warn_goto_done(ret, "Timed out writing MII reg %02X", idx); | 254 | check_warn_goto_done(ret, "Timed out writing MII reg %02X\n", idx); |
230 | 255 | ||
231 | done: | 256 | done: |
232 | mutex_unlock(&dev->phy_mutex); | 257 | mutex_unlock(&dev->phy_mutex); |
233 | } | 258 | } |
234 | 259 | ||
260 | static int smsc95xx_mdio_read_nopm(struct net_device *netdev, int phy_id, | ||
261 | int idx) | ||
262 | { | ||
263 | return __smsc95xx_mdio_read(netdev, phy_id, idx, 1); | ||
264 | } | ||
265 | |||
266 | static void smsc95xx_mdio_write_nopm(struct net_device *netdev, int phy_id, | ||
267 | int idx, int regval) | ||
268 | { | ||
269 | __smsc95xx_mdio_write(netdev, phy_id, idx, regval, 1); | ||
270 | } | ||
271 | |||
272 | static int smsc95xx_mdio_read(struct net_device *netdev, int phy_id, int idx) | ||
273 | { | ||
274 | return __smsc95xx_mdio_read(netdev, phy_id, idx, 0); | ||
275 | } | ||
276 | |||
277 | static void smsc95xx_mdio_write(struct net_device *netdev, int phy_id, int idx, | ||
278 | int regval) | ||
279 | { | ||
280 | __smsc95xx_mdio_write(netdev, phy_id, idx, regval, 0); | ||
281 | } | ||
282 | |||
235 | static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev) | 283 | static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev) |
236 | { | 284 | { |
237 | unsigned long start_time = jiffies; | 285 | unsigned long start_time = jiffies; |
@@ -240,7 +288,7 @@ static int __must_check smsc95xx_wait_eeprom(struct usbnet *dev) | |||
240 | 288 | ||
241 | do { | 289 | do { |
242 | ret = smsc95xx_read_reg(dev, E2P_CMD, &val); | 290 | ret = smsc95xx_read_reg(dev, E2P_CMD, &val); |
243 | check_warn_return(ret, "Error reading E2P_CMD"); | 291 | check_warn_return(ret, "Error reading E2P_CMD\n"); |
244 | if (!(val & E2P_CMD_BUSY_) || (val & E2P_CMD_TIMEOUT_)) | 292 | if (!(val & E2P_CMD_BUSY_) || (val & E2P_CMD_TIMEOUT_)) |
245 | break; | 293 | break; |
246 | udelay(40); | 294 | udelay(40); |
@@ -262,7 +310,7 @@ static int __must_check smsc95xx_eeprom_confirm_not_busy(struct usbnet *dev) | |||
262 | 310 | ||
263 | do { | 311 | do { |
264 | ret = smsc95xx_read_reg(dev, E2P_CMD, &val); | 312 | ret = smsc95xx_read_reg(dev, E2P_CMD, &val); |
265 | check_warn_return(ret, "Error reading E2P_CMD"); | 313 | check_warn_return(ret, "Error reading E2P_CMD\n"); |
266 | 314 | ||
267 | if (!(val & E2P_CMD_BUSY_)) | 315 | if (!(val & E2P_CMD_BUSY_)) |
268 | return 0; | 316 | return 0; |
@@ -290,14 +338,14 @@ static int smsc95xx_read_eeprom(struct usbnet *dev, u32 offset, u32 length, | |||
290 | for (i = 0; i < length; i++) { | 338 | for (i = 0; i < length; i++) { |
291 | val = E2P_CMD_BUSY_ | E2P_CMD_READ_ | (offset & E2P_CMD_ADDR_); | 339 | val = E2P_CMD_BUSY_ | E2P_CMD_READ_ | (offset & E2P_CMD_ADDR_); |
292 | ret = smsc95xx_write_reg(dev, E2P_CMD, val); | 340 | ret = smsc95xx_write_reg(dev, E2P_CMD, val); |
293 | check_warn_return(ret, "Error writing E2P_CMD"); | 341 | check_warn_return(ret, "Error writing E2P_CMD\n"); |
294 | 342 | ||
295 | ret = smsc95xx_wait_eeprom(dev); | 343 | ret = smsc95xx_wait_eeprom(dev); |
296 | if (ret < 0) | 344 | if (ret < 0) |
297 | return ret; | 345 | return ret; |
298 | 346 | ||
299 | ret = smsc95xx_read_reg(dev, E2P_DATA, &val); | 347 | ret = smsc95xx_read_reg(dev, E2P_DATA, &val); |
300 | check_warn_return(ret, "Error reading E2P_DATA"); | 348 | check_warn_return(ret, "Error reading E2P_DATA\n"); |
301 | 349 | ||
302 | data[i] = val & 0xFF; | 350 | data[i] = val & 0xFF; |
303 | offset++; | 351 | offset++; |
@@ -322,7 +370,7 @@ static int smsc95xx_write_eeprom(struct usbnet *dev, u32 offset, u32 length, | |||
322 | /* Issue write/erase enable command */ | 370 | /* Issue write/erase enable command */ |
323 | val = E2P_CMD_BUSY_ | E2P_CMD_EWEN_; | 371 | val = E2P_CMD_BUSY_ | E2P_CMD_EWEN_; |
324 | ret = smsc95xx_write_reg(dev, E2P_CMD, val); | 372 | ret = smsc95xx_write_reg(dev, E2P_CMD, val); |
325 | check_warn_return(ret, "Error writing E2P_DATA"); | 373 | check_warn_return(ret, "Error writing E2P_DATA\n"); |
326 | 374 | ||
327 | ret = smsc95xx_wait_eeprom(dev); | 375 | ret = smsc95xx_wait_eeprom(dev); |
328 | if (ret < 0) | 376 | if (ret < 0) |
@@ -333,12 +381,12 @@ static int smsc95xx_write_eeprom(struct usbnet *dev, u32 offset, u32 length, | |||
333 | /* Fill data register */ | 381 | /* Fill data register */ |
334 | val = data[i]; | 382 | val = data[i]; |
335 | ret = smsc95xx_write_reg(dev, E2P_DATA, val); | 383 | ret = smsc95xx_write_reg(dev, E2P_DATA, val); |
336 | check_warn_return(ret, "Error writing E2P_DATA"); | 384 | check_warn_return(ret, "Error writing E2P_DATA\n"); |
337 | 385 | ||
338 | /* Send "write" command */ | 386 | /* Send "write" command */ |
339 | val = E2P_CMD_BUSY_ | E2P_CMD_WRITE_ | (offset & E2P_CMD_ADDR_); | 387 | val = E2P_CMD_BUSY_ | E2P_CMD_WRITE_ | (offset & E2P_CMD_ADDR_); |
340 | ret = smsc95xx_write_reg(dev, E2P_CMD, val); | 388 | ret = smsc95xx_write_reg(dev, E2P_CMD, val); |
341 | check_warn_return(ret, "Error writing E2P_CMD"); | 389 | check_warn_return(ret, "Error writing E2P_CMD\n"); |
342 | 390 | ||
343 | ret = smsc95xx_wait_eeprom(dev); | 391 | ret = smsc95xx_wait_eeprom(dev); |
344 | if (ret < 0) | 392 | if (ret < 0) |
@@ -350,60 +398,20 @@ static int smsc95xx_write_eeprom(struct usbnet *dev, u32 offset, u32 length, | |||
350 | return 0; | 398 | return 0; |
351 | } | 399 | } |
352 | 400 | ||
353 | static void smsc95xx_async_cmd_callback(struct urb *urb) | ||
354 | { | ||
355 | struct usb_context *usb_context = urb->context; | ||
356 | struct usbnet *dev = usb_context->dev; | ||
357 | int status = urb->status; | ||
358 | |||
359 | check_warn(status, "async callback failed with %d\n", status); | ||
360 | |||
361 | kfree(usb_context); | ||
362 | usb_free_urb(urb); | ||
363 | } | ||
364 | |||
365 | static int __must_check smsc95xx_write_reg_async(struct usbnet *dev, u16 index, | 401 | static int __must_check smsc95xx_write_reg_async(struct usbnet *dev, u16 index, |
366 | u32 *data) | 402 | u32 *data) |
367 | { | 403 | { |
368 | struct usb_context *usb_context; | ||
369 | int status; | ||
370 | struct urb *urb; | ||
371 | const u16 size = 4; | 404 | const u16 size = 4; |
405 | int ret; | ||
372 | 406 | ||
373 | urb = usb_alloc_urb(0, GFP_ATOMIC); | 407 | ret = usbnet_write_cmd_async(dev, USB_VENDOR_REQUEST_WRITE_REGISTER, |
374 | if (!urb) { | 408 | USB_DIR_OUT | USB_TYPE_VENDOR | |
375 | netdev_warn(dev->net, "Error allocating URB\n"); | 409 | USB_RECIP_DEVICE, |
376 | return -ENOMEM; | 410 | 0, index, data, size); |
377 | } | 411 | if (ret < 0) |
378 | 412 | netdev_warn(dev->net, "Error write async cmd, sts=%d\n", | |
379 | usb_context = kmalloc(sizeof(struct usb_context), GFP_ATOMIC); | 413 | ret); |
380 | if (usb_context == NULL) { | 414 | return ret; |
381 | netdev_warn(dev->net, "Error allocating control msg\n"); | ||
382 | usb_free_urb(urb); | ||
383 | return -ENOMEM; | ||
384 | } | ||
385 | |||
386 | usb_context->req.bRequestType = | ||
387 | USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; | ||
388 | usb_context->req.bRequest = USB_VENDOR_REQUEST_WRITE_REGISTER; | ||
389 | usb_context->req.wValue = 00; | ||
390 | usb_context->req.wIndex = cpu_to_le16(index); | ||
391 | usb_context->req.wLength = cpu_to_le16(size); | ||
392 | |||
393 | usb_fill_control_urb(urb, dev->udev, usb_sndctrlpipe(dev->udev, 0), | ||
394 | (void *)&usb_context->req, data, size, | ||
395 | smsc95xx_async_cmd_callback, | ||
396 | (void *)usb_context); | ||
397 | |||
398 | status = usb_submit_urb(urb, GFP_ATOMIC); | ||
399 | if (status < 0) { | ||
400 | netdev_warn(dev->net, "Error submitting control msg, sts=%d\n", | ||
401 | status); | ||
402 | kfree(usb_context); | ||
403 | usb_free_urb(urb); | ||
404 | } | ||
405 | |||
406 | return status; | ||
407 | } | 415 | } |
408 | 416 | ||
409 | /* returns hash bit number for given MAC address | 417 | /* returns hash bit number for given MAC address |
@@ -461,13 +469,13 @@ static void smsc95xx_set_multicast(struct net_device *netdev) | |||
461 | 469 | ||
462 | /* Initiate async writes, as we can't wait for completion here */ | 470 | /* Initiate async writes, as we can't wait for completion here */ |
463 | ret = smsc95xx_write_reg_async(dev, HASHH, &pdata->hash_hi); | 471 | ret = smsc95xx_write_reg_async(dev, HASHH, &pdata->hash_hi); |
464 | check_warn(ret, "failed to initiate async write to HASHH"); | 472 | check_warn(ret, "failed to initiate async write to HASHH\n"); |
465 | 473 | ||
466 | ret = smsc95xx_write_reg_async(dev, HASHL, &pdata->hash_lo); | 474 | ret = smsc95xx_write_reg_async(dev, HASHL, &pdata->hash_lo); |
467 | check_warn(ret, "failed to initiate async write to HASHL"); | 475 | check_warn(ret, "failed to initiate async write to HASHL\n"); |
468 | 476 | ||
469 | ret = smsc95xx_write_reg_async(dev, MAC_CR, &pdata->mac_cr); | 477 | ret = smsc95xx_write_reg_async(dev, MAC_CR, &pdata->mac_cr); |
470 | check_warn(ret, "failed to initiate async write to MAC_CR"); | 478 | check_warn(ret, "failed to initiate async write to MAC_CR\n"); |
471 | } | 479 | } |
472 | 480 | ||
473 | static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex, | 481 | static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex, |
@@ -476,7 +484,7 @@ static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex, | |||
476 | u32 flow, afc_cfg = 0; | 484 | u32 flow, afc_cfg = 0; |
477 | 485 | ||
478 | int ret = smsc95xx_read_reg(dev, AFC_CFG, &afc_cfg); | 486 | int ret = smsc95xx_read_reg(dev, AFC_CFG, &afc_cfg); |
479 | check_warn_return(ret, "Error reading AFC_CFG"); | 487 | check_warn_return(ret, "Error reading AFC_CFG\n"); |
480 | 488 | ||
481 | if (duplex == DUPLEX_FULL) { | 489 | if (duplex == DUPLEX_FULL) { |
482 | u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv); | 490 | u8 cap = mii_resolve_flowctrl_fdx(lcladv, rmtadv); |
@@ -501,10 +509,10 @@ static int smsc95xx_phy_update_flowcontrol(struct usbnet *dev, u8 duplex, | |||
501 | } | 509 | } |
502 | 510 | ||
503 | ret = smsc95xx_write_reg(dev, FLOW, flow); | 511 | ret = smsc95xx_write_reg(dev, FLOW, flow); |
504 | check_warn_return(ret, "Error writing FLOW"); | 512 | check_warn_return(ret, "Error writing FLOW\n"); |
505 | 513 | ||
506 | ret = smsc95xx_write_reg(dev, AFC_CFG, afc_cfg); | 514 | ret = smsc95xx_write_reg(dev, AFC_CFG, afc_cfg); |
507 | check_warn_return(ret, "Error writing AFC_CFG"); | 515 | check_warn_return(ret, "Error writing AFC_CFG\n"); |
508 | 516 | ||
509 | return 0; | 517 | return 0; |
510 | } | 518 | } |
@@ -520,10 +528,10 @@ static int smsc95xx_link_reset(struct usbnet *dev) | |||
520 | 528 | ||
521 | /* clear interrupt status */ | 529 | /* clear interrupt status */ |
522 | ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC); | 530 | ret = smsc95xx_mdio_read(dev->net, mii->phy_id, PHY_INT_SRC); |
523 | check_warn_return(ret, "Error reading PHY_INT_SRC"); | 531 | check_warn_return(ret, "Error reading PHY_INT_SRC\n"); |
524 | 532 | ||
525 | ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_); | 533 | ret = smsc95xx_write_reg(dev, INT_STS, INT_STS_CLEAR_ALL_); |
526 | check_warn_return(ret, "Error writing INT_STS"); | 534 | check_warn_return(ret, "Error writing INT_STS\n"); |
527 | 535 | ||
528 | mii_check_media(mii, 1, 1); | 536 | mii_check_media(mii, 1, 1); |
529 | mii_ethtool_gset(&dev->mii, &ecmd); | 537 | mii_ethtool_gset(&dev->mii, &ecmd); |
@@ -545,10 +553,10 @@ static int smsc95xx_link_reset(struct usbnet *dev) | |||
545 | spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); | 553 | spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); |
546 | 554 | ||
547 | ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); | 555 | ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); |
548 | check_warn_return(ret, "Error writing MAC_CR"); | 556 | check_warn_return(ret, "Error writing MAC_CR\n"); |
549 | 557 | ||
550 | ret = smsc95xx_phy_update_flowcontrol(dev, ecmd.duplex, lcladv, rmtadv); | 558 | ret = smsc95xx_phy_update_flowcontrol(dev, ecmd.duplex, lcladv, rmtadv); |
551 | check_warn_return(ret, "Error updating PHY flow control"); | 559 | check_warn_return(ret, "Error updating PHY flow control\n"); |
552 | 560 | ||
553 | return 0; | 561 | return 0; |
554 | } | 562 | } |
@@ -765,7 +773,7 @@ static int smsc95xx_start_tx_path(struct usbnet *dev) | |||
765 | } | 773 | } |
766 | 774 | ||
767 | /* Starts the Receive path */ | 775 | /* Starts the Receive path */ |
768 | static int smsc95xx_start_rx_path(struct usbnet *dev) | 776 | static int smsc95xx_start_rx_path(struct usbnet *dev, int in_pm) |
769 | { | 777 | { |
770 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | 778 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); |
771 | unsigned long flags; | 779 | unsigned long flags; |
@@ -775,7 +783,7 @@ static int smsc95xx_start_rx_path(struct usbnet *dev) | |||
775 | pdata->mac_cr |= MAC_CR_RXEN_; | 783 | pdata->mac_cr |= MAC_CR_RXEN_; |
776 | spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); | 784 | spin_unlock_irqrestore(&pdata->mac_cr_lock, flags); |
777 | 785 | ||
778 | ret = smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr); | 786 | ret = __smsc95xx_write_reg(dev, MAC_CR, pdata->mac_cr, in_pm); |
779 | check_warn_return(ret, "Failed to write MAC_CR: %d\n", ret); | 787 | check_warn_return(ret, "Failed to write MAC_CR: %d\n", ret); |
780 | 788 | ||
781 | return 0; | 789 | return 0; |
@@ -813,7 +821,7 @@ static int smsc95xx_phy_initialize(struct usbnet *dev) | |||
813 | 821 | ||
814 | /* read to clear */ | 822 | /* read to clear */ |
815 | ret = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, PHY_INT_SRC); | 823 | ret = smsc95xx_mdio_read(dev->net, dev->mii.phy_id, PHY_INT_SRC); |
816 | check_warn_return(ret, "Failed to read PHY_INT_SRC during init"); | 824 | check_warn_return(ret, "Failed to read PHY_INT_SRC during init\n"); |
817 | 825 | ||
818 | smsc95xx_mdio_write(dev->net, dev->mii.phy_id, PHY_INT_MASK, | 826 | smsc95xx_mdio_write(dev->net, dev->mii.phy_id, PHY_INT_MASK, |
819 | PHY_INT_MASK_DEFAULT_); | 827 | PHY_INT_MASK_DEFAULT_); |
@@ -867,14 +875,14 @@ static int smsc95xx_reset(struct usbnet *dev) | |||
867 | if (ret < 0) | 875 | if (ret < 0) |
868 | return ret; | 876 | return ret; |
869 | 877 | ||
870 | netif_dbg(dev, ifup, dev->net, | 878 | netif_dbg(dev, ifup, dev->net, "MAC Address: %pM\n", |
871 | "MAC Address: %pM\n", dev->net->dev_addr); | 879 | dev->net->dev_addr); |
872 | 880 | ||
873 | ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); | 881 | ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); |
874 | check_warn_return(ret, "Failed to read HW_CFG: %d\n", ret); | 882 | check_warn_return(ret, "Failed to read HW_CFG: %d\n", ret); |
875 | 883 | ||
876 | netif_dbg(dev, ifup, dev->net, | 884 | netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG : 0x%08x\n", |
877 | "Read Value from HW_CFG : 0x%08x\n", read_buf); | 885 | read_buf); |
878 | 886 | ||
879 | read_buf |= HW_CFG_BIR_; | 887 | read_buf |= HW_CFG_BIR_; |
880 | 888 | ||
@@ -898,8 +906,8 @@ static int smsc95xx_reset(struct usbnet *dev) | |||
898 | dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE; | 906 | dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE; |
899 | } | 907 | } |
900 | 908 | ||
901 | netif_dbg(dev, ifup, dev->net, | 909 | netif_dbg(dev, ifup, dev->net, "rx_urb_size=%ld\n", |
902 | "rx_urb_size=%ld\n", (ulong)dev->rx_urb_size); | 910 | (ulong)dev->rx_urb_size); |
903 | 911 | ||
904 | ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap); | 912 | ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap); |
905 | check_warn_return(ret, "Failed to write BURST_CAP: %d\n", ret); | 913 | check_warn_return(ret, "Failed to write BURST_CAP: %d\n", ret); |
@@ -924,8 +932,8 @@ static int smsc95xx_reset(struct usbnet *dev) | |||
924 | ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); | 932 | ret = smsc95xx_read_reg(dev, HW_CFG, &read_buf); |
925 | check_warn_return(ret, "Failed to read HW_CFG: %d\n", ret); | 933 | check_warn_return(ret, "Failed to read HW_CFG: %d\n", ret); |
926 | 934 | ||
927 | netif_dbg(dev, ifup, dev->net, | 935 | netif_dbg(dev, ifup, dev->net, "Read Value from HW_CFG: 0x%08x\n", |
928 | "Read Value from HW_CFG: 0x%08x\n", read_buf); | 936 | read_buf); |
929 | 937 | ||
930 | if (turbo_mode) | 938 | if (turbo_mode) |
931 | read_buf |= (HW_CFG_MEF_ | HW_CFG_BCE_); | 939 | read_buf |= (HW_CFG_MEF_ | HW_CFG_BCE_); |
@@ -975,12 +983,12 @@ static int smsc95xx_reset(struct usbnet *dev) | |||
975 | 983 | ||
976 | /* Enable or disable checksum offload engines */ | 984 | /* Enable or disable checksum offload engines */ |
977 | ret = smsc95xx_set_features(dev->net, dev->net->features); | 985 | ret = smsc95xx_set_features(dev->net, dev->net->features); |
978 | check_warn_return(ret, "Failed to set checksum offload features"); | 986 | check_warn_return(ret, "Failed to set checksum offload features\n"); |
979 | 987 | ||
980 | smsc95xx_set_multicast(dev->net); | 988 | smsc95xx_set_multicast(dev->net); |
981 | 989 | ||
982 | ret = smsc95xx_phy_initialize(dev); | 990 | ret = smsc95xx_phy_initialize(dev); |
983 | check_warn_return(ret, "Failed to init PHY"); | 991 | check_warn_return(ret, "Failed to init PHY\n"); |
984 | 992 | ||
985 | ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf); | 993 | ret = smsc95xx_read_reg(dev, INT_EP_CTL, &read_buf); |
986 | check_warn_return(ret, "Failed to read INT_EP_CTL: %d\n", ret); | 994 | check_warn_return(ret, "Failed to read INT_EP_CTL: %d\n", ret); |
@@ -992,10 +1000,10 @@ static int smsc95xx_reset(struct usbnet *dev) | |||
992 | check_warn_return(ret, "Failed to write INT_EP_CTL: %d\n", ret); | 1000 | check_warn_return(ret, "Failed to write INT_EP_CTL: %d\n", ret); |
993 | 1001 | ||
994 | ret = smsc95xx_start_tx_path(dev); | 1002 | ret = smsc95xx_start_tx_path(dev); |
995 | check_warn_return(ret, "Failed to start TX path"); | 1003 | check_warn_return(ret, "Failed to start TX path\n"); |
996 | 1004 | ||
997 | ret = smsc95xx_start_rx_path(dev); | 1005 | ret = smsc95xx_start_rx_path(dev, 0); |
998 | check_warn_return(ret, "Failed to start RX path"); | 1006 | check_warn_return(ret, "Failed to start RX path\n"); |
999 | 1007 | ||
1000 | netif_dbg(dev, ifup, dev->net, "smsc95xx_reset, return 0\n"); | 1008 | netif_dbg(dev, ifup, dev->net, "smsc95xx_reset, return 0\n"); |
1001 | return 0; | 1009 | return 0; |
@@ -1017,6 +1025,7 @@ static const struct net_device_ops smsc95xx_netdev_ops = { | |||
1017 | static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) | 1025 | static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) |
1018 | { | 1026 | { |
1019 | struct smsc95xx_priv *pdata = NULL; | 1027 | struct smsc95xx_priv *pdata = NULL; |
1028 | u32 val; | ||
1020 | int ret; | 1029 | int ret; |
1021 | 1030 | ||
1022 | printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n"); | 1031 | printk(KERN_INFO SMSC_CHIPNAME " v" SMSC_DRIVER_VERSION "\n"); |
@@ -1047,6 +1056,19 @@ static int smsc95xx_bind(struct usbnet *dev, struct usb_interface *intf) | |||
1047 | /* Init all registers */ | 1056 | /* Init all registers */ |
1048 | ret = smsc95xx_reset(dev); | 1057 | ret = smsc95xx_reset(dev); |
1049 | 1058 | ||
1059 | /* detect device revision as different features may be available */ | ||
1060 | ret = smsc95xx_read_reg(dev, ID_REV, &val); | ||
1061 | check_warn_return(ret, "Failed to read ID_REV: %d\n", ret); | ||
1062 | val >>= 16; | ||
1063 | |||
1064 | if ((val == ID_REV_CHIP_ID_9500A_) || (val == ID_REV_CHIP_ID_9530_) || | ||
1065 | (val == ID_REV_CHIP_ID_89530_) || (val == ID_REV_CHIP_ID_9730_)) | ||
1066 | pdata->features = (FEATURE_8_WAKEUP_FILTERS | | ||
1067 | FEATURE_PHY_NLP_CROSSOVER | | ||
1068 | FEATURE_AUTOSUSPEND); | ||
1069 | else if (val == ID_REV_CHIP_ID_9512_) | ||
1070 | pdata->features = FEATURE_8_WAKEUP_FILTERS; | ||
1071 | |||
1050 | dev->net->netdev_ops = &smsc95xx_netdev_ops; | 1072 | dev->net->netdev_ops = &smsc95xx_netdev_ops; |
1051 | dev->net->ethtool_ops = &smsc95xx_ethtool_ops; | 1073 | dev->net->ethtool_ops = &smsc95xx_ethtool_ops; |
1052 | dev->net->flags |= IFF_MULTICAST; | 1074 | dev->net->flags |= IFF_MULTICAST; |
@@ -1066,113 +1088,349 @@ static void smsc95xx_unbind(struct usbnet *dev, struct usb_interface *intf) | |||
1066 | } | 1088 | } |
1067 | } | 1089 | } |
1068 | 1090 | ||
1091 | static u16 smsc_crc(const u8 *buffer, size_t len, int filter) | ||
1092 | { | ||
1093 | return bitrev16(crc16(0xFFFF, buffer, len)) << ((filter % 2) * 16); | ||
1094 | } | ||
1095 | |||
1096 | static int smsc95xx_enable_phy_wakeup_interrupts(struct usbnet *dev, u16 mask) | ||
1097 | { | ||
1098 | struct mii_if_info *mii = &dev->mii; | ||
1099 | int ret; | ||
1100 | |||
1101 | netdev_dbg(dev->net, "enabling PHY wakeup interrupts\n"); | ||
1102 | |||
1103 | /* read to clear */ | ||
1104 | ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_SRC); | ||
1105 | check_warn_return(ret, "Error reading PHY_INT_SRC\n"); | ||
1106 | |||
1107 | /* enable interrupt source */ | ||
1108 | ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_INT_MASK); | ||
1109 | check_warn_return(ret, "Error reading PHY_INT_MASK\n"); | ||
1110 | |||
1111 | ret |= mask; | ||
1112 | |||
1113 | smsc95xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_INT_MASK, ret); | ||
1114 | |||
1115 | return 0; | ||
1116 | } | ||
1117 | |||
1118 | static int smsc95xx_link_ok_nopm(struct usbnet *dev) | ||
1119 | { | ||
1120 | struct mii_if_info *mii = &dev->mii; | ||
1121 | int ret; | ||
1122 | |||
1123 | /* first, a dummy read, needed to latch some MII phys */ | ||
1124 | ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); | ||
1125 | check_warn_return(ret, "Error reading MII_BMSR\n"); | ||
1126 | |||
1127 | ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, MII_BMSR); | ||
1128 | check_warn_return(ret, "Error reading MII_BMSR\n"); | ||
1129 | |||
1130 | return !!(ret & BMSR_LSTATUS); | ||
1131 | } | ||
1132 | |||
1133 | static int smsc95xx_enter_suspend0(struct usbnet *dev) | ||
1134 | { | ||
1135 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | ||
1136 | u32 val; | ||
1137 | int ret; | ||
1138 | |||
1139 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); | ||
1140 | check_warn_return(ret, "Error reading PM_CTRL\n"); | ||
1141 | |||
1142 | val &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); | ||
1143 | val |= PM_CTL_SUS_MODE_0; | ||
1144 | |||
1145 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1146 | check_warn_return(ret, "Error writing PM_CTRL\n"); | ||
1147 | |||
1148 | /* clear wol status */ | ||
1149 | val &= ~PM_CTL_WUPS_; | ||
1150 | val |= PM_CTL_WUPS_WOL_; | ||
1151 | |||
1152 | /* enable energy detection */ | ||
1153 | if (pdata->wolopts & WAKE_PHY) | ||
1154 | val |= PM_CTL_WUPS_ED_; | ||
1155 | |||
1156 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1157 | check_warn_return(ret, "Error writing PM_CTRL\n"); | ||
1158 | |||
1159 | /* read back PM_CTRL */ | ||
1160 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); | ||
1161 | check_warn_return(ret, "Error reading PM_CTRL\n"); | ||
1162 | |||
1163 | smsc95xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP); | ||
1164 | |||
1165 | return 0; | ||
1166 | } | ||
1167 | |||
1168 | static int smsc95xx_enter_suspend1(struct usbnet *dev) | ||
1169 | { | ||
1170 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | ||
1171 | struct mii_if_info *mii = &dev->mii; | ||
1172 | u32 val; | ||
1173 | int ret; | ||
1174 | |||
1175 | /* reconfigure link pulse detection timing for | ||
1176 | * compatibility with non-standard link partners | ||
1177 | */ | ||
1178 | if (pdata->features & FEATURE_PHY_NLP_CROSSOVER) | ||
1179 | smsc95xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_EDPD_CONFIG, | ||
1180 | PHY_EDPD_CONFIG_DEFAULT); | ||
1181 | |||
1182 | /* enable energy detect power-down mode */ | ||
1183 | ret = smsc95xx_mdio_read_nopm(dev->net, mii->phy_id, PHY_MODE_CTRL_STS); | ||
1184 | check_warn_return(ret, "Error reading PHY_MODE_CTRL_STS\n"); | ||
1185 | |||
1186 | ret |= MODE_CTRL_STS_EDPWRDOWN_; | ||
1187 | |||
1188 | smsc95xx_mdio_write_nopm(dev->net, mii->phy_id, PHY_MODE_CTRL_STS, ret); | ||
1189 | |||
1190 | /* enter SUSPEND1 mode */ | ||
1191 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); | ||
1192 | check_warn_return(ret, "Error reading PM_CTRL\n"); | ||
1193 | |||
1194 | val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); | ||
1195 | val |= PM_CTL_SUS_MODE_1; | ||
1196 | |||
1197 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1198 | check_warn_return(ret, "Error writing PM_CTRL\n"); | ||
1199 | |||
1200 | /* clear wol status, enable energy detection */ | ||
1201 | val &= ~PM_CTL_WUPS_; | ||
1202 | val |= (PM_CTL_WUPS_ED_ | PM_CTL_ED_EN_); | ||
1203 | |||
1204 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1205 | check_warn_return(ret, "Error writing PM_CTRL\n"); | ||
1206 | |||
1207 | smsc95xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP); | ||
1208 | |||
1209 | return 0; | ||
1210 | } | ||
1211 | |||
1212 | static int smsc95xx_enter_suspend2(struct usbnet *dev) | ||
1213 | { | ||
1214 | u32 val; | ||
1215 | int ret; | ||
1216 | |||
1217 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); | ||
1218 | check_warn_return(ret, "Error reading PM_CTRL\n"); | ||
1219 | |||
1220 | val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); | ||
1221 | val |= PM_CTL_SUS_MODE_2; | ||
1222 | |||
1223 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); | ||
1224 | check_warn_return(ret, "Error writing PM_CTRL\n"); | ||
1225 | |||
1226 | return 0; | ||
1227 | } | ||
1228 | |||
1069 | static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) | 1229 | static int smsc95xx_suspend(struct usb_interface *intf, pm_message_t message) |
1070 | { | 1230 | { |
1071 | struct usbnet *dev = usb_get_intfdata(intf); | 1231 | struct usbnet *dev = usb_get_intfdata(intf); |
1072 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); | 1232 | struct smsc95xx_priv *pdata = (struct smsc95xx_priv *)(dev->data[0]); |
1233 | u32 val, link_up; | ||
1073 | int ret; | 1234 | int ret; |
1074 | u32 val; | ||
1075 | 1235 | ||
1076 | ret = usbnet_suspend(intf, message); | 1236 | ret = usbnet_suspend(intf, message); |
1077 | check_warn_return(ret, "usbnet_suspend error"); | 1237 | check_warn_return(ret, "usbnet_suspend error\n"); |
1238 | |||
1239 | /* determine if link is up using only _nopm functions */ | ||
1240 | link_up = smsc95xx_link_ok_nopm(dev); | ||
1078 | 1241 | ||
1079 | /* if no wol options set, enter lowest power SUSPEND2 mode */ | 1242 | /* if no wol options set, or if link is down and we're not waking on |
1080 | if (!(pdata->wolopts & SUPPORTED_WAKE)) { | 1243 | * PHY activity, enter lowest power SUSPEND2 mode |
1081 | netdev_info(dev->net, "entering SUSPEND2 mode"); | 1244 | */ |
1245 | if (!(pdata->wolopts & SUPPORTED_WAKE) || | ||
1246 | !(link_up || (pdata->wolopts & WAKE_PHY))) { | ||
1247 | netdev_info(dev->net, "entering SUSPEND2 mode\n"); | ||
1082 | 1248 | ||
1083 | /* disable energy detect (link up) & wake up events */ | 1249 | /* disable energy detect (link up) & wake up events */ |
1084 | ret = smsc95xx_read_reg(dev, WUCSR, &val); | 1250 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); |
1085 | check_warn_return(ret, "Error reading WUCSR"); | 1251 | check_warn_return(ret, "Error reading WUCSR\n"); |
1086 | 1252 | ||
1087 | val &= ~(WUCSR_MPEN_ | WUCSR_WAKE_EN_); | 1253 | val &= ~(WUCSR_MPEN_ | WUCSR_WAKE_EN_); |
1088 | 1254 | ||
1089 | ret = smsc95xx_write_reg(dev, WUCSR, val); | 1255 | ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); |
1090 | check_warn_return(ret, "Error writing WUCSR"); | 1256 | check_warn_return(ret, "Error writing WUCSR\n"); |
1091 | 1257 | ||
1092 | ret = smsc95xx_read_reg(dev, PM_CTRL, &val); | 1258 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); |
1093 | check_warn_return(ret, "Error reading PM_CTRL"); | 1259 | check_warn_return(ret, "Error reading PM_CTRL\n"); |
1094 | 1260 | ||
1095 | val &= ~(PM_CTL_ED_EN_ | PM_CTL_WOL_EN_); | 1261 | val &= ~(PM_CTL_ED_EN_ | PM_CTL_WOL_EN_); |
1096 | 1262 | ||
1097 | ret = smsc95xx_write_reg(dev, PM_CTRL, val); | 1263 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); |
1098 | check_warn_return(ret, "Error writing PM_CTRL"); | 1264 | check_warn_return(ret, "Error writing PM_CTRL\n"); |
1099 | 1265 | ||
1100 | /* enter suspend2 mode */ | 1266 | return smsc95xx_enter_suspend2(dev); |
1101 | ret = smsc95xx_read_reg(dev, PM_CTRL, &val); | 1267 | } |
1102 | check_warn_return(ret, "Error reading PM_CTRL"); | ||
1103 | 1268 | ||
1104 | val &= ~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_); | 1269 | if (pdata->wolopts & WAKE_PHY) { |
1105 | val |= PM_CTL_SUS_MODE_2; | 1270 | ret = smsc95xx_enable_phy_wakeup_interrupts(dev, |
1271 | (PHY_INT_MASK_ANEG_COMP_ | PHY_INT_MASK_LINK_DOWN_)); | ||
1272 | check_warn_return(ret, "error enabling PHY wakeup ints\n"); | ||
1273 | |||
1274 | /* if link is down then configure EDPD and enter SUSPEND1, | ||
1275 | * otherwise enter SUSPEND0 below | ||
1276 | */ | ||
1277 | if (!link_up) { | ||
1278 | netdev_info(dev->net, "entering SUSPEND1 mode\n"); | ||
1279 | return smsc95xx_enter_suspend1(dev); | ||
1280 | } | ||
1281 | } | ||
1106 | 1282 | ||
1107 | ret = smsc95xx_write_reg(dev, PM_CTRL, val); | 1283 | if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) { |
1108 | check_warn_return(ret, "Error writing PM_CTRL"); | 1284 | u32 *filter_mask = kzalloc(32, GFP_KERNEL); |
1285 | u32 command[2]; | ||
1286 | u32 offset[2]; | ||
1287 | u32 crc[4]; | ||
1288 | int wuff_filter_count = | ||
1289 | (pdata->features & FEATURE_8_WAKEUP_FILTERS) ? | ||
1290 | LAN9500A_WUFF_NUM : LAN9500_WUFF_NUM; | ||
1291 | int i, filter = 0; | ||
1292 | |||
1293 | memset(command, 0, sizeof(command)); | ||
1294 | memset(offset, 0, sizeof(offset)); | ||
1295 | memset(crc, 0, sizeof(crc)); | ||
1296 | |||
1297 | if (pdata->wolopts & WAKE_BCAST) { | ||
1298 | const u8 bcast[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; | ||
1299 | netdev_info(dev->net, "enabling broadcast detection\n"); | ||
1300 | filter_mask[filter * 4] = 0x003F; | ||
1301 | filter_mask[filter * 4 + 1] = 0x00; | ||
1302 | filter_mask[filter * 4 + 2] = 0x00; | ||
1303 | filter_mask[filter * 4 + 3] = 0x00; | ||
1304 | command[filter/4] |= 0x05UL << ((filter % 4) * 8); | ||
1305 | offset[filter/4] |= 0x00 << ((filter % 4) * 8); | ||
1306 | crc[filter/2] |= smsc_crc(bcast, 6, filter); | ||
1307 | filter++; | ||
1308 | } | ||
1109 | 1309 | ||
1110 | return 0; | 1310 | if (pdata->wolopts & WAKE_MCAST) { |
1311 | const u8 mcast[] = {0x01, 0x00, 0x5E}; | ||
1312 | netdev_info(dev->net, "enabling multicast detection\n"); | ||
1313 | filter_mask[filter * 4] = 0x0007; | ||
1314 | filter_mask[filter * 4 + 1] = 0x00; | ||
1315 | filter_mask[filter * 4 + 2] = 0x00; | ||
1316 | filter_mask[filter * 4 + 3] = 0x00; | ||
1317 | command[filter/4] |= 0x09UL << ((filter % 4) * 8); | ||
1318 | offset[filter/4] |= 0x00 << ((filter % 4) * 8); | ||
1319 | crc[filter/2] |= smsc_crc(mcast, 3, filter); | ||
1320 | filter++; | ||
1321 | } | ||
1322 | |||
1323 | if (pdata->wolopts & WAKE_ARP) { | ||
1324 | const u8 arp[] = {0x08, 0x06}; | ||
1325 | netdev_info(dev->net, "enabling ARP detection\n"); | ||
1326 | filter_mask[filter * 4] = 0x0003; | ||
1327 | filter_mask[filter * 4 + 1] = 0x00; | ||
1328 | filter_mask[filter * 4 + 2] = 0x00; | ||
1329 | filter_mask[filter * 4 + 3] = 0x00; | ||
1330 | command[filter/4] |= 0x05UL << ((filter % 4) * 8); | ||
1331 | offset[filter/4] |= 0x0C << ((filter % 4) * 8); | ||
1332 | crc[filter/2] |= smsc_crc(arp, 2, filter); | ||
1333 | filter++; | ||
1334 | } | ||
1335 | |||
1336 | if (pdata->wolopts & WAKE_UCAST) { | ||
1337 | netdev_info(dev->net, "enabling unicast detection\n"); | ||
1338 | filter_mask[filter * 4] = 0x003F; | ||
1339 | filter_mask[filter * 4 + 1] = 0x00; | ||
1340 | filter_mask[filter * 4 + 2] = 0x00; | ||
1341 | filter_mask[filter * 4 + 3] = 0x00; | ||
1342 | command[filter/4] |= 0x01UL << ((filter % 4) * 8); | ||
1343 | offset[filter/4] |= 0x00 << ((filter % 4) * 8); | ||
1344 | crc[filter/2] |= smsc_crc(dev->net->dev_addr, ETH_ALEN, filter); | ||
1345 | filter++; | ||
1346 | } | ||
1347 | |||
1348 | for (i = 0; i < (wuff_filter_count * 4); i++) { | ||
1349 | ret = smsc95xx_write_reg_nopm(dev, WUFF, filter_mask[i]); | ||
1350 | if (ret < 0) | ||
1351 | kfree(filter_mask); | ||
1352 | check_warn_return(ret, "Error writing WUFF\n"); | ||
1353 | } | ||
1354 | kfree(filter_mask); | ||
1355 | |||
1356 | for (i = 0; i < (wuff_filter_count / 4); i++) { | ||
1357 | ret = smsc95xx_write_reg_nopm(dev, WUFF, command[i]); | ||
1358 | check_warn_return(ret, "Error writing WUFF\n"); | ||
1359 | } | ||
1360 | |||
1361 | for (i = 0; i < (wuff_filter_count / 4); i++) { | ||
1362 | ret = smsc95xx_write_reg_nopm(dev, WUFF, offset[i]); | ||
1363 | check_warn_return(ret, "Error writing WUFF\n"); | ||
1364 | } | ||
1365 | |||
1366 | for (i = 0; i < (wuff_filter_count / 2); i++) { | ||
1367 | ret = smsc95xx_write_reg_nopm(dev, WUFF, crc[i]); | ||
1368 | check_warn_return(ret, "Error writing WUFF\n"); | ||
1369 | } | ||
1370 | |||
1371 | /* clear any pending pattern match packet status */ | ||
1372 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); | ||
1373 | check_warn_return(ret, "Error reading WUCSR\n"); | ||
1374 | |||
1375 | val |= WUCSR_WUFR_; | ||
1376 | |||
1377 | ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); | ||
1378 | check_warn_return(ret, "Error writing WUCSR\n"); | ||
1111 | } | 1379 | } |
1112 | 1380 | ||
1113 | if (pdata->wolopts & WAKE_MAGIC) { | 1381 | if (pdata->wolopts & WAKE_MAGIC) { |
1114 | /* clear any pending magic packet status */ | 1382 | /* clear any pending magic packet status */ |
1115 | ret = smsc95xx_read_reg(dev, WUCSR, &val); | 1383 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); |
1116 | check_warn_return(ret, "Error reading WUCSR"); | 1384 | check_warn_return(ret, "Error reading WUCSR\n"); |
1117 | 1385 | ||
1118 | val |= WUCSR_MPR_; | 1386 | val |= WUCSR_MPR_; |
1119 | 1387 | ||
1120 | ret = smsc95xx_write_reg(dev, WUCSR, val); | 1388 | ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); |
1121 | check_warn_return(ret, "Error writing WUCSR"); | 1389 | check_warn_return(ret, "Error writing WUCSR\n"); |
1122 | } | 1390 | } |
1123 | 1391 | ||
1124 | /* enable/disable magic packup wake */ | 1392 | /* enable/disable wakeup sources */ |
1125 | ret = smsc95xx_read_reg(dev, WUCSR, &val); | 1393 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); |
1126 | check_warn_return(ret, "Error reading WUCSR"); | 1394 | check_warn_return(ret, "Error reading WUCSR\n"); |
1395 | |||
1396 | if (pdata->wolopts & (WAKE_BCAST | WAKE_MCAST | WAKE_ARP | WAKE_UCAST)) { | ||
1397 | netdev_info(dev->net, "enabling pattern match wakeup\n"); | ||
1398 | val |= WUCSR_WAKE_EN_; | ||
1399 | } else { | ||
1400 | netdev_info(dev->net, "disabling pattern match wakeup\n"); | ||
1401 | val &= ~WUCSR_WAKE_EN_; | ||
1402 | } | ||
1127 | 1403 | ||
1128 | if (pdata->wolopts & WAKE_MAGIC) { | 1404 | if (pdata->wolopts & WAKE_MAGIC) { |
1129 | netdev_info(dev->net, "enabling magic packet wakeup"); | 1405 | netdev_info(dev->net, "enabling magic packet wakeup\n"); |
1130 | val |= WUCSR_MPEN_; | 1406 | val |= WUCSR_MPEN_; |
1131 | } else { | 1407 | } else { |
1132 | netdev_info(dev->net, "disabling magic packet wakeup"); | 1408 | netdev_info(dev->net, "disabling magic packet wakeup\n"); |
1133 | val &= ~WUCSR_MPEN_; | 1409 | val &= ~WUCSR_MPEN_; |
1134 | } | 1410 | } |
1135 | 1411 | ||
1136 | ret = smsc95xx_write_reg(dev, WUCSR, val); | 1412 | ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); |
1137 | check_warn_return(ret, "Error writing WUCSR"); | 1413 | check_warn_return(ret, "Error writing WUCSR\n"); |
1138 | 1414 | ||
1139 | /* enable wol wakeup source */ | 1415 | /* enable wol wakeup source */ |
1140 | ret = smsc95xx_read_reg(dev, PM_CTRL, &val); | 1416 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); |
1141 | check_warn_return(ret, "Error reading PM_CTRL"); | 1417 | check_warn_return(ret, "Error reading PM_CTRL\n"); |
1142 | 1418 | ||
1143 | val |= PM_CTL_WOL_EN_; | 1419 | val |= PM_CTL_WOL_EN_; |
1144 | 1420 | ||
1145 | ret = smsc95xx_write_reg(dev, PM_CTRL, val); | 1421 | /* phy energy detect wakeup source */ |
1146 | check_warn_return(ret, "Error writing PM_CTRL"); | 1422 | if (pdata->wolopts & WAKE_PHY) |
1147 | 1423 | val |= PM_CTL_ED_EN_; | |
1148 | /* enable receiver */ | ||
1149 | smsc95xx_start_rx_path(dev); | ||
1150 | |||
1151 | /* some wol options are enabled, so enter SUSPEND0 */ | ||
1152 | netdev_info(dev->net, "entering SUSPEND0 mode"); | ||
1153 | |||
1154 | ret = smsc95xx_read_reg(dev, PM_CTRL, &val); | ||
1155 | check_warn_return(ret, "Error reading PM_CTRL"); | ||
1156 | |||
1157 | val &= (~(PM_CTL_SUS_MODE_ | PM_CTL_WUPS_ | PM_CTL_PHY_RST_)); | ||
1158 | val |= PM_CTL_SUS_MODE_0; | ||
1159 | |||
1160 | ret = smsc95xx_write_reg(dev, PM_CTRL, val); | ||
1161 | check_warn_return(ret, "Error writing PM_CTRL"); | ||
1162 | |||
1163 | /* clear wol status */ | ||
1164 | val &= ~PM_CTL_WUPS_; | ||
1165 | val |= PM_CTL_WUPS_WOL_; | ||
1166 | ret = smsc95xx_write_reg(dev, PM_CTRL, val); | ||
1167 | check_warn_return(ret, "Error writing PM_CTRL"); | ||
1168 | 1424 | ||
1169 | /* read back PM_CTRL */ | 1425 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); |
1170 | ret = smsc95xx_read_reg(dev, PM_CTRL, &val); | 1426 | check_warn_return(ret, "Error writing PM_CTRL\n"); |
1171 | check_warn_return(ret, "Error reading PM_CTRL"); | ||
1172 | 1427 | ||
1173 | smsc95xx_set_feature(dev, USB_DEVICE_REMOTE_WAKEUP); | 1428 | /* enable receiver to enable frame reception */ |
1429 | smsc95xx_start_rx_path(dev, 1); | ||
1174 | 1430 | ||
1175 | return 0; | 1431 | /* some wol options are enabled, so enter SUSPEND0 */ |
1432 | netdev_info(dev->net, "entering SUSPEND0 mode\n"); | ||
1433 | return smsc95xx_enter_suspend0(dev); | ||
1176 | } | 1434 | } |
1177 | 1435 | ||
1178 | static int smsc95xx_resume(struct usb_interface *intf) | 1436 | static int smsc95xx_resume(struct usb_interface *intf) |
@@ -1184,31 +1442,31 @@ static int smsc95xx_resume(struct usb_interface *intf) | |||
1184 | 1442 | ||
1185 | BUG_ON(!dev); | 1443 | BUG_ON(!dev); |
1186 | 1444 | ||
1187 | if (pdata->wolopts & WAKE_MAGIC) { | 1445 | if (pdata->wolopts) { |
1188 | smsc95xx_clear_feature(dev, USB_DEVICE_REMOTE_WAKEUP); | 1446 | smsc95xx_clear_feature(dev, USB_DEVICE_REMOTE_WAKEUP); |
1189 | 1447 | ||
1190 | /* Disable magic packup wake */ | 1448 | /* clear wake-up sources */ |
1191 | ret = smsc95xx_read_reg(dev, WUCSR, &val); | 1449 | ret = smsc95xx_read_reg_nopm(dev, WUCSR, &val); |
1192 | check_warn_return(ret, "Error reading WUCSR"); | 1450 | check_warn_return(ret, "Error reading WUCSR\n"); |
1193 | 1451 | ||
1194 | val &= ~WUCSR_MPEN_; | 1452 | val &= ~(WUCSR_WAKE_EN_ | WUCSR_MPEN_); |
1195 | 1453 | ||
1196 | ret = smsc95xx_write_reg(dev, WUCSR, val); | 1454 | ret = smsc95xx_write_reg_nopm(dev, WUCSR, val); |
1197 | check_warn_return(ret, "Error writing WUCSR"); | 1455 | check_warn_return(ret, "Error writing WUCSR\n"); |
1198 | 1456 | ||
1199 | /* clear wake-up status */ | 1457 | /* clear wake-up status */ |
1200 | ret = smsc95xx_read_reg(dev, PM_CTRL, &val); | 1458 | ret = smsc95xx_read_reg_nopm(dev, PM_CTRL, &val); |
1201 | check_warn_return(ret, "Error reading PM_CTRL"); | 1459 | check_warn_return(ret, "Error reading PM_CTRL\n"); |
1202 | 1460 | ||
1203 | val &= ~PM_CTL_WOL_EN_; | 1461 | val &= ~PM_CTL_WOL_EN_; |
1204 | val |= PM_CTL_WUPS_; | 1462 | val |= PM_CTL_WUPS_; |
1205 | 1463 | ||
1206 | ret = smsc95xx_write_reg(dev, PM_CTRL, val); | 1464 | ret = smsc95xx_write_reg_nopm(dev, PM_CTRL, val); |
1207 | check_warn_return(ret, "Error writing PM_CTRL"); | 1465 | check_warn_return(ret, "Error writing PM_CTRL\n"); |
1208 | } | 1466 | } |
1209 | 1467 | ||
1210 | return usbnet_resume(intf); | 1468 | ret = usbnet_resume(intf); |
1211 | check_warn_return(ret, "usbnet_resume error"); | 1469 | check_warn_return(ret, "usbnet_resume error\n"); |
1212 | 1470 | ||
1213 | return 0; | 1471 | return 0; |
1214 | } | 1472 | } |