162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci#include <linux/module.h> 362306a36Sopenharmony_ci#include <linux/netdevice.h> 462306a36Sopenharmony_ci#include <linux/mii.h> 562306a36Sopenharmony_ci#include <linux/usb.h> 662306a36Sopenharmony_ci#include <linux/usb/cdc.h> 762306a36Sopenharmony_ci#include <linux/usb/usbnet.h> 862306a36Sopenharmony_ci#include <linux/usb/r8152.h> 962306a36Sopenharmony_ci 1062306a36Sopenharmony_ci#define OCP_BASE 0xe86c 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_cistatic int pla_read_word(struct usbnet *dev, u16 index) 1362306a36Sopenharmony_ci{ 1462306a36Sopenharmony_ci u16 byen = BYTE_EN_WORD; 1562306a36Sopenharmony_ci u8 shift = index & 2; 1662306a36Sopenharmony_ci __le32 tmp; 1762306a36Sopenharmony_ci int ret; 1862306a36Sopenharmony_ci 1962306a36Sopenharmony_ci if (shift) 2062306a36Sopenharmony_ci byen <<= shift; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci index &= ~3; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci ret = usbnet_read_cmd(dev, RTL8152_REQ_GET_REGS, RTL8152_REQT_READ, index, 2562306a36Sopenharmony_ci MCU_TYPE_PLA | byen, &tmp, sizeof(tmp)); 2662306a36Sopenharmony_ci if (ret < 0) 2762306a36Sopenharmony_ci goto out; 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci ret = __le32_to_cpu(tmp); 3062306a36Sopenharmony_ci ret >>= (shift * 8); 3162306a36Sopenharmony_ci ret &= 0xffff; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ciout: 3462306a36Sopenharmony_ci return ret; 3562306a36Sopenharmony_ci} 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_cistatic int pla_write_word(struct usbnet *dev, u16 index, u32 data) 3862306a36Sopenharmony_ci{ 3962306a36Sopenharmony_ci u32 mask = 0xffff; 4062306a36Sopenharmony_ci u16 byen = BYTE_EN_WORD; 4162306a36Sopenharmony_ci u8 shift = index & 2; 4262306a36Sopenharmony_ci __le32 tmp; 4362306a36Sopenharmony_ci int ret; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci data &= mask; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci if (shift) { 4862306a36Sopenharmony_ci byen <<= shift; 4962306a36Sopenharmony_ci mask <<= (shift * 8); 5062306a36Sopenharmony_ci data <<= (shift * 8); 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci index &= ~3; 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci ret = usbnet_read_cmd(dev, RTL8152_REQ_GET_REGS, RTL8152_REQT_READ, index, 5662306a36Sopenharmony_ci MCU_TYPE_PLA | byen, &tmp, sizeof(tmp)); 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci if (ret < 0) 5962306a36Sopenharmony_ci goto out; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci data |= __le32_to_cpu(tmp) & ~mask; 6262306a36Sopenharmony_ci tmp = __cpu_to_le32(data); 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci ret = usbnet_write_cmd(dev, RTL8152_REQ_SET_REGS, RTL8152_REQT_WRITE, index, 6562306a36Sopenharmony_ci MCU_TYPE_PLA | byen, &tmp, sizeof(tmp)); 6662306a36Sopenharmony_ci 6762306a36Sopenharmony_ciout: 6862306a36Sopenharmony_ci return ret; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cistatic int r8153_ecm_mdio_read(struct net_device *netdev, int phy_id, int reg) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci struct usbnet *dev = netdev_priv(netdev); 7462306a36Sopenharmony_ci int ret; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci ret = pla_write_word(dev, OCP_BASE, 0xa000); 7762306a36Sopenharmony_ci if (ret < 0) 7862306a36Sopenharmony_ci goto out; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci ret = pla_read_word(dev, 0xb400 + reg * 2); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ciout: 8362306a36Sopenharmony_ci return ret; 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic void r8153_ecm_mdio_write(struct net_device *netdev, int phy_id, int reg, int val) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci struct usbnet *dev = netdev_priv(netdev); 8962306a36Sopenharmony_ci int ret; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci ret = pla_write_word(dev, OCP_BASE, 0xa000); 9262306a36Sopenharmony_ci if (ret < 0) 9362306a36Sopenharmony_ci return; 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci ret = pla_write_word(dev, 0xb400 + reg * 2, val); 9662306a36Sopenharmony_ci} 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_cistatic int r8153_bind(struct usbnet *dev, struct usb_interface *intf) 9962306a36Sopenharmony_ci{ 10062306a36Sopenharmony_ci int status; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci status = usbnet_cdc_bind(dev, intf); 10362306a36Sopenharmony_ci if (status < 0) 10462306a36Sopenharmony_ci return status; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci dev->mii.dev = dev->net; 10762306a36Sopenharmony_ci dev->mii.mdio_read = r8153_ecm_mdio_read; 10862306a36Sopenharmony_ci dev->mii.mdio_write = r8153_ecm_mdio_write; 10962306a36Sopenharmony_ci dev->mii.reg_num_mask = 0x1f; 11062306a36Sopenharmony_ci dev->mii.supports_gmii = 1; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci return status; 11362306a36Sopenharmony_ci} 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_cistatic const struct driver_info r8153_info = { 11662306a36Sopenharmony_ci .description = "RTL8153 ECM Device", 11762306a36Sopenharmony_ci .flags = FLAG_ETHER, 11862306a36Sopenharmony_ci .bind = r8153_bind, 11962306a36Sopenharmony_ci .unbind = usbnet_cdc_unbind, 12062306a36Sopenharmony_ci .status = usbnet_cdc_status, 12162306a36Sopenharmony_ci .manage_power = usbnet_manage_power, 12262306a36Sopenharmony_ci}; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_cistatic const struct usb_device_id products[] = { 12562306a36Sopenharmony_ci/* Realtek RTL8153 Based USB 3.0 Ethernet Adapters */ 12662306a36Sopenharmony_ci{ 12762306a36Sopenharmony_ci USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID_REALTEK, 0x8153, USB_CLASS_COMM, 12862306a36Sopenharmony_ci USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), 12962306a36Sopenharmony_ci .driver_info = (unsigned long)&r8153_info, 13062306a36Sopenharmony_ci}, 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/* Lenovo Powered USB-C Travel Hub (4X90S92381, based on Realtek RTL8153) */ 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID_LENOVO, 0x721e, USB_CLASS_COMM, 13562306a36Sopenharmony_ci USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE), 13662306a36Sopenharmony_ci .driver_info = (unsigned long)&r8153_info, 13762306a36Sopenharmony_ci}, 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci { }, /* END */ 14062306a36Sopenharmony_ci}; 14162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, products); 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_cistatic int rtl8153_ecm_probe(struct usb_interface *intf, 14462306a36Sopenharmony_ci const struct usb_device_id *id) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci#if IS_REACHABLE(CONFIG_USB_RTL8152) 14762306a36Sopenharmony_ci if (rtl8152_get_version(intf)) 14862306a36Sopenharmony_ci return -ENODEV; 14962306a36Sopenharmony_ci#endif 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci return usbnet_probe(intf, id); 15262306a36Sopenharmony_ci} 15362306a36Sopenharmony_ci 15462306a36Sopenharmony_cistatic struct usb_driver r8153_ecm_driver = { 15562306a36Sopenharmony_ci .name = "r8153_ecm", 15662306a36Sopenharmony_ci .id_table = products, 15762306a36Sopenharmony_ci .probe = rtl8153_ecm_probe, 15862306a36Sopenharmony_ci .disconnect = usbnet_disconnect, 15962306a36Sopenharmony_ci .suspend = usbnet_suspend, 16062306a36Sopenharmony_ci .resume = usbnet_resume, 16162306a36Sopenharmony_ci .reset_resume = usbnet_resume, 16262306a36Sopenharmony_ci .supports_autosuspend = 1, 16362306a36Sopenharmony_ci .disable_hub_initiated_lpm = 1, 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cimodule_usb_driver(r8153_ecm_driver); 16762306a36Sopenharmony_ci 16862306a36Sopenharmony_ciMODULE_AUTHOR("Hayes Wang"); 16962306a36Sopenharmony_ciMODULE_DESCRIPTION("Realtek USB ECM device"); 17062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 171