root/drivers/net/usb/r8153_ecm.c
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/module.h>
#include <linux/netdevice.h>
#include <linux/mii.h>
#include <linux/usb.h>
#include <linux/usb/cdc.h>
#include <linux/usb/usbnet.h>
#include <linux/usb/r8152.h>

#define OCP_BASE                0xe86c

static int pla_read_word(struct usbnet *dev, u16 index)
{
        u16 byen = BYTE_EN_WORD;
        u8 shift = index & 2;
        __le32 tmp;
        int ret;

        if (shift)
                byen <<= shift;

        index &= ~3;

        ret = usbnet_read_cmd(dev, RTL8152_REQ_GET_REGS, RTL8152_REQT_READ, index,
                              MCU_TYPE_PLA | byen, &tmp, sizeof(tmp));
        if (ret < 0)
                goto out;

        ret = __le32_to_cpu(tmp);
        ret >>= (shift * 8);
        ret &= 0xffff;

out:
        return ret;
}

static int pla_write_word(struct usbnet *dev, u16 index, u32 data)
{
        u32 mask = 0xffff;
        u16 byen = BYTE_EN_WORD;
        u8 shift = index & 2;
        __le32 tmp;
        int ret;

        data &= mask;

        if (shift) {
                byen <<= shift;
                mask <<= (shift * 8);
                data <<= (shift * 8);
        }

        index &= ~3;

        ret = usbnet_read_cmd(dev, RTL8152_REQ_GET_REGS, RTL8152_REQT_READ, index,
                              MCU_TYPE_PLA | byen, &tmp, sizeof(tmp));

        if (ret < 0)
                goto out;

        data |= __le32_to_cpu(tmp) & ~mask;
        tmp = __cpu_to_le32(data);

        ret = usbnet_write_cmd(dev, RTL8152_REQ_SET_REGS, RTL8152_REQT_WRITE, index,
                               MCU_TYPE_PLA | byen, &tmp, sizeof(tmp));

out:
        return ret;
}

static int r8153_ecm_mdio_read(struct net_device *netdev, int phy_id, int reg)
{
        struct usbnet *dev = netdev_priv(netdev);
        int ret;

        ret = pla_write_word(dev, OCP_BASE, 0xa000);
        if (ret < 0)
                goto out;

        ret = pla_read_word(dev, 0xb400 + reg * 2);

out:
        return ret;
}

static void r8153_ecm_mdio_write(struct net_device *netdev, int phy_id, int reg, int val)
{
        struct usbnet *dev = netdev_priv(netdev);
        int ret;

        ret = pla_write_word(dev, OCP_BASE, 0xa000);
        if (ret < 0)
                return;

        ret = pla_write_word(dev, 0xb400 + reg * 2, val);
}

static int r8153_bind(struct usbnet *dev, struct usb_interface *intf)
{
        int status;

        status = usbnet_cdc_bind(dev, intf);
        if (status < 0)
                return status;

        dev->mii.dev = dev->net;
        dev->mii.mdio_read = r8153_ecm_mdio_read;
        dev->mii.mdio_write = r8153_ecm_mdio_write;
        dev->mii.reg_num_mask = 0x1f;
        dev->mii.supports_gmii = 1;

        return status;
}

static const struct driver_info r8153_info = {
        .description =  "RTL8153 ECM Device",
        .flags =        FLAG_ETHER,
        .bind =         r8153_bind,
        .unbind =       usbnet_cdc_unbind,
        .status =       usbnet_cdc_status,
        .manage_power = usbnet_manage_power,
};

static const struct usb_device_id products[] = {
/* Realtek RTL8153 Based USB 3.0 Ethernet Adapters */
{
        USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID_REALTEK, 0x8153, USB_CLASS_COMM,
                                      USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
        .driver_info = (unsigned long)&r8153_info,
},

/* Lenovo Powered USB-C Travel Hub (4X90S92381, based on Realtek RTL8153) */
{
        USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID_LENOVO, 0x721e, USB_CLASS_COMM,
                                      USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
        .driver_info = (unsigned long)&r8153_info,
},
/* Lenovo ThinkPad Hybrid USB-C with USB-A Dock (40af0135eu, based on Realtek RTL8153) */
{
        USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID_LENOVO, 0xa359, USB_CLASS_COMM,
                                      USB_CDC_SUBCLASS_ETHERNET, USB_CDC_PROTO_NONE),
        .driver_info = (unsigned long)&r8153_info,
},

        { },            /* END */
};
MODULE_DEVICE_TABLE(usb, products);

static int rtl8153_ecm_probe(struct usb_interface *intf,
                             const struct usb_device_id *id)
{
#if IS_REACHABLE(CONFIG_USB_RTL8152)
        if (rtl8152_get_version(intf))
                return -ENODEV;
#endif

        return usbnet_probe(intf, id);
}

static struct usb_driver r8153_ecm_driver = {
        .name =         "r8153_ecm",
        .id_table =     products,
        .probe =        rtl8153_ecm_probe,
        .disconnect =   usbnet_disconnect,
        .suspend =      usbnet_suspend,
        .resume =       usbnet_resume,
        .reset_resume = usbnet_resume,
        .supports_autosuspend = 1,
        .disable_hub_initiated_lpm = 1,
};

module_usb_driver(r8153_ecm_driver);

MODULE_AUTHOR("Hayes Wang");
MODULE_DESCRIPTION("Realtek USB ECM device");
MODULE_LICENSE("GPL");