162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 1999-2021 Petko Manolov (petkan@nucleusys.com) 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/sched.h> 862306a36Sopenharmony_ci#include <linux/slab.h> 962306a36Sopenharmony_ci#include <linux/init.h> 1062306a36Sopenharmony_ci#include <linux/delay.h> 1162306a36Sopenharmony_ci#include <linux/netdevice.h> 1262306a36Sopenharmony_ci#include <linux/etherdevice.h> 1362306a36Sopenharmony_ci#include <linux/ethtool.h> 1462306a36Sopenharmony_ci#include <linux/mii.h> 1562306a36Sopenharmony_ci#include <linux/usb.h> 1662306a36Sopenharmony_ci#include <linux/module.h> 1762306a36Sopenharmony_ci#include <asm/byteorder.h> 1862306a36Sopenharmony_ci#include <linux/uaccess.h> 1962306a36Sopenharmony_ci#include "pegasus.h" 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci/* 2262306a36Sopenharmony_ci * Version Information 2362306a36Sopenharmony_ci */ 2462306a36Sopenharmony_ci#define DRIVER_AUTHOR "Petko Manolov <petkan@nucleusys.com>" 2562306a36Sopenharmony_ci#define DRIVER_DESC "Pegasus/Pegasus II USB Ethernet driver" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic const char driver_name[] = "pegasus"; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#undef PEGASUS_WRITE_EEPROM 3062306a36Sopenharmony_ci#define BMSR_MEDIA (BMSR_10HALF | BMSR_10FULL | BMSR_100HALF | \ 3162306a36Sopenharmony_ci BMSR_100FULL | BMSR_ANEGCAPABLE) 3262306a36Sopenharmony_ci#define CARRIER_CHECK_DELAY (2 * HZ) 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic bool loopback; 3562306a36Sopenharmony_cistatic bool mii_mode; 3662306a36Sopenharmony_cistatic char *devid; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_cistatic struct usb_eth_dev usb_dev_id[] = { 3962306a36Sopenharmony_ci#define PEGASUS_DEV(pn, vid, pid, flags) \ 4062306a36Sopenharmony_ci {.name = pn, .vendor = vid, .device = pid, .private = flags}, 4162306a36Sopenharmony_ci#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \ 4262306a36Sopenharmony_ci PEGASUS_DEV(pn, vid, pid, flags) 4362306a36Sopenharmony_ci#include "pegasus.h" 4462306a36Sopenharmony_ci#undef PEGASUS_DEV 4562306a36Sopenharmony_ci#undef PEGASUS_DEV_CLASS 4662306a36Sopenharmony_ci {NULL, 0, 0, 0}, 4762306a36Sopenharmony_ci {NULL, 0, 0, 0} 4862306a36Sopenharmony_ci}; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_cistatic struct usb_device_id pegasus_ids[] = { 5162306a36Sopenharmony_ci#define PEGASUS_DEV(pn, vid, pid, flags) \ 5262306a36Sopenharmony_ci {.match_flags = USB_DEVICE_ID_MATCH_DEVICE, .idVendor = vid, .idProduct = pid}, 5362306a36Sopenharmony_ci/* 5462306a36Sopenharmony_ci * The Belkin F8T012xx1 bluetooth adaptor has the same vendor and product 5562306a36Sopenharmony_ci * IDs as the Belkin F5D5050, so we need to teach the pegasus driver to 5662306a36Sopenharmony_ci * ignore adaptors belonging to the "Wireless" class 0xE0. For this one 5762306a36Sopenharmony_ci * case anyway, seeing as the pegasus is for "Wired" adaptors. 5862306a36Sopenharmony_ci */ 5962306a36Sopenharmony_ci#define PEGASUS_DEV_CLASS(pn, vid, pid, dclass, flags) \ 6062306a36Sopenharmony_ci {.match_flags = (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_DEV_CLASS), \ 6162306a36Sopenharmony_ci .idVendor = vid, .idProduct = pid, .bDeviceClass = dclass}, 6262306a36Sopenharmony_ci#include "pegasus.h" 6362306a36Sopenharmony_ci#undef PEGASUS_DEV 6462306a36Sopenharmony_ci#undef PEGASUS_DEV_CLASS 6562306a36Sopenharmony_ci {}, 6662306a36Sopenharmony_ci {} 6762306a36Sopenharmony_ci}; 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_ciMODULE_AUTHOR(DRIVER_AUTHOR); 7062306a36Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC); 7162306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 7262306a36Sopenharmony_cimodule_param(loopback, bool, 0); 7362306a36Sopenharmony_cimodule_param(mii_mode, bool, 0); 7462306a36Sopenharmony_cimodule_param(devid, charp, 0); 7562306a36Sopenharmony_ciMODULE_PARM_DESC(loopback, "Enable MAC loopback mode (bit 0)"); 7662306a36Sopenharmony_ciMODULE_PARM_DESC(mii_mode, "Enable HomePNA mode (bit 0),default=MII mode = 0"); 7762306a36Sopenharmony_ciMODULE_PARM_DESC(devid, "The format is: 'DEV_name:VendorID:DeviceID:Flags'"); 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci/* use ethtool to change the level for any given device */ 8062306a36Sopenharmony_cistatic int msg_level = -1; 8162306a36Sopenharmony_cimodule_param(msg_level, int, 0); 8262306a36Sopenharmony_ciMODULE_PARM_DESC(msg_level, "Override default message level"); 8362306a36Sopenharmony_ci 8462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, pegasus_ids); 8562306a36Sopenharmony_cistatic const struct net_device_ops pegasus_netdev_ops; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci/*****/ 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_cistatic void async_ctrl_callback(struct urb *urb) 9062306a36Sopenharmony_ci{ 9162306a36Sopenharmony_ci struct usb_ctrlrequest *req = (struct usb_ctrlrequest *)urb->context; 9262306a36Sopenharmony_ci int status = urb->status; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci if (status < 0) 9562306a36Sopenharmony_ci dev_dbg(&urb->dev->dev, "%s failed with %d", __func__, status); 9662306a36Sopenharmony_ci kfree(req); 9762306a36Sopenharmony_ci usb_free_urb(urb); 9862306a36Sopenharmony_ci} 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_cistatic int get_registers(pegasus_t *pegasus, __u16 indx, __u16 size, void *data) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci return usb_control_msg_recv(pegasus->usb, 0, PEGASUS_REQ_GET_REGS, 10362306a36Sopenharmony_ci PEGASUS_REQT_READ, 0, indx, data, size, 10462306a36Sopenharmony_ci 1000, GFP_NOIO); 10562306a36Sopenharmony_ci} 10662306a36Sopenharmony_ci 10762306a36Sopenharmony_cistatic int set_registers(pegasus_t *pegasus, __u16 indx, __u16 size, 10862306a36Sopenharmony_ci const void *data) 10962306a36Sopenharmony_ci{ 11062306a36Sopenharmony_ci int ret; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci ret = usb_control_msg_send(pegasus->usb, 0, PEGASUS_REQ_SET_REGS, 11362306a36Sopenharmony_ci PEGASUS_REQT_WRITE, 0, indx, data, size, 11462306a36Sopenharmony_ci 1000, GFP_NOIO); 11562306a36Sopenharmony_ci if (ret < 0) 11662306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed with %d\n", __func__, ret); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci return ret; 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/* 12262306a36Sopenharmony_ci * There is only one way to write to a single ADM8511 register and this is via 12362306a36Sopenharmony_ci * specific control request. 'data' is ignored by the device, but it is here to 12462306a36Sopenharmony_ci * not break the API. 12562306a36Sopenharmony_ci */ 12662306a36Sopenharmony_cistatic int set_register(pegasus_t *pegasus, __u16 indx, __u8 data) 12762306a36Sopenharmony_ci{ 12862306a36Sopenharmony_ci void *buf = &data; 12962306a36Sopenharmony_ci int ret; 13062306a36Sopenharmony_ci 13162306a36Sopenharmony_ci ret = usb_control_msg_send(pegasus->usb, 0, PEGASUS_REQ_SET_REG, 13262306a36Sopenharmony_ci PEGASUS_REQT_WRITE, data, indx, buf, 1, 13362306a36Sopenharmony_ci 1000, GFP_NOIO); 13462306a36Sopenharmony_ci if (ret < 0) 13562306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed with %d\n", __func__, ret); 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci return ret; 13862306a36Sopenharmony_ci} 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic int update_eth_regs_async(pegasus_t *pegasus) 14162306a36Sopenharmony_ci{ 14262306a36Sopenharmony_ci int ret = -ENOMEM; 14362306a36Sopenharmony_ci struct urb *async_urb; 14462306a36Sopenharmony_ci struct usb_ctrlrequest *req; 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci req = kmalloc(sizeof(struct usb_ctrlrequest), GFP_ATOMIC); 14762306a36Sopenharmony_ci if (req == NULL) 14862306a36Sopenharmony_ci return ret; 14962306a36Sopenharmony_ci 15062306a36Sopenharmony_ci async_urb = usb_alloc_urb(0, GFP_ATOMIC); 15162306a36Sopenharmony_ci if (async_urb == NULL) { 15262306a36Sopenharmony_ci kfree(req); 15362306a36Sopenharmony_ci return ret; 15462306a36Sopenharmony_ci } 15562306a36Sopenharmony_ci req->bRequestType = PEGASUS_REQT_WRITE; 15662306a36Sopenharmony_ci req->bRequest = PEGASUS_REQ_SET_REGS; 15762306a36Sopenharmony_ci req->wValue = cpu_to_le16(0); 15862306a36Sopenharmony_ci req->wIndex = cpu_to_le16(EthCtrl0); 15962306a36Sopenharmony_ci req->wLength = cpu_to_le16(3); 16062306a36Sopenharmony_ci 16162306a36Sopenharmony_ci usb_fill_control_urb(async_urb, pegasus->usb, 16262306a36Sopenharmony_ci usb_sndctrlpipe(pegasus->usb, 0), (void *)req, 16362306a36Sopenharmony_ci pegasus->eth_regs, 3, async_ctrl_callback, req); 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_ci ret = usb_submit_urb(async_urb, GFP_ATOMIC); 16662306a36Sopenharmony_ci if (ret) { 16762306a36Sopenharmony_ci if (ret == -ENODEV) 16862306a36Sopenharmony_ci netif_device_detach(pegasus->net); 16962306a36Sopenharmony_ci netif_err(pegasus, drv, pegasus->net, 17062306a36Sopenharmony_ci "%s returned %d\n", __func__, ret); 17162306a36Sopenharmony_ci } 17262306a36Sopenharmony_ci return ret; 17362306a36Sopenharmony_ci} 17462306a36Sopenharmony_ci 17562306a36Sopenharmony_cistatic int __mii_op(pegasus_t *p, __u8 phy, __u8 indx, __u16 *regd, __u8 cmd) 17662306a36Sopenharmony_ci{ 17762306a36Sopenharmony_ci int i, ret; 17862306a36Sopenharmony_ci __le16 regdi; 17962306a36Sopenharmony_ci __u8 data[4] = { phy, 0, 0, indx }; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci if (cmd & PHY_WRITE) { 18262306a36Sopenharmony_ci __le16 *t = (__le16 *) & data[1]; 18362306a36Sopenharmony_ci *t = cpu_to_le16(*regd); 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci set_register(p, PhyCtrl, 0); 18662306a36Sopenharmony_ci set_registers(p, PhyAddr, sizeof(data), data); 18762306a36Sopenharmony_ci set_register(p, PhyCtrl, (indx | cmd)); 18862306a36Sopenharmony_ci for (i = 0; i < REG_TIMEOUT; i++) { 18962306a36Sopenharmony_ci ret = get_registers(p, PhyCtrl, 1, data); 19062306a36Sopenharmony_ci if (ret < 0) 19162306a36Sopenharmony_ci goto fail; 19262306a36Sopenharmony_ci if (data[0] & PHY_DONE) 19362306a36Sopenharmony_ci break; 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci if (i >= REG_TIMEOUT) { 19662306a36Sopenharmony_ci ret = -ETIMEDOUT; 19762306a36Sopenharmony_ci goto fail; 19862306a36Sopenharmony_ci } 19962306a36Sopenharmony_ci if (cmd & PHY_READ) { 20062306a36Sopenharmony_ci ret = get_registers(p, PhyData, 2, ®di); 20162306a36Sopenharmony_ci if (ret < 0) 20262306a36Sopenharmony_ci goto fail; 20362306a36Sopenharmony_ci *regd = le16_to_cpu(regdi); 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci return 0; 20662306a36Sopenharmony_cifail: 20762306a36Sopenharmony_ci netif_dbg(p, drv, p->net, "%s failed\n", __func__); 20862306a36Sopenharmony_ci return ret; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci/* Returns non-negative int on success, error on failure */ 21262306a36Sopenharmony_cistatic int read_mii_word(pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci return __mii_op(pegasus, phy, indx, regd, PHY_READ); 21562306a36Sopenharmony_ci} 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ci/* Returns zero on success, error on failure */ 21862306a36Sopenharmony_cistatic int write_mii_word(pegasus_t *pegasus, __u8 phy, __u8 indx, __u16 *regd) 21962306a36Sopenharmony_ci{ 22062306a36Sopenharmony_ci return __mii_op(pegasus, phy, indx, regd, PHY_WRITE); 22162306a36Sopenharmony_ci} 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_cistatic int mdio_read(struct net_device *dev, int phy_id, int loc) 22462306a36Sopenharmony_ci{ 22562306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 22662306a36Sopenharmony_ci int ret; 22762306a36Sopenharmony_ci u16 res; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci ret = read_mii_word(pegasus, phy_id, loc, &res); 23062306a36Sopenharmony_ci if (ret < 0) 23162306a36Sopenharmony_ci return ret; 23262306a36Sopenharmony_ci 23362306a36Sopenharmony_ci return (int)res; 23462306a36Sopenharmony_ci} 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_cistatic void mdio_write(struct net_device *dev, int phy_id, int loc, int val) 23762306a36Sopenharmony_ci{ 23862306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 23962306a36Sopenharmony_ci u16 data = val; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci write_mii_word(pegasus, phy_id, loc, &data); 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_cistatic int read_eprom_word(pegasus_t *pegasus, __u8 index, __u16 *retdata) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci int ret, i; 24762306a36Sopenharmony_ci __le16 retdatai; 24862306a36Sopenharmony_ci __u8 tmp = 0; 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_ci set_register(pegasus, EpromCtrl, 0); 25162306a36Sopenharmony_ci set_register(pegasus, EpromOffset, index); 25262306a36Sopenharmony_ci set_register(pegasus, EpromCtrl, EPROM_READ); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci for (i = 0; i < REG_TIMEOUT; i++) { 25562306a36Sopenharmony_ci ret = get_registers(pegasus, EpromCtrl, 1, &tmp); 25662306a36Sopenharmony_ci if (ret < 0) 25762306a36Sopenharmony_ci goto fail; 25862306a36Sopenharmony_ci if (tmp & EPROM_DONE) 25962306a36Sopenharmony_ci break; 26062306a36Sopenharmony_ci } 26162306a36Sopenharmony_ci if (i >= REG_TIMEOUT) { 26262306a36Sopenharmony_ci ret = -ETIMEDOUT; 26362306a36Sopenharmony_ci goto fail; 26462306a36Sopenharmony_ci } 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci ret = get_registers(pegasus, EpromData, 2, &retdatai); 26762306a36Sopenharmony_ci if (ret < 0) 26862306a36Sopenharmony_ci goto fail; 26962306a36Sopenharmony_ci *retdata = le16_to_cpu(retdatai); 27062306a36Sopenharmony_ci return ret; 27162306a36Sopenharmony_ci 27262306a36Sopenharmony_cifail: 27362306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed\n", __func__); 27462306a36Sopenharmony_ci return ret; 27562306a36Sopenharmony_ci} 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci#ifdef PEGASUS_WRITE_EEPROM 27862306a36Sopenharmony_cistatic inline void enable_eprom_write(pegasus_t *pegasus) 27962306a36Sopenharmony_ci{ 28062306a36Sopenharmony_ci __u8 tmp; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci get_registers(pegasus, EthCtrl2, 1, &tmp); 28362306a36Sopenharmony_ci set_register(pegasus, EthCtrl2, tmp | EPROM_WR_ENABLE); 28462306a36Sopenharmony_ci} 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_cistatic inline void disable_eprom_write(pegasus_t *pegasus) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci __u8 tmp; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci get_registers(pegasus, EthCtrl2, 1, &tmp); 29162306a36Sopenharmony_ci set_register(pegasus, EpromCtrl, 0); 29262306a36Sopenharmony_ci set_register(pegasus, EthCtrl2, tmp & ~EPROM_WR_ENABLE); 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic int write_eprom_word(pegasus_t *pegasus, __u8 index, __u16 data) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci int i; 29862306a36Sopenharmony_ci __u8 tmp, d[4] = { 0x3f, 0, 0, EPROM_WRITE }; 29962306a36Sopenharmony_ci int ret; 30062306a36Sopenharmony_ci __le16 le_data = cpu_to_le16(data); 30162306a36Sopenharmony_ci 30262306a36Sopenharmony_ci set_registers(pegasus, EpromOffset, 4, d); 30362306a36Sopenharmony_ci enable_eprom_write(pegasus); 30462306a36Sopenharmony_ci set_register(pegasus, EpromOffset, index); 30562306a36Sopenharmony_ci set_registers(pegasus, EpromData, 2, &le_data); 30662306a36Sopenharmony_ci set_register(pegasus, EpromCtrl, EPROM_WRITE); 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_ci for (i = 0; i < REG_TIMEOUT; i++) { 30962306a36Sopenharmony_ci ret = get_registers(pegasus, EpromCtrl, 1, &tmp); 31062306a36Sopenharmony_ci if (ret == -ESHUTDOWN) 31162306a36Sopenharmony_ci goto fail; 31262306a36Sopenharmony_ci if (tmp & EPROM_DONE) 31362306a36Sopenharmony_ci break; 31462306a36Sopenharmony_ci } 31562306a36Sopenharmony_ci disable_eprom_write(pegasus); 31662306a36Sopenharmony_ci if (i >= REG_TIMEOUT) 31762306a36Sopenharmony_ci goto fail; 31862306a36Sopenharmony_ci 31962306a36Sopenharmony_ci return ret; 32062306a36Sopenharmony_ci 32162306a36Sopenharmony_cifail: 32262306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed\n", __func__); 32362306a36Sopenharmony_ci return -ETIMEDOUT; 32462306a36Sopenharmony_ci} 32562306a36Sopenharmony_ci#endif /* PEGASUS_WRITE_EEPROM */ 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_cistatic inline int get_node_id(pegasus_t *pegasus, u8 *id) 32862306a36Sopenharmony_ci{ 32962306a36Sopenharmony_ci int i, ret; 33062306a36Sopenharmony_ci u16 w16; 33162306a36Sopenharmony_ci 33262306a36Sopenharmony_ci for (i = 0; i < 3; i++) { 33362306a36Sopenharmony_ci ret = read_eprom_word(pegasus, i, &w16); 33462306a36Sopenharmony_ci if (ret < 0) 33562306a36Sopenharmony_ci return ret; 33662306a36Sopenharmony_ci ((__le16 *) id)[i] = cpu_to_le16(w16); 33762306a36Sopenharmony_ci } 33862306a36Sopenharmony_ci 33962306a36Sopenharmony_ci return 0; 34062306a36Sopenharmony_ci} 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_cistatic void set_ethernet_addr(pegasus_t *pegasus) 34362306a36Sopenharmony_ci{ 34462306a36Sopenharmony_ci int ret; 34562306a36Sopenharmony_ci u8 node_id[6]; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci if (pegasus->features & PEGASUS_II) { 34862306a36Sopenharmony_ci ret = get_registers(pegasus, 0x10, sizeof(node_id), node_id); 34962306a36Sopenharmony_ci if (ret < 0) 35062306a36Sopenharmony_ci goto err; 35162306a36Sopenharmony_ci } else { 35262306a36Sopenharmony_ci ret = get_node_id(pegasus, node_id); 35362306a36Sopenharmony_ci if (ret < 0) 35462306a36Sopenharmony_ci goto err; 35562306a36Sopenharmony_ci ret = set_registers(pegasus, EthID, sizeof(node_id), node_id); 35662306a36Sopenharmony_ci if (ret < 0) 35762306a36Sopenharmony_ci goto err; 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci eth_hw_addr_set(pegasus->net, node_id); 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci return; 36362306a36Sopenharmony_cierr: 36462306a36Sopenharmony_ci eth_hw_addr_random(pegasus->net); 36562306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "software assigned MAC address.\n"); 36662306a36Sopenharmony_ci 36762306a36Sopenharmony_ci return; 36862306a36Sopenharmony_ci} 36962306a36Sopenharmony_ci 37062306a36Sopenharmony_cistatic inline int reset_mac(pegasus_t *pegasus) 37162306a36Sopenharmony_ci{ 37262306a36Sopenharmony_ci int ret, i; 37362306a36Sopenharmony_ci __u8 data = 0x8; 37462306a36Sopenharmony_ci 37562306a36Sopenharmony_ci set_register(pegasus, EthCtrl1, data); 37662306a36Sopenharmony_ci for (i = 0; i < REG_TIMEOUT; i++) { 37762306a36Sopenharmony_ci ret = get_registers(pegasus, EthCtrl1, 1, &data); 37862306a36Sopenharmony_ci if (ret < 0) 37962306a36Sopenharmony_ci goto fail; 38062306a36Sopenharmony_ci if (~data & 0x08) { 38162306a36Sopenharmony_ci if (loopback) 38262306a36Sopenharmony_ci break; 38362306a36Sopenharmony_ci if (mii_mode && (pegasus->features & HAS_HOME_PNA)) 38462306a36Sopenharmony_ci set_register(pegasus, Gpio1, 0x34); 38562306a36Sopenharmony_ci else 38662306a36Sopenharmony_ci set_register(pegasus, Gpio1, 0x26); 38762306a36Sopenharmony_ci set_register(pegasus, Gpio0, pegasus->features); 38862306a36Sopenharmony_ci set_register(pegasus, Gpio0, DEFAULT_GPIO_SET); 38962306a36Sopenharmony_ci break; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci } 39262306a36Sopenharmony_ci if (i == REG_TIMEOUT) 39362306a36Sopenharmony_ci return -ETIMEDOUT; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS || 39662306a36Sopenharmony_ci usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) { 39762306a36Sopenharmony_ci set_register(pegasus, Gpio0, 0x24); 39862306a36Sopenharmony_ci set_register(pegasus, Gpio0, 0x26); 39962306a36Sopenharmony_ci } 40062306a36Sopenharmony_ci if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_ELCON) { 40162306a36Sopenharmony_ci __u16 auxmode; 40262306a36Sopenharmony_ci ret = read_mii_word(pegasus, 3, 0x1b, &auxmode); 40362306a36Sopenharmony_ci if (ret < 0) 40462306a36Sopenharmony_ci goto fail; 40562306a36Sopenharmony_ci auxmode |= 4; 40662306a36Sopenharmony_ci write_mii_word(pegasus, 3, 0x1b, &auxmode); 40762306a36Sopenharmony_ci } 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_ci return 0; 41062306a36Sopenharmony_cifail: 41162306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed\n", __func__); 41262306a36Sopenharmony_ci return ret; 41362306a36Sopenharmony_ci} 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_cistatic int enable_net_traffic(struct net_device *dev, struct usb_device *usb) 41662306a36Sopenharmony_ci{ 41762306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 41862306a36Sopenharmony_ci int ret; 41962306a36Sopenharmony_ci __u16 linkpart; 42062306a36Sopenharmony_ci __u8 data[4]; 42162306a36Sopenharmony_ci 42262306a36Sopenharmony_ci ret = read_mii_word(pegasus, pegasus->phy, MII_LPA, &linkpart); 42362306a36Sopenharmony_ci if (ret < 0) 42462306a36Sopenharmony_ci goto fail; 42562306a36Sopenharmony_ci data[0] = 0xc8; /* TX & RX enable, append status, no CRC */ 42662306a36Sopenharmony_ci data[1] = 0; 42762306a36Sopenharmony_ci if (linkpart & (ADVERTISE_100FULL | ADVERTISE_10FULL)) 42862306a36Sopenharmony_ci data[1] |= 0x20; /* set full duplex */ 42962306a36Sopenharmony_ci if (linkpart & (ADVERTISE_100FULL | ADVERTISE_100HALF)) 43062306a36Sopenharmony_ci data[1] |= 0x10; /* set 100 Mbps */ 43162306a36Sopenharmony_ci if (mii_mode) 43262306a36Sopenharmony_ci data[1] = 0; 43362306a36Sopenharmony_ci data[2] = loopback ? 0x09 : 0x01; 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci memcpy(pegasus->eth_regs, data, sizeof(data)); 43662306a36Sopenharmony_ci ret = set_registers(pegasus, EthCtrl0, 3, data); 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci if (usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS || 43962306a36Sopenharmony_ci usb_dev_id[pegasus->dev_index].vendor == VENDOR_LINKSYS2 || 44062306a36Sopenharmony_ci usb_dev_id[pegasus->dev_index].vendor == VENDOR_DLINK) { 44162306a36Sopenharmony_ci u16 auxmode; 44262306a36Sopenharmony_ci ret = read_mii_word(pegasus, 0, 0x1b, &auxmode); 44362306a36Sopenharmony_ci if (ret < 0) 44462306a36Sopenharmony_ci goto fail; 44562306a36Sopenharmony_ci auxmode |= 4; 44662306a36Sopenharmony_ci write_mii_word(pegasus, 0, 0x1b, &auxmode); 44762306a36Sopenharmony_ci } 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci return ret; 45062306a36Sopenharmony_cifail: 45162306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed\n", __func__); 45262306a36Sopenharmony_ci return ret; 45362306a36Sopenharmony_ci} 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_cistatic void read_bulk_callback(struct urb *urb) 45662306a36Sopenharmony_ci{ 45762306a36Sopenharmony_ci pegasus_t *pegasus = urb->context; 45862306a36Sopenharmony_ci struct net_device *net; 45962306a36Sopenharmony_ci u8 *buf = urb->transfer_buffer; 46062306a36Sopenharmony_ci int rx_status, count = urb->actual_length; 46162306a36Sopenharmony_ci int status = urb->status; 46262306a36Sopenharmony_ci __u16 pkt_len; 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci if (!pegasus) 46562306a36Sopenharmony_ci return; 46662306a36Sopenharmony_ci 46762306a36Sopenharmony_ci net = pegasus->net; 46862306a36Sopenharmony_ci if (!netif_device_present(net) || !netif_running(net)) 46962306a36Sopenharmony_ci return; 47062306a36Sopenharmony_ci 47162306a36Sopenharmony_ci switch (status) { 47262306a36Sopenharmony_ci case 0: 47362306a36Sopenharmony_ci break; 47462306a36Sopenharmony_ci case -ETIME: 47562306a36Sopenharmony_ci netif_dbg(pegasus, rx_err, net, "reset MAC\n"); 47662306a36Sopenharmony_ci pegasus->flags &= ~PEGASUS_RX_BUSY; 47762306a36Sopenharmony_ci break; 47862306a36Sopenharmony_ci case -EPIPE: /* stall, or disconnect from TT */ 47962306a36Sopenharmony_ci /* FIXME schedule work to clear the halt */ 48062306a36Sopenharmony_ci netif_warn(pegasus, rx_err, net, "no rx stall recovery\n"); 48162306a36Sopenharmony_ci return; 48262306a36Sopenharmony_ci case -ENOENT: 48362306a36Sopenharmony_ci case -ECONNRESET: 48462306a36Sopenharmony_ci case -ESHUTDOWN: 48562306a36Sopenharmony_ci netif_dbg(pegasus, ifdown, net, "rx unlink, %d\n", status); 48662306a36Sopenharmony_ci return; 48762306a36Sopenharmony_ci default: 48862306a36Sopenharmony_ci netif_dbg(pegasus, rx_err, net, "RX status %d\n", status); 48962306a36Sopenharmony_ci goto goon; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci if (count < 4) 49362306a36Sopenharmony_ci goto goon; 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci rx_status = buf[count - 2]; 49662306a36Sopenharmony_ci if (rx_status & 0x1c) { 49762306a36Sopenharmony_ci netif_dbg(pegasus, rx_err, net, 49862306a36Sopenharmony_ci "RX packet error %x\n", rx_status); 49962306a36Sopenharmony_ci net->stats.rx_errors++; 50062306a36Sopenharmony_ci if (rx_status & 0x04) /* runt */ 50162306a36Sopenharmony_ci net->stats.rx_length_errors++; 50262306a36Sopenharmony_ci if (rx_status & 0x08) 50362306a36Sopenharmony_ci net->stats.rx_crc_errors++; 50462306a36Sopenharmony_ci if (rx_status & 0x10) /* extra bits */ 50562306a36Sopenharmony_ci net->stats.rx_frame_errors++; 50662306a36Sopenharmony_ci goto goon; 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci if (pegasus->chip == 0x8513) { 50962306a36Sopenharmony_ci pkt_len = le32_to_cpu(*(__le32 *)urb->transfer_buffer); 51062306a36Sopenharmony_ci pkt_len &= 0x0fff; 51162306a36Sopenharmony_ci pegasus->rx_skb->data += 2; 51262306a36Sopenharmony_ci } else { 51362306a36Sopenharmony_ci pkt_len = buf[count - 3] << 8; 51462306a36Sopenharmony_ci pkt_len += buf[count - 4]; 51562306a36Sopenharmony_ci pkt_len &= 0xfff; 51662306a36Sopenharmony_ci pkt_len -= 4; 51762306a36Sopenharmony_ci } 51862306a36Sopenharmony_ci 51962306a36Sopenharmony_ci /* 52062306a36Sopenharmony_ci * If the packet is unreasonably long, quietly drop it rather than 52162306a36Sopenharmony_ci * kernel panicing by calling skb_put. 52262306a36Sopenharmony_ci */ 52362306a36Sopenharmony_ci if (pkt_len > PEGASUS_MTU) 52462306a36Sopenharmony_ci goto goon; 52562306a36Sopenharmony_ci 52662306a36Sopenharmony_ci /* 52762306a36Sopenharmony_ci * at this point we are sure pegasus->rx_skb != NULL 52862306a36Sopenharmony_ci * so we go ahead and pass up the packet. 52962306a36Sopenharmony_ci */ 53062306a36Sopenharmony_ci skb_put(pegasus->rx_skb, pkt_len); 53162306a36Sopenharmony_ci pegasus->rx_skb->protocol = eth_type_trans(pegasus->rx_skb, net); 53262306a36Sopenharmony_ci netif_rx(pegasus->rx_skb); 53362306a36Sopenharmony_ci net->stats.rx_packets++; 53462306a36Sopenharmony_ci net->stats.rx_bytes += pkt_len; 53562306a36Sopenharmony_ci 53662306a36Sopenharmony_ci if (pegasus->flags & PEGASUS_UNPLUG) 53762306a36Sopenharmony_ci return; 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci pegasus->rx_skb = __netdev_alloc_skb_ip_align(pegasus->net, PEGASUS_MTU, 54062306a36Sopenharmony_ci GFP_ATOMIC); 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci if (pegasus->rx_skb == NULL) 54362306a36Sopenharmony_ci goto tl_sched; 54462306a36Sopenharmony_cigoon: 54562306a36Sopenharmony_ci usb_fill_bulk_urb(pegasus->rx_urb, pegasus->usb, 54662306a36Sopenharmony_ci usb_rcvbulkpipe(pegasus->usb, 1), 54762306a36Sopenharmony_ci pegasus->rx_skb->data, PEGASUS_MTU, 54862306a36Sopenharmony_ci read_bulk_callback, pegasus); 54962306a36Sopenharmony_ci rx_status = usb_submit_urb(pegasus->rx_urb, GFP_ATOMIC); 55062306a36Sopenharmony_ci if (rx_status == -ENODEV) 55162306a36Sopenharmony_ci netif_device_detach(pegasus->net); 55262306a36Sopenharmony_ci else if (rx_status) { 55362306a36Sopenharmony_ci pegasus->flags |= PEGASUS_RX_URB_FAIL; 55462306a36Sopenharmony_ci goto tl_sched; 55562306a36Sopenharmony_ci } else { 55662306a36Sopenharmony_ci pegasus->flags &= ~PEGASUS_RX_URB_FAIL; 55762306a36Sopenharmony_ci } 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci return; 56062306a36Sopenharmony_ci 56162306a36Sopenharmony_citl_sched: 56262306a36Sopenharmony_ci tasklet_schedule(&pegasus->rx_tl); 56362306a36Sopenharmony_ci} 56462306a36Sopenharmony_ci 56562306a36Sopenharmony_cistatic void rx_fixup(struct tasklet_struct *t) 56662306a36Sopenharmony_ci{ 56762306a36Sopenharmony_ci pegasus_t *pegasus = from_tasklet(pegasus, t, rx_tl); 56862306a36Sopenharmony_ci int status; 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci if (pegasus->flags & PEGASUS_UNPLUG) 57162306a36Sopenharmony_ci return; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci if (pegasus->flags & PEGASUS_RX_URB_FAIL) 57462306a36Sopenharmony_ci if (pegasus->rx_skb) 57562306a36Sopenharmony_ci goto try_again; 57662306a36Sopenharmony_ci if (pegasus->rx_skb == NULL) 57762306a36Sopenharmony_ci pegasus->rx_skb = __netdev_alloc_skb_ip_align(pegasus->net, 57862306a36Sopenharmony_ci PEGASUS_MTU, 57962306a36Sopenharmony_ci GFP_ATOMIC); 58062306a36Sopenharmony_ci if (pegasus->rx_skb == NULL) { 58162306a36Sopenharmony_ci netif_warn(pegasus, rx_err, pegasus->net, "low on memory\n"); 58262306a36Sopenharmony_ci tasklet_schedule(&pegasus->rx_tl); 58362306a36Sopenharmony_ci return; 58462306a36Sopenharmony_ci } 58562306a36Sopenharmony_ci usb_fill_bulk_urb(pegasus->rx_urb, pegasus->usb, 58662306a36Sopenharmony_ci usb_rcvbulkpipe(pegasus->usb, 1), 58762306a36Sopenharmony_ci pegasus->rx_skb->data, PEGASUS_MTU, 58862306a36Sopenharmony_ci read_bulk_callback, pegasus); 58962306a36Sopenharmony_citry_again: 59062306a36Sopenharmony_ci status = usb_submit_urb(pegasus->rx_urb, GFP_ATOMIC); 59162306a36Sopenharmony_ci if (status == -ENODEV) 59262306a36Sopenharmony_ci netif_device_detach(pegasus->net); 59362306a36Sopenharmony_ci else if (status) { 59462306a36Sopenharmony_ci pegasus->flags |= PEGASUS_RX_URB_FAIL; 59562306a36Sopenharmony_ci tasklet_schedule(&pegasus->rx_tl); 59662306a36Sopenharmony_ci } else { 59762306a36Sopenharmony_ci pegasus->flags &= ~PEGASUS_RX_URB_FAIL; 59862306a36Sopenharmony_ci } 59962306a36Sopenharmony_ci} 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_cistatic void write_bulk_callback(struct urb *urb) 60262306a36Sopenharmony_ci{ 60362306a36Sopenharmony_ci pegasus_t *pegasus = urb->context; 60462306a36Sopenharmony_ci struct net_device *net; 60562306a36Sopenharmony_ci int status = urb->status; 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_ci if (!pegasus) 60862306a36Sopenharmony_ci return; 60962306a36Sopenharmony_ci 61062306a36Sopenharmony_ci net = pegasus->net; 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci if (!netif_device_present(net) || !netif_running(net)) 61362306a36Sopenharmony_ci return; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci switch (status) { 61662306a36Sopenharmony_ci case -EPIPE: 61762306a36Sopenharmony_ci /* FIXME schedule_work() to clear the tx halt */ 61862306a36Sopenharmony_ci netif_stop_queue(net); 61962306a36Sopenharmony_ci netif_warn(pegasus, tx_err, net, "no tx stall recovery\n"); 62062306a36Sopenharmony_ci return; 62162306a36Sopenharmony_ci case -ENOENT: 62262306a36Sopenharmony_ci case -ECONNRESET: 62362306a36Sopenharmony_ci case -ESHUTDOWN: 62462306a36Sopenharmony_ci netif_dbg(pegasus, ifdown, net, "tx unlink, %d\n", status); 62562306a36Sopenharmony_ci return; 62662306a36Sopenharmony_ci default: 62762306a36Sopenharmony_ci netif_info(pegasus, tx_err, net, "TX status %d\n", status); 62862306a36Sopenharmony_ci fallthrough; 62962306a36Sopenharmony_ci case 0: 63062306a36Sopenharmony_ci break; 63162306a36Sopenharmony_ci } 63262306a36Sopenharmony_ci 63362306a36Sopenharmony_ci netif_trans_update(net); /* prevent tx timeout */ 63462306a36Sopenharmony_ci netif_wake_queue(net); 63562306a36Sopenharmony_ci} 63662306a36Sopenharmony_ci 63762306a36Sopenharmony_cistatic void intr_callback(struct urb *urb) 63862306a36Sopenharmony_ci{ 63962306a36Sopenharmony_ci pegasus_t *pegasus = urb->context; 64062306a36Sopenharmony_ci struct net_device *net; 64162306a36Sopenharmony_ci int res, status = urb->status; 64262306a36Sopenharmony_ci 64362306a36Sopenharmony_ci if (!pegasus) 64462306a36Sopenharmony_ci return; 64562306a36Sopenharmony_ci net = pegasus->net; 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci switch (status) { 64862306a36Sopenharmony_ci case 0: 64962306a36Sopenharmony_ci break; 65062306a36Sopenharmony_ci case -ECONNRESET: /* unlink */ 65162306a36Sopenharmony_ci case -ENOENT: 65262306a36Sopenharmony_ci case -ESHUTDOWN: 65362306a36Sopenharmony_ci return; 65462306a36Sopenharmony_ci default: 65562306a36Sopenharmony_ci /* some Pegasus-I products report LOTS of data 65662306a36Sopenharmony_ci * toggle errors... avoid log spamming 65762306a36Sopenharmony_ci */ 65862306a36Sopenharmony_ci netif_dbg(pegasus, timer, net, "intr status %d\n", status); 65962306a36Sopenharmony_ci } 66062306a36Sopenharmony_ci 66162306a36Sopenharmony_ci if (urb->actual_length >= 6) { 66262306a36Sopenharmony_ci u8 *d = urb->transfer_buffer; 66362306a36Sopenharmony_ci 66462306a36Sopenharmony_ci /* byte 0 == tx_status1, reg 2B */ 66562306a36Sopenharmony_ci if (d[0] & (TX_UNDERRUN|EXCESSIVE_COL 66662306a36Sopenharmony_ci |LATE_COL|JABBER_TIMEOUT)) { 66762306a36Sopenharmony_ci net->stats.tx_errors++; 66862306a36Sopenharmony_ci if (d[0] & TX_UNDERRUN) 66962306a36Sopenharmony_ci net->stats.tx_fifo_errors++; 67062306a36Sopenharmony_ci if (d[0] & (EXCESSIVE_COL | JABBER_TIMEOUT)) 67162306a36Sopenharmony_ci net->stats.tx_aborted_errors++; 67262306a36Sopenharmony_ci if (d[0] & LATE_COL) 67362306a36Sopenharmony_ci net->stats.tx_window_errors++; 67462306a36Sopenharmony_ci } 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci /* d[5].LINK_STATUS lies on some adapters. 67762306a36Sopenharmony_ci * d[0].NO_CARRIER kicks in only with failed TX. 67862306a36Sopenharmony_ci * ... so monitoring with MII may be safest. 67962306a36Sopenharmony_ci */ 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci /* bytes 3-4 == rx_lostpkt, reg 2E/2F */ 68262306a36Sopenharmony_ci net->stats.rx_missed_errors += ((d[3] & 0x7f) << 8) | d[4]; 68362306a36Sopenharmony_ci } 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_ci res = usb_submit_urb(urb, GFP_ATOMIC); 68662306a36Sopenharmony_ci if (res == -ENODEV) 68762306a36Sopenharmony_ci netif_device_detach(pegasus->net); 68862306a36Sopenharmony_ci if (res) 68962306a36Sopenharmony_ci netif_err(pegasus, timer, net, 69062306a36Sopenharmony_ci "can't resubmit interrupt urb, %d\n", res); 69162306a36Sopenharmony_ci} 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_cistatic void pegasus_tx_timeout(struct net_device *net, unsigned int txqueue) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 69662306a36Sopenharmony_ci netif_warn(pegasus, timer, net, "tx timeout\n"); 69762306a36Sopenharmony_ci usb_unlink_urb(pegasus->tx_urb); 69862306a36Sopenharmony_ci net->stats.tx_errors++; 69962306a36Sopenharmony_ci} 70062306a36Sopenharmony_ci 70162306a36Sopenharmony_cistatic netdev_tx_t pegasus_start_xmit(struct sk_buff *skb, 70262306a36Sopenharmony_ci struct net_device *net) 70362306a36Sopenharmony_ci{ 70462306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 70562306a36Sopenharmony_ci int count = ((skb->len + 2) & 0x3f) ? skb->len + 2 : skb->len + 3; 70662306a36Sopenharmony_ci int res; 70762306a36Sopenharmony_ci __u16 l16 = skb->len; 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci netif_stop_queue(net); 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci ((__le16 *) pegasus->tx_buff)[0] = cpu_to_le16(l16); 71262306a36Sopenharmony_ci skb_copy_from_linear_data(skb, pegasus->tx_buff + 2, skb->len); 71362306a36Sopenharmony_ci usb_fill_bulk_urb(pegasus->tx_urb, pegasus->usb, 71462306a36Sopenharmony_ci usb_sndbulkpipe(pegasus->usb, 2), 71562306a36Sopenharmony_ci pegasus->tx_buff, count, 71662306a36Sopenharmony_ci write_bulk_callback, pegasus); 71762306a36Sopenharmony_ci if ((res = usb_submit_urb(pegasus->tx_urb, GFP_ATOMIC))) { 71862306a36Sopenharmony_ci netif_warn(pegasus, tx_err, net, "fail tx, %d\n", res); 71962306a36Sopenharmony_ci switch (res) { 72062306a36Sopenharmony_ci case -EPIPE: /* stall, or disconnect from TT */ 72162306a36Sopenharmony_ci /* cleanup should already have been scheduled */ 72262306a36Sopenharmony_ci break; 72362306a36Sopenharmony_ci case -ENODEV: /* disconnect() upcoming */ 72462306a36Sopenharmony_ci case -EPERM: 72562306a36Sopenharmony_ci netif_device_detach(pegasus->net); 72662306a36Sopenharmony_ci break; 72762306a36Sopenharmony_ci default: 72862306a36Sopenharmony_ci net->stats.tx_errors++; 72962306a36Sopenharmony_ci netif_start_queue(net); 73062306a36Sopenharmony_ci } 73162306a36Sopenharmony_ci } else { 73262306a36Sopenharmony_ci net->stats.tx_packets++; 73362306a36Sopenharmony_ci net->stats.tx_bytes += skb->len; 73462306a36Sopenharmony_ci } 73562306a36Sopenharmony_ci dev_kfree_skb(skb); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci return NETDEV_TX_OK; 73862306a36Sopenharmony_ci} 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_cistatic inline void disable_net_traffic(pegasus_t *pegasus) 74162306a36Sopenharmony_ci{ 74262306a36Sopenharmony_ci __le16 tmp = cpu_to_le16(0); 74362306a36Sopenharmony_ci 74462306a36Sopenharmony_ci set_registers(pegasus, EthCtrl0, sizeof(tmp), &tmp); 74562306a36Sopenharmony_ci} 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_cistatic inline int get_interrupt_interval(pegasus_t *pegasus) 74862306a36Sopenharmony_ci{ 74962306a36Sopenharmony_ci u16 data; 75062306a36Sopenharmony_ci u8 interval; 75162306a36Sopenharmony_ci int ret; 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci ret = read_eprom_word(pegasus, 4, &data); 75462306a36Sopenharmony_ci if (ret < 0) 75562306a36Sopenharmony_ci return ret; 75662306a36Sopenharmony_ci 75762306a36Sopenharmony_ci interval = data >> 8; 75862306a36Sopenharmony_ci if (pegasus->usb->speed != USB_SPEED_HIGH) { 75962306a36Sopenharmony_ci if (interval < 0x80) { 76062306a36Sopenharmony_ci netif_info(pegasus, timer, pegasus->net, 76162306a36Sopenharmony_ci "intr interval changed from %ums to %ums\n", 76262306a36Sopenharmony_ci interval, 0x80); 76362306a36Sopenharmony_ci interval = 0x80; 76462306a36Sopenharmony_ci data = (data & 0x00FF) | ((u16)interval << 8); 76562306a36Sopenharmony_ci#ifdef PEGASUS_WRITE_EEPROM 76662306a36Sopenharmony_ci write_eprom_word(pegasus, 4, data); 76762306a36Sopenharmony_ci#endif 76862306a36Sopenharmony_ci } 76962306a36Sopenharmony_ci } 77062306a36Sopenharmony_ci pegasus->intr_interval = interval; 77162306a36Sopenharmony_ci 77262306a36Sopenharmony_ci return 0; 77362306a36Sopenharmony_ci} 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_cistatic void set_carrier(struct net_device *net) 77662306a36Sopenharmony_ci{ 77762306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 77862306a36Sopenharmony_ci u16 tmp; 77962306a36Sopenharmony_ci 78062306a36Sopenharmony_ci if (read_mii_word(pegasus, pegasus->phy, MII_BMSR, &tmp)) 78162306a36Sopenharmony_ci return; 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ci if (tmp & BMSR_LSTATUS) 78462306a36Sopenharmony_ci netif_carrier_on(net); 78562306a36Sopenharmony_ci else 78662306a36Sopenharmony_ci netif_carrier_off(net); 78762306a36Sopenharmony_ci} 78862306a36Sopenharmony_ci 78962306a36Sopenharmony_cistatic void free_all_urbs(pegasus_t *pegasus) 79062306a36Sopenharmony_ci{ 79162306a36Sopenharmony_ci usb_free_urb(pegasus->intr_urb); 79262306a36Sopenharmony_ci usb_free_urb(pegasus->tx_urb); 79362306a36Sopenharmony_ci usb_free_urb(pegasus->rx_urb); 79462306a36Sopenharmony_ci} 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_cistatic void unlink_all_urbs(pegasus_t *pegasus) 79762306a36Sopenharmony_ci{ 79862306a36Sopenharmony_ci usb_kill_urb(pegasus->intr_urb); 79962306a36Sopenharmony_ci usb_kill_urb(pegasus->tx_urb); 80062306a36Sopenharmony_ci usb_kill_urb(pegasus->rx_urb); 80162306a36Sopenharmony_ci} 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_cistatic int alloc_urbs(pegasus_t *pegasus) 80462306a36Sopenharmony_ci{ 80562306a36Sopenharmony_ci int res = -ENOMEM; 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci pegasus->rx_urb = usb_alloc_urb(0, GFP_KERNEL); 80862306a36Sopenharmony_ci if (!pegasus->rx_urb) { 80962306a36Sopenharmony_ci return res; 81062306a36Sopenharmony_ci } 81162306a36Sopenharmony_ci pegasus->tx_urb = usb_alloc_urb(0, GFP_KERNEL); 81262306a36Sopenharmony_ci if (!pegasus->tx_urb) { 81362306a36Sopenharmony_ci usb_free_urb(pegasus->rx_urb); 81462306a36Sopenharmony_ci return res; 81562306a36Sopenharmony_ci } 81662306a36Sopenharmony_ci pegasus->intr_urb = usb_alloc_urb(0, GFP_KERNEL); 81762306a36Sopenharmony_ci if (!pegasus->intr_urb) { 81862306a36Sopenharmony_ci usb_free_urb(pegasus->tx_urb); 81962306a36Sopenharmony_ci usb_free_urb(pegasus->rx_urb); 82062306a36Sopenharmony_ci return res; 82162306a36Sopenharmony_ci } 82262306a36Sopenharmony_ci 82362306a36Sopenharmony_ci return 0; 82462306a36Sopenharmony_ci} 82562306a36Sopenharmony_ci 82662306a36Sopenharmony_cistatic int pegasus_open(struct net_device *net) 82762306a36Sopenharmony_ci{ 82862306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 82962306a36Sopenharmony_ci int res=-ENOMEM; 83062306a36Sopenharmony_ci 83162306a36Sopenharmony_ci if (pegasus->rx_skb == NULL) 83262306a36Sopenharmony_ci pegasus->rx_skb = __netdev_alloc_skb_ip_align(pegasus->net, 83362306a36Sopenharmony_ci PEGASUS_MTU, 83462306a36Sopenharmony_ci GFP_KERNEL); 83562306a36Sopenharmony_ci if (!pegasus->rx_skb) 83662306a36Sopenharmony_ci goto exit; 83762306a36Sopenharmony_ci 83862306a36Sopenharmony_ci set_registers(pegasus, EthID, 6, net->dev_addr); 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ci usb_fill_bulk_urb(pegasus->rx_urb, pegasus->usb, 84162306a36Sopenharmony_ci usb_rcvbulkpipe(pegasus->usb, 1), 84262306a36Sopenharmony_ci pegasus->rx_skb->data, PEGASUS_MTU, 84362306a36Sopenharmony_ci read_bulk_callback, pegasus); 84462306a36Sopenharmony_ci if ((res = usb_submit_urb(pegasus->rx_urb, GFP_KERNEL))) { 84562306a36Sopenharmony_ci if (res == -ENODEV) 84662306a36Sopenharmony_ci netif_device_detach(pegasus->net); 84762306a36Sopenharmony_ci netif_dbg(pegasus, ifup, net, "failed rx_urb, %d\n", res); 84862306a36Sopenharmony_ci goto exit; 84962306a36Sopenharmony_ci } 85062306a36Sopenharmony_ci 85162306a36Sopenharmony_ci usb_fill_int_urb(pegasus->intr_urb, pegasus->usb, 85262306a36Sopenharmony_ci usb_rcvintpipe(pegasus->usb, 3), 85362306a36Sopenharmony_ci pegasus->intr_buff, sizeof(pegasus->intr_buff), 85462306a36Sopenharmony_ci intr_callback, pegasus, pegasus->intr_interval); 85562306a36Sopenharmony_ci if ((res = usb_submit_urb(pegasus->intr_urb, GFP_KERNEL))) { 85662306a36Sopenharmony_ci if (res == -ENODEV) 85762306a36Sopenharmony_ci netif_device_detach(pegasus->net); 85862306a36Sopenharmony_ci netif_dbg(pegasus, ifup, net, "failed intr_urb, %d\n", res); 85962306a36Sopenharmony_ci usb_kill_urb(pegasus->rx_urb); 86062306a36Sopenharmony_ci goto exit; 86162306a36Sopenharmony_ci } 86262306a36Sopenharmony_ci res = enable_net_traffic(net, pegasus->usb); 86362306a36Sopenharmony_ci if (res < 0) { 86462306a36Sopenharmony_ci netif_dbg(pegasus, ifup, net, 86562306a36Sopenharmony_ci "can't enable_net_traffic() - %d\n", res); 86662306a36Sopenharmony_ci res = -EIO; 86762306a36Sopenharmony_ci usb_kill_urb(pegasus->rx_urb); 86862306a36Sopenharmony_ci usb_kill_urb(pegasus->intr_urb); 86962306a36Sopenharmony_ci goto exit; 87062306a36Sopenharmony_ci } 87162306a36Sopenharmony_ci set_carrier(net); 87262306a36Sopenharmony_ci netif_start_queue(net); 87362306a36Sopenharmony_ci netif_dbg(pegasus, ifup, net, "open\n"); 87462306a36Sopenharmony_ci res = 0; 87562306a36Sopenharmony_ciexit: 87662306a36Sopenharmony_ci return res; 87762306a36Sopenharmony_ci} 87862306a36Sopenharmony_ci 87962306a36Sopenharmony_cistatic int pegasus_close(struct net_device *net) 88062306a36Sopenharmony_ci{ 88162306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci netif_stop_queue(net); 88462306a36Sopenharmony_ci if (!(pegasus->flags & PEGASUS_UNPLUG)) 88562306a36Sopenharmony_ci disable_net_traffic(pegasus); 88662306a36Sopenharmony_ci tasklet_kill(&pegasus->rx_tl); 88762306a36Sopenharmony_ci unlink_all_urbs(pegasus); 88862306a36Sopenharmony_ci 88962306a36Sopenharmony_ci return 0; 89062306a36Sopenharmony_ci} 89162306a36Sopenharmony_ci 89262306a36Sopenharmony_cistatic void pegasus_get_drvinfo(struct net_device *dev, 89362306a36Sopenharmony_ci struct ethtool_drvinfo *info) 89462306a36Sopenharmony_ci{ 89562306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 89662306a36Sopenharmony_ci 89762306a36Sopenharmony_ci strscpy(info->driver, driver_name, sizeof(info->driver)); 89862306a36Sopenharmony_ci usb_make_path(pegasus->usb, info->bus_info, sizeof(info->bus_info)); 89962306a36Sopenharmony_ci} 90062306a36Sopenharmony_ci 90162306a36Sopenharmony_ci/* also handles three patterns of some kind in hardware */ 90262306a36Sopenharmony_ci#define WOL_SUPPORTED (WAKE_MAGIC|WAKE_PHY) 90362306a36Sopenharmony_ci 90462306a36Sopenharmony_cistatic void 90562306a36Sopenharmony_cipegasus_get_wol(struct net_device *dev, struct ethtool_wolinfo *wol) 90662306a36Sopenharmony_ci{ 90762306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_ci wol->supported = WAKE_MAGIC | WAKE_PHY; 91062306a36Sopenharmony_ci wol->wolopts = pegasus->wolopts; 91162306a36Sopenharmony_ci} 91262306a36Sopenharmony_ci 91362306a36Sopenharmony_cistatic int 91462306a36Sopenharmony_cipegasus_set_wol(struct net_device *dev, struct ethtool_wolinfo *wol) 91562306a36Sopenharmony_ci{ 91662306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 91762306a36Sopenharmony_ci u8 reg78 = 0x04; 91862306a36Sopenharmony_ci int ret; 91962306a36Sopenharmony_ci 92062306a36Sopenharmony_ci if (wol->wolopts & ~WOL_SUPPORTED) 92162306a36Sopenharmony_ci return -EINVAL; 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_ci if (wol->wolopts & WAKE_MAGIC) 92462306a36Sopenharmony_ci reg78 |= 0x80; 92562306a36Sopenharmony_ci if (wol->wolopts & WAKE_PHY) 92662306a36Sopenharmony_ci reg78 |= 0x40; 92762306a36Sopenharmony_ci /* FIXME this 0x10 bit still needs to get set in the chip... */ 92862306a36Sopenharmony_ci if (wol->wolopts) 92962306a36Sopenharmony_ci pegasus->eth_regs[0] |= 0x10; 93062306a36Sopenharmony_ci else 93162306a36Sopenharmony_ci pegasus->eth_regs[0] &= ~0x10; 93262306a36Sopenharmony_ci pegasus->wolopts = wol->wolopts; 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci ret = set_register(pegasus, WakeupControl, reg78); 93562306a36Sopenharmony_ci if (!ret) 93662306a36Sopenharmony_ci ret = device_set_wakeup_enable(&pegasus->usb->dev, 93762306a36Sopenharmony_ci wol->wolopts); 93862306a36Sopenharmony_ci return ret; 93962306a36Sopenharmony_ci} 94062306a36Sopenharmony_ci 94162306a36Sopenharmony_cistatic inline void pegasus_reset_wol(struct net_device *dev) 94262306a36Sopenharmony_ci{ 94362306a36Sopenharmony_ci struct ethtool_wolinfo wol; 94462306a36Sopenharmony_ci 94562306a36Sopenharmony_ci memset(&wol, 0, sizeof wol); 94662306a36Sopenharmony_ci (void) pegasus_set_wol(dev, &wol); 94762306a36Sopenharmony_ci} 94862306a36Sopenharmony_ci 94962306a36Sopenharmony_cistatic int 95062306a36Sopenharmony_cipegasus_get_link_ksettings(struct net_device *dev, 95162306a36Sopenharmony_ci struct ethtool_link_ksettings *ecmd) 95262306a36Sopenharmony_ci{ 95362306a36Sopenharmony_ci pegasus_t *pegasus; 95462306a36Sopenharmony_ci 95562306a36Sopenharmony_ci pegasus = netdev_priv(dev); 95662306a36Sopenharmony_ci mii_ethtool_get_link_ksettings(&pegasus->mii, ecmd); 95762306a36Sopenharmony_ci return 0; 95862306a36Sopenharmony_ci} 95962306a36Sopenharmony_ci 96062306a36Sopenharmony_cistatic int 96162306a36Sopenharmony_cipegasus_set_link_ksettings(struct net_device *dev, 96262306a36Sopenharmony_ci const struct ethtool_link_ksettings *ecmd) 96362306a36Sopenharmony_ci{ 96462306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 96562306a36Sopenharmony_ci return mii_ethtool_set_link_ksettings(&pegasus->mii, ecmd); 96662306a36Sopenharmony_ci} 96762306a36Sopenharmony_ci 96862306a36Sopenharmony_cistatic int pegasus_nway_reset(struct net_device *dev) 96962306a36Sopenharmony_ci{ 97062306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 97162306a36Sopenharmony_ci return mii_nway_restart(&pegasus->mii); 97262306a36Sopenharmony_ci} 97362306a36Sopenharmony_ci 97462306a36Sopenharmony_cistatic u32 pegasus_get_link(struct net_device *dev) 97562306a36Sopenharmony_ci{ 97662306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 97762306a36Sopenharmony_ci return mii_link_ok(&pegasus->mii); 97862306a36Sopenharmony_ci} 97962306a36Sopenharmony_ci 98062306a36Sopenharmony_cistatic u32 pegasus_get_msglevel(struct net_device *dev) 98162306a36Sopenharmony_ci{ 98262306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 98362306a36Sopenharmony_ci return pegasus->msg_enable; 98462306a36Sopenharmony_ci} 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_cistatic void pegasus_set_msglevel(struct net_device *dev, u32 v) 98762306a36Sopenharmony_ci{ 98862306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(dev); 98962306a36Sopenharmony_ci pegasus->msg_enable = v; 99062306a36Sopenharmony_ci} 99162306a36Sopenharmony_ci 99262306a36Sopenharmony_cistatic const struct ethtool_ops ops = { 99362306a36Sopenharmony_ci .get_drvinfo = pegasus_get_drvinfo, 99462306a36Sopenharmony_ci .nway_reset = pegasus_nway_reset, 99562306a36Sopenharmony_ci .get_link = pegasus_get_link, 99662306a36Sopenharmony_ci .get_msglevel = pegasus_get_msglevel, 99762306a36Sopenharmony_ci .set_msglevel = pegasus_set_msglevel, 99862306a36Sopenharmony_ci .get_wol = pegasus_get_wol, 99962306a36Sopenharmony_ci .set_wol = pegasus_set_wol, 100062306a36Sopenharmony_ci .get_link_ksettings = pegasus_get_link_ksettings, 100162306a36Sopenharmony_ci .set_link_ksettings = pegasus_set_link_ksettings, 100262306a36Sopenharmony_ci}; 100362306a36Sopenharmony_ci 100462306a36Sopenharmony_cistatic int pegasus_siocdevprivate(struct net_device *net, struct ifreq *rq, 100562306a36Sopenharmony_ci void __user *udata, int cmd) 100662306a36Sopenharmony_ci{ 100762306a36Sopenharmony_ci __u16 *data = (__u16 *) &rq->ifr_ifru; 100862306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 100962306a36Sopenharmony_ci int res; 101062306a36Sopenharmony_ci 101162306a36Sopenharmony_ci switch (cmd) { 101262306a36Sopenharmony_ci case SIOCDEVPRIVATE: 101362306a36Sopenharmony_ci data[0] = pegasus->phy; 101462306a36Sopenharmony_ci fallthrough; 101562306a36Sopenharmony_ci case SIOCDEVPRIVATE + 1: 101662306a36Sopenharmony_ci res = read_mii_word(pegasus, data[0], data[1] & 0x1f, &data[3]); 101762306a36Sopenharmony_ci break; 101862306a36Sopenharmony_ci case SIOCDEVPRIVATE + 2: 101962306a36Sopenharmony_ci if (!capable(CAP_NET_ADMIN)) 102062306a36Sopenharmony_ci return -EPERM; 102162306a36Sopenharmony_ci write_mii_word(pegasus, pegasus->phy, data[1] & 0x1f, &data[2]); 102262306a36Sopenharmony_ci res = 0; 102362306a36Sopenharmony_ci break; 102462306a36Sopenharmony_ci default: 102562306a36Sopenharmony_ci res = -EOPNOTSUPP; 102662306a36Sopenharmony_ci } 102762306a36Sopenharmony_ci return res; 102862306a36Sopenharmony_ci} 102962306a36Sopenharmony_ci 103062306a36Sopenharmony_cistatic void pegasus_set_multicast(struct net_device *net) 103162306a36Sopenharmony_ci{ 103262306a36Sopenharmony_ci pegasus_t *pegasus = netdev_priv(net); 103362306a36Sopenharmony_ci 103462306a36Sopenharmony_ci if (net->flags & IFF_PROMISC) { 103562306a36Sopenharmony_ci pegasus->eth_regs[EthCtrl2] |= RX_PROMISCUOUS; 103662306a36Sopenharmony_ci netif_info(pegasus, link, net, "Promiscuous mode enabled\n"); 103762306a36Sopenharmony_ci } else if (!netdev_mc_empty(net) || (net->flags & IFF_ALLMULTI)) { 103862306a36Sopenharmony_ci pegasus->eth_regs[EthCtrl0] |= RX_MULTICAST; 103962306a36Sopenharmony_ci pegasus->eth_regs[EthCtrl2] &= ~RX_PROMISCUOUS; 104062306a36Sopenharmony_ci netif_dbg(pegasus, link, net, "set allmulti\n"); 104162306a36Sopenharmony_ci } else { 104262306a36Sopenharmony_ci pegasus->eth_regs[EthCtrl0] &= ~RX_MULTICAST; 104362306a36Sopenharmony_ci pegasus->eth_regs[EthCtrl2] &= ~RX_PROMISCUOUS; 104462306a36Sopenharmony_ci } 104562306a36Sopenharmony_ci update_eth_regs_async(pegasus); 104662306a36Sopenharmony_ci} 104762306a36Sopenharmony_ci 104862306a36Sopenharmony_cistatic __u8 mii_phy_probe(pegasus_t *pegasus) 104962306a36Sopenharmony_ci{ 105062306a36Sopenharmony_ci int i, ret; 105162306a36Sopenharmony_ci __u16 tmp; 105262306a36Sopenharmony_ci 105362306a36Sopenharmony_ci for (i = 0; i < 32; i++) { 105462306a36Sopenharmony_ci ret = read_mii_word(pegasus, i, MII_BMSR, &tmp); 105562306a36Sopenharmony_ci if (ret < 0) 105662306a36Sopenharmony_ci goto fail; 105762306a36Sopenharmony_ci if (tmp == 0 || tmp == 0xffff || (tmp & BMSR_MEDIA) == 0) 105862306a36Sopenharmony_ci continue; 105962306a36Sopenharmony_ci else 106062306a36Sopenharmony_ci return i; 106162306a36Sopenharmony_ci } 106262306a36Sopenharmony_cifail: 106362306a36Sopenharmony_ci return 0xff; 106462306a36Sopenharmony_ci} 106562306a36Sopenharmony_ci 106662306a36Sopenharmony_cistatic inline void setup_pegasus_II(pegasus_t *pegasus) 106762306a36Sopenharmony_ci{ 106862306a36Sopenharmony_ci int ret; 106962306a36Sopenharmony_ci __u8 data = 0xa5; 107062306a36Sopenharmony_ci 107162306a36Sopenharmony_ci set_register(pegasus, Reg1d, 0); 107262306a36Sopenharmony_ci set_register(pegasus, Reg7b, 1); 107362306a36Sopenharmony_ci msleep(100); 107462306a36Sopenharmony_ci if ((pegasus->features & HAS_HOME_PNA) && mii_mode) 107562306a36Sopenharmony_ci set_register(pegasus, Reg7b, 0); 107662306a36Sopenharmony_ci else 107762306a36Sopenharmony_ci set_register(pegasus, Reg7b, 2); 107862306a36Sopenharmony_ci 107962306a36Sopenharmony_ci set_register(pegasus, 0x83, data); 108062306a36Sopenharmony_ci ret = get_registers(pegasus, 0x83, 1, &data); 108162306a36Sopenharmony_ci if (ret < 0) 108262306a36Sopenharmony_ci goto fail; 108362306a36Sopenharmony_ci 108462306a36Sopenharmony_ci if (data == 0xa5) 108562306a36Sopenharmony_ci pegasus->chip = 0x8513; 108662306a36Sopenharmony_ci else 108762306a36Sopenharmony_ci pegasus->chip = 0; 108862306a36Sopenharmony_ci 108962306a36Sopenharmony_ci set_register(pegasus, 0x80, 0xc0); 109062306a36Sopenharmony_ci set_register(pegasus, 0x83, 0xff); 109162306a36Sopenharmony_ci set_register(pegasus, 0x84, 0x01); 109262306a36Sopenharmony_ci 109362306a36Sopenharmony_ci if (pegasus->features & HAS_HOME_PNA && mii_mode) 109462306a36Sopenharmony_ci set_register(pegasus, Reg81, 6); 109562306a36Sopenharmony_ci else 109662306a36Sopenharmony_ci set_register(pegasus, Reg81, 2); 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci return; 109962306a36Sopenharmony_cifail: 110062306a36Sopenharmony_ci netif_dbg(pegasus, drv, pegasus->net, "%s failed\n", __func__); 110162306a36Sopenharmony_ci} 110262306a36Sopenharmony_ci 110362306a36Sopenharmony_cistatic void check_carrier(struct work_struct *work) 110462306a36Sopenharmony_ci{ 110562306a36Sopenharmony_ci pegasus_t *pegasus = container_of(work, pegasus_t, carrier_check.work); 110662306a36Sopenharmony_ci set_carrier(pegasus->net); 110762306a36Sopenharmony_ci if (!(pegasus->flags & PEGASUS_UNPLUG)) { 110862306a36Sopenharmony_ci queue_delayed_work(system_long_wq, &pegasus->carrier_check, 110962306a36Sopenharmony_ci CARRIER_CHECK_DELAY); 111062306a36Sopenharmony_ci } 111162306a36Sopenharmony_ci} 111262306a36Sopenharmony_ci 111362306a36Sopenharmony_cistatic int pegasus_blacklisted(struct usb_device *udev) 111462306a36Sopenharmony_ci{ 111562306a36Sopenharmony_ci struct usb_device_descriptor *udd = &udev->descriptor; 111662306a36Sopenharmony_ci 111762306a36Sopenharmony_ci /* Special quirk to keep the driver from handling the Belkin Bluetooth 111862306a36Sopenharmony_ci * dongle which happens to have the same ID. 111962306a36Sopenharmony_ci */ 112062306a36Sopenharmony_ci if ((udd->idVendor == cpu_to_le16(VENDOR_BELKIN)) && 112162306a36Sopenharmony_ci (udd->idProduct == cpu_to_le16(0x0121)) && 112262306a36Sopenharmony_ci (udd->bDeviceClass == USB_CLASS_WIRELESS_CONTROLLER) && 112362306a36Sopenharmony_ci (udd->bDeviceProtocol == 1)) 112462306a36Sopenharmony_ci return 1; 112562306a36Sopenharmony_ci 112662306a36Sopenharmony_ci return 0; 112762306a36Sopenharmony_ci} 112862306a36Sopenharmony_ci 112962306a36Sopenharmony_cistatic int pegasus_probe(struct usb_interface *intf, 113062306a36Sopenharmony_ci const struct usb_device_id *id) 113162306a36Sopenharmony_ci{ 113262306a36Sopenharmony_ci struct usb_device *dev = interface_to_usbdev(intf); 113362306a36Sopenharmony_ci struct net_device *net; 113462306a36Sopenharmony_ci pegasus_t *pegasus; 113562306a36Sopenharmony_ci int dev_index = id - pegasus_ids; 113662306a36Sopenharmony_ci int res = -ENOMEM; 113762306a36Sopenharmony_ci 113862306a36Sopenharmony_ci if (pegasus_blacklisted(dev)) 113962306a36Sopenharmony_ci return -ENODEV; 114062306a36Sopenharmony_ci 114162306a36Sopenharmony_ci net = alloc_etherdev(sizeof(struct pegasus)); 114262306a36Sopenharmony_ci if (!net) 114362306a36Sopenharmony_ci goto out; 114462306a36Sopenharmony_ci 114562306a36Sopenharmony_ci pegasus = netdev_priv(net); 114662306a36Sopenharmony_ci pegasus->dev_index = dev_index; 114762306a36Sopenharmony_ci 114862306a36Sopenharmony_ci res = alloc_urbs(pegasus); 114962306a36Sopenharmony_ci if (res < 0) { 115062306a36Sopenharmony_ci dev_err(&intf->dev, "can't allocate %s\n", "urbs"); 115162306a36Sopenharmony_ci goto out1; 115262306a36Sopenharmony_ci } 115362306a36Sopenharmony_ci 115462306a36Sopenharmony_ci tasklet_setup(&pegasus->rx_tl, rx_fixup); 115562306a36Sopenharmony_ci 115662306a36Sopenharmony_ci INIT_DELAYED_WORK(&pegasus->carrier_check, check_carrier); 115762306a36Sopenharmony_ci 115862306a36Sopenharmony_ci pegasus->intf = intf; 115962306a36Sopenharmony_ci pegasus->usb = dev; 116062306a36Sopenharmony_ci pegasus->net = net; 116162306a36Sopenharmony_ci 116262306a36Sopenharmony_ci 116362306a36Sopenharmony_ci net->watchdog_timeo = PEGASUS_TX_TIMEOUT; 116462306a36Sopenharmony_ci net->netdev_ops = &pegasus_netdev_ops; 116562306a36Sopenharmony_ci net->ethtool_ops = &ops; 116662306a36Sopenharmony_ci pegasus->mii.dev = net; 116762306a36Sopenharmony_ci pegasus->mii.mdio_read = mdio_read; 116862306a36Sopenharmony_ci pegasus->mii.mdio_write = mdio_write; 116962306a36Sopenharmony_ci pegasus->mii.phy_id_mask = 0x1f; 117062306a36Sopenharmony_ci pegasus->mii.reg_num_mask = 0x1f; 117162306a36Sopenharmony_ci pegasus->msg_enable = netif_msg_init(msg_level, NETIF_MSG_DRV 117262306a36Sopenharmony_ci | NETIF_MSG_PROBE | NETIF_MSG_LINK); 117362306a36Sopenharmony_ci 117462306a36Sopenharmony_ci pegasus->features = usb_dev_id[dev_index].private; 117562306a36Sopenharmony_ci res = get_interrupt_interval(pegasus); 117662306a36Sopenharmony_ci if (res) 117762306a36Sopenharmony_ci goto out2; 117862306a36Sopenharmony_ci if (reset_mac(pegasus)) { 117962306a36Sopenharmony_ci dev_err(&intf->dev, "can't reset MAC\n"); 118062306a36Sopenharmony_ci res = -EIO; 118162306a36Sopenharmony_ci goto out2; 118262306a36Sopenharmony_ci } 118362306a36Sopenharmony_ci set_ethernet_addr(pegasus); 118462306a36Sopenharmony_ci if (pegasus->features & PEGASUS_II) { 118562306a36Sopenharmony_ci dev_info(&intf->dev, "setup Pegasus II specific registers\n"); 118662306a36Sopenharmony_ci setup_pegasus_II(pegasus); 118762306a36Sopenharmony_ci } 118862306a36Sopenharmony_ci pegasus->phy = mii_phy_probe(pegasus); 118962306a36Sopenharmony_ci if (pegasus->phy == 0xff) { 119062306a36Sopenharmony_ci dev_warn(&intf->dev, "can't locate MII phy, using default\n"); 119162306a36Sopenharmony_ci pegasus->phy = 1; 119262306a36Sopenharmony_ci } 119362306a36Sopenharmony_ci pegasus->mii.phy_id = pegasus->phy; 119462306a36Sopenharmony_ci usb_set_intfdata(intf, pegasus); 119562306a36Sopenharmony_ci SET_NETDEV_DEV(net, &intf->dev); 119662306a36Sopenharmony_ci pegasus_reset_wol(net); 119762306a36Sopenharmony_ci res = register_netdev(net); 119862306a36Sopenharmony_ci if (res) 119962306a36Sopenharmony_ci goto out3; 120062306a36Sopenharmony_ci queue_delayed_work(system_long_wq, &pegasus->carrier_check, 120162306a36Sopenharmony_ci CARRIER_CHECK_DELAY); 120262306a36Sopenharmony_ci dev_info(&intf->dev, "%s, %s, %pM\n", net->name, 120362306a36Sopenharmony_ci usb_dev_id[dev_index].name, net->dev_addr); 120462306a36Sopenharmony_ci return 0; 120562306a36Sopenharmony_ci 120662306a36Sopenharmony_ciout3: 120762306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 120862306a36Sopenharmony_ciout2: 120962306a36Sopenharmony_ci free_all_urbs(pegasus); 121062306a36Sopenharmony_ciout1: 121162306a36Sopenharmony_ci free_netdev(net); 121262306a36Sopenharmony_ciout: 121362306a36Sopenharmony_ci return res; 121462306a36Sopenharmony_ci} 121562306a36Sopenharmony_ci 121662306a36Sopenharmony_cistatic void pegasus_disconnect(struct usb_interface *intf) 121762306a36Sopenharmony_ci{ 121862306a36Sopenharmony_ci struct pegasus *pegasus = usb_get_intfdata(intf); 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_ci usb_set_intfdata(intf, NULL); 122162306a36Sopenharmony_ci if (!pegasus) { 122262306a36Sopenharmony_ci dev_dbg(&intf->dev, "unregistering non-bound device?\n"); 122362306a36Sopenharmony_ci return; 122462306a36Sopenharmony_ci } 122562306a36Sopenharmony_ci 122662306a36Sopenharmony_ci pegasus->flags |= PEGASUS_UNPLUG; 122762306a36Sopenharmony_ci cancel_delayed_work_sync(&pegasus->carrier_check); 122862306a36Sopenharmony_ci unregister_netdev(pegasus->net); 122962306a36Sopenharmony_ci unlink_all_urbs(pegasus); 123062306a36Sopenharmony_ci free_all_urbs(pegasus); 123162306a36Sopenharmony_ci if (pegasus->rx_skb != NULL) { 123262306a36Sopenharmony_ci dev_kfree_skb(pegasus->rx_skb); 123362306a36Sopenharmony_ci pegasus->rx_skb = NULL; 123462306a36Sopenharmony_ci } 123562306a36Sopenharmony_ci free_netdev(pegasus->net); 123662306a36Sopenharmony_ci} 123762306a36Sopenharmony_ci 123862306a36Sopenharmony_cistatic int pegasus_suspend(struct usb_interface *intf, pm_message_t message) 123962306a36Sopenharmony_ci{ 124062306a36Sopenharmony_ci struct pegasus *pegasus = usb_get_intfdata(intf); 124162306a36Sopenharmony_ci 124262306a36Sopenharmony_ci netif_device_detach(pegasus->net); 124362306a36Sopenharmony_ci cancel_delayed_work_sync(&pegasus->carrier_check); 124462306a36Sopenharmony_ci if (netif_running(pegasus->net)) { 124562306a36Sopenharmony_ci usb_kill_urb(pegasus->rx_urb); 124662306a36Sopenharmony_ci usb_kill_urb(pegasus->intr_urb); 124762306a36Sopenharmony_ci } 124862306a36Sopenharmony_ci return 0; 124962306a36Sopenharmony_ci} 125062306a36Sopenharmony_ci 125162306a36Sopenharmony_cistatic int pegasus_resume(struct usb_interface *intf) 125262306a36Sopenharmony_ci{ 125362306a36Sopenharmony_ci struct pegasus *pegasus = usb_get_intfdata(intf); 125462306a36Sopenharmony_ci 125562306a36Sopenharmony_ci netif_device_attach(pegasus->net); 125662306a36Sopenharmony_ci if (netif_running(pegasus->net)) { 125762306a36Sopenharmony_ci pegasus->rx_urb->status = 0; 125862306a36Sopenharmony_ci pegasus->rx_urb->actual_length = 0; 125962306a36Sopenharmony_ci read_bulk_callback(pegasus->rx_urb); 126062306a36Sopenharmony_ci 126162306a36Sopenharmony_ci pegasus->intr_urb->status = 0; 126262306a36Sopenharmony_ci pegasus->intr_urb->actual_length = 0; 126362306a36Sopenharmony_ci intr_callback(pegasus->intr_urb); 126462306a36Sopenharmony_ci } 126562306a36Sopenharmony_ci queue_delayed_work(system_long_wq, &pegasus->carrier_check, 126662306a36Sopenharmony_ci CARRIER_CHECK_DELAY); 126762306a36Sopenharmony_ci return 0; 126862306a36Sopenharmony_ci} 126962306a36Sopenharmony_ci 127062306a36Sopenharmony_cistatic const struct net_device_ops pegasus_netdev_ops = { 127162306a36Sopenharmony_ci .ndo_open = pegasus_open, 127262306a36Sopenharmony_ci .ndo_stop = pegasus_close, 127362306a36Sopenharmony_ci .ndo_siocdevprivate = pegasus_siocdevprivate, 127462306a36Sopenharmony_ci .ndo_start_xmit = pegasus_start_xmit, 127562306a36Sopenharmony_ci .ndo_set_rx_mode = pegasus_set_multicast, 127662306a36Sopenharmony_ci .ndo_tx_timeout = pegasus_tx_timeout, 127762306a36Sopenharmony_ci .ndo_set_mac_address = eth_mac_addr, 127862306a36Sopenharmony_ci .ndo_validate_addr = eth_validate_addr, 127962306a36Sopenharmony_ci}; 128062306a36Sopenharmony_ci 128162306a36Sopenharmony_cistatic struct usb_driver pegasus_driver = { 128262306a36Sopenharmony_ci .name = driver_name, 128362306a36Sopenharmony_ci .probe = pegasus_probe, 128462306a36Sopenharmony_ci .disconnect = pegasus_disconnect, 128562306a36Sopenharmony_ci .id_table = pegasus_ids, 128662306a36Sopenharmony_ci .suspend = pegasus_suspend, 128762306a36Sopenharmony_ci .resume = pegasus_resume, 128862306a36Sopenharmony_ci .disable_hub_initiated_lpm = 1, 128962306a36Sopenharmony_ci}; 129062306a36Sopenharmony_ci 129162306a36Sopenharmony_cistatic void __init parse_id(char *id) 129262306a36Sopenharmony_ci{ 129362306a36Sopenharmony_ci unsigned int vendor_id = 0, device_id = 0, flags = 0, i = 0; 129462306a36Sopenharmony_ci char *token, *name = NULL; 129562306a36Sopenharmony_ci 129662306a36Sopenharmony_ci if ((token = strsep(&id, ":")) != NULL) 129762306a36Sopenharmony_ci name = token; 129862306a36Sopenharmony_ci /* name now points to a null terminated string*/ 129962306a36Sopenharmony_ci if ((token = strsep(&id, ":")) != NULL) 130062306a36Sopenharmony_ci vendor_id = simple_strtoul(token, NULL, 16); 130162306a36Sopenharmony_ci if ((token = strsep(&id, ":")) != NULL) 130262306a36Sopenharmony_ci device_id = simple_strtoul(token, NULL, 16); 130362306a36Sopenharmony_ci flags = simple_strtoul(id, NULL, 16); 130462306a36Sopenharmony_ci pr_info("%s: new device %s, vendor ID 0x%04x, device ID 0x%04x, flags: 0x%x\n", 130562306a36Sopenharmony_ci driver_name, name, vendor_id, device_id, flags); 130662306a36Sopenharmony_ci 130762306a36Sopenharmony_ci if (vendor_id > 0x10000 || vendor_id == 0) 130862306a36Sopenharmony_ci return; 130962306a36Sopenharmony_ci if (device_id > 0x10000 || device_id == 0) 131062306a36Sopenharmony_ci return; 131162306a36Sopenharmony_ci 131262306a36Sopenharmony_ci for (i = 0; usb_dev_id[i].name; i++); 131362306a36Sopenharmony_ci usb_dev_id[i].name = name; 131462306a36Sopenharmony_ci usb_dev_id[i].vendor = vendor_id; 131562306a36Sopenharmony_ci usb_dev_id[i].device = device_id; 131662306a36Sopenharmony_ci usb_dev_id[i].private = flags; 131762306a36Sopenharmony_ci pegasus_ids[i].match_flags = USB_DEVICE_ID_MATCH_DEVICE; 131862306a36Sopenharmony_ci pegasus_ids[i].idVendor = vendor_id; 131962306a36Sopenharmony_ci pegasus_ids[i].idProduct = device_id; 132062306a36Sopenharmony_ci} 132162306a36Sopenharmony_ci 132262306a36Sopenharmony_cistatic int __init pegasus_init(void) 132362306a36Sopenharmony_ci{ 132462306a36Sopenharmony_ci pr_info("%s: " DRIVER_DESC "\n", driver_name); 132562306a36Sopenharmony_ci if (devid) 132662306a36Sopenharmony_ci parse_id(devid); 132762306a36Sopenharmony_ci return usb_register(&pegasus_driver); 132862306a36Sopenharmony_ci} 132962306a36Sopenharmony_ci 133062306a36Sopenharmony_cistatic void __exit pegasus_exit(void) 133162306a36Sopenharmony_ci{ 133262306a36Sopenharmony_ci usb_deregister(&pegasus_driver); 133362306a36Sopenharmony_ci} 133462306a36Sopenharmony_ci 133562306a36Sopenharmony_cimodule_init(pegasus_init); 133662306a36Sopenharmony_cimodule_exit(pegasus_exit); 1337